在我的团队的帮助下,我最终自己找到了问题的答案。对于像我这样带着“如何安排从我的服务向其客户发送通知”的问题来到这里的人,这里是可用选项的概述。
网络钩子
这是客户端自己打开端点的时候。只要服务有一些通知要传递,服务就会调用客户端的端点。这样,客户端也充当服务,因此客户端和服务在通知传递期间交换角色。
使用 WebHooks,客户端必须能够使用已知地址打开端点。如果客户端的软件在 NAT 或防火墙后面运行,或者客户端是浏览器或移动应用程序,这会很复杂。
服务需要做好准备,客户端的 WebHook 端点可能并不总是在线并且可能并不总是健康的。
另一个问题是流量控制:应在服务中采取特殊措施,以免大量连接、请求和/或数据使客户端不堪重负。
轮询
在这种情况下,客户端仍然是客户端,服务仍然是服务,这与 WebHooks 不同。该服务提供了一个端点,客户端可以在其中不断请求新通知。此选项的优点是它不会改变连接方向和请求-响应方向,因此它适用于基于 HTTP 的服务。
需要注意的是,轮询 API 应该具有一些丰富的语义,以便在如果通知丢失是不可接受的情况下相当可靠。很好的例子可能是 Google Pub/Sub pull 和 Amazon SQS。
这里有几个注意事项:
-
接收和删除通知应该是单独的操作。否则,如果服务在将通知发送给客户端之前删除了通知,并且客户端无法处理通知,则通知将永远丢失。当删除操作与接收分开时,客户端被强制执行显式删除,这通常发生在成功处理后。
-
如果客户端收到通知但尚未将其删除,则可能不希望让其他参与者处理相同的通知(可能是同一客户端的并发进程)。因此,必须在第一次收到通知后隐藏通知。
-
如果客户端由于错误、网络丢失或进程崩溃而未能在合理的时间内删除通知,服务必须使通知可见以便再次接收。这是允许最终处理通知的重试机制。
-
如果服务没有要传递的通知,它应该通过不立即传递空响应来阻止客户端的调用一段时间。否则,如果客户端循环轮询并立即响应,则循环迭代将很短,客户端将向服务提出过多请求,从而增加网络、解析负载和请求计数。一个不错的功能是,一旦出现一些交付通知,服务就会取消阻止并响应客户端。这有时被称为“长轮询”。
HTTP 服务器发送的事件
使用 HTTP 服务器发送事件,客户端打开 HTTP 连接并向服务发送请求,然后服务可以发送多个事件(通知)而不是单个响应。连接是长期存在的,服务一旦准备好就可以发送事件。
缺点是通信是单向的,如果成功处理了事件,客户端无法通知服务。由于没有这种反馈,服务可能难以控制事件的发生率以防止让客户端不堪重负。
WebSockets
创建 WebSockets 是为了启用任意双向通信,因此这是服务向客户端发送通知的可行选项。客户端还可以将处理确认发送回服务。
WebSockets 已经存在了一段时间,应该得到许多框架和语言的支持。 WebSocket 连接以 HTTP 1.1 连接开始,因此许多负载均衡器和反向代理应该支持基于 HTTPS 的 WebSocket。
WebSockets 通常用于浏览器和移动客户端,很少用于服务到服务的通信。
gRPC
gRPC 在某种程度上类似于 WebSockets,它支持任意双向通信。 gRPC 的优势在于它以协议和消息格式定义文件为中心。这些文件用于生成对客户端和服务开发人员至关重要的代码。
gRPC 用于服务到服务的通信,并且支持带有 grpc-web 的浏览器客户端。
gRPC 支持多种流行的编程语言和平台,但支持范围比 HTTP 窄。
gRPC 在 HTTP/2 之上工作,这可能会导致反向代理和负载平衡器在 TLS 终止等问题上遇到困难。
消息队列(PubSub)
最后,服务和客户端可以使用消息队列作为通知的传递机制。该服务将通知放在队列中,客户端从队列中接收它们。队列可以由许多系统之一提供,例如 RabbitMQ、Kafka、Celery、Google PubSub、Amazon SQS 等。有多种具有不同属性的队列系统可供选择,而选择一个本身就是一个挑战。队列也可以用数据库来模拟。
必须在服务和拥有队列的客户之间做出决定,即谁为它付费。无论哪种方式,只要服务需要向其推送通知,排队系统和队列都应该可用,否则通知将丢失(除非服务在内部使用另一个队列缓冲它们)。
队列通常用于服务到服务的通信,但某些技术也允许浏览器作为客户端。
值得注意的是,在上面列出的其他选项中,服务端可能会使用“隐式”内部队列。一个原因是防止在没有客户端可以接收通知时丢失通知。还有许多其他好的理由,例如让客户端按照自己的节奏处理通知、允许最大化处理吞吐量、允许处理具有固定容量的尖峰流量。
在此选项中,队列被“明确”用作传递机制,即服务不会在队列前面放置任何其他机制(HTTP、gRPC 或 WebSocket 端点),并让客户端直接从队列接收通知。
消息传递在组织微服务通信中很流行。
常见注意事项
在所有选项中,必须确定服务、客户端和业务是否可以容忍通知丢失。如果由于处理错误、不可用等而丢失通知是可以接受的,则可以选择一些更简单的技术选择。
从服务端监控客户端处理错误是很有价值的。通过这种方式,服务所有者无需询问即可知道哪些客户端更糟糕。
如果使用队列(隐式或显式),则监控队列的长度和最旧通知的年龄是很有价值的。它让服务所有者可以判断客户端中数据的陈旧程度。
如果通知的传递是以仅在客户端成功处理后才删除通知的方式组织的,则当客户端无法处理相同的通知时,可能会陷入无限接收循环。这种通知有时被称为“毒消息”。服务或排队系统应删除有毒消息,以防止客户端陷入无限循环。一种常见的做法是将有害消息移动到一个特殊的位置,有时称为“死信队列”,以便以后进行人工干预。