HTTP 服务器推送:服务到服务,无需浏览器

HTTP Server-Push: Service to Service, without Browser

我正在开发一个基于云的后端 HTTP 服务,该服务将公开用于与一些本地系统集成。客户端系统由外部供应商定制,它们是具有自己数据库的后端系统。这些系统部署在我们客户的公司中,我们无法访问它们,也无法控制它们。我们向供应商提供我们的 API 规范,他们实施客户端代码。

我的服务与客户交换的数据格式基于XML并遵循一定的标准。供应商使用不同的编程语言实现他们的客户端系统,并且随着时间的推移会出现新的供应商。我希望尽可能多的客户能够使用我的服务。

我的大部分服务 API 都是类似 REST 的:它接收 HTTP 请求,处理它们,然后发回 HTTP 响应。

此外,我的服务积累了一些数据状态变化,需要定期将这些数据推送到客户端系统。由于以下限制,此用例似乎不适合传统的客户端-服务器 HTTP 请求-响应模型。

  1. 由于业务的性质,客户端系统不能打开自己的 HTTP API 端点,因此我的服务无法与它们建立出站 HTTP 连接传递数据状态通知。 IE。使用 WebHooks 不是一种选择。

  2. 同时,我的服务利益相关者需要记录确认数据状态通知已被客户端系统接受,因此像 Amazon SNS 这样的即发即弃系统似乎不适用。

我正在考虑解决这个问题的几种方法,但我不确定我是否遗漏了一些简单的选项或一些已经解决该问题的技术。因此这个问题。

问题文本已更新:选项已移至我自己的答案。

相关问题和资源

WebSockets 的一种替代方案,用于解决服务器→客户端通知问题,并带有来自客户端的确认似乎是 gRPC

  • 支持bidirectional streaming模式下服务端和客户端的双向通信
  • 它在 HTTP 2.0 之上工作。在我们的例子中,通过 HTTP 端口运行是必不可少的。
  • 有多个流行 languages and platforms 的客户端和服务器生成器。一件好事是我可以与供应商共享协议定义文件,并且可以确保我的服务和他们的客户使用相同的语言。

缺点:

  • 与 HTTP 相比,支持的语言和平台不多。如果基于 HTTP 1.1,问题中的备选方案 C 将更易于访问。 WebSockets 的存在时间也更长,我希望它比 gRPC 得到更广泛的采用。
  • 目前似乎并非所有 gRPC 实现都支持数据 XML 格式 according to FAQ。为了传输 XML 我的服务及其客户端必须将 XML 消息作为字节数组传输到 gRPC protobuf 消息中。
  • 使用 gRPC,cannot be done 在 general-purpose HTTP 1.1 负载平衡器上终止 TLS。需要 application-layer HTTP/2-aware 反向代理(负载均衡器),例如 Traefik。
    this and this 等方法允许 HTTP 1.1 兼容协议,但它们有自己的限制,例如有限数量的可用客户端或必要的客户端自定义。

在团队的帮助下,我最终自己找到了问题的答案。对于像我这样带着“如何安排从我的服务向其客户发送通知”的问题来到这里的人,这里是可用选项的概述。

WebHooks

这是客户端自己打开端点的时候。只要服务有一些通知要传递,服务就会调用客户端的端点。这样,客户端也充当服务,因此客户端和服务在通知传递期间交换角色。

使用 WebHooks,客户端必须能够使用 well-known 地址打开端点。如果客户端的软件在 NAT 或防火墙后运行,或者如果客户端是浏览器或移动应用程序,这会很复杂。

服务需要做好准备,客户端的 WebHook 端点可能并不总是在线并且可能并不总是健康的。

另一个问题是流量控制:应该在服务中采取特殊措施,不要让大量连接、请求 and/or 数据的客户端不堪重负。

轮询

这种情况下客户端还是客户端,服务还是服务,不像WebHooks。该服务提供了一个端点,客户端可以在其中连续请求新通知。此选项的优点是它不会改变连接方向和 request-response 方向,因此它适用于 HTTP-based 服务。

需要注意的是,轮询 API 应该具有一些丰富的语义才能相当可靠 if 丢失通知是不可接受的。 Google Pub/Sub pull 和 Amazon SQS 就是很好的例子。

这里有一些注意事项:

  1. 接收和删除通知应该是分开的操作。否则,如果服务在将通知发送给客户端之前删除了通知,而客户端未能处理该通知,则该通知将永远丢失。当删除操作与接收分开时,客户端被迫显式删除,这通常发生在成功处理之后。

  2. 如果客户端收到了通知但还没有删除它,可能不希望让其他参与者处理相同的通知(可能是同一客户端的并发进程) .因此必须在第一次收到通知后隐藏接收。

  3. 如果客户端由于错误、网络丢失或进程崩溃而未能在合理的时间内删除通知,服务必须使通知可见以便再次接收。这是允许最终处理通知的重试机制。

  4. 如果服务没有要传递的通知,它应该通过不立即传递空响应来阻止客户端的调用一段时间。否则,如果客户端在循环中轮询并立即响应,则循环迭代会很短,并且客户端会向服务发出过多的请求,增加网络、解析负载和请求计数。 nice-to have 功能是让服务解除阻塞并在某些通知出现时立即响应客户端。这有时称为“长轮询”。

HTTP Server-sent 事件

使用 HTTP Server-sent 事件客户端打开 HTTP 连接并向服务发送请求,然后服务可以发送多个事件(通知)而不是单个响应。连接 long-living 并且服务可以在事件准备就绪后立即发送。

缺点是通信是one-way,如果成功处理了事件,客户端没有办法通知服务。由于缺少此反馈,服务可能难以控制事件的发生率以防止客户端不堪重负。

WebSockets

创建 WebSocket 是为了启用任意 two-way 通信,因此这是服务向客户端发送通知的可行选项。客户端还可以将处理确认发送回服务。

WebSockets 已经存在了一段时间,应该得到许多框架和语言的支持。 WebSocket 连接始于 HTTP 1.1 连接,因此许多负载平衡器和反向代理应该支持基于 HTTPS 的 WebSockets。

WebSockets 通常用于浏览器和移动客户端,很少用于 service-to-service 通信。

gRPC

gRPC 在支持任意 two-way 通信方面类似于 WebSockets。 gRPC 的优势在于它以协议和消息格式定义文件为中心。这些文件用于生成对客户端和服务开发人员必不可少的代码。

gRPC 用于 service-to-service 通信,并且支持 grpc-web.

的浏览器客户端

gRPC 支持多个 poular 编程语言和平台,但支持范围比 HTTP 窄。

gRPC 在 HTTP/2 之上工作,这可能会导致反向代理和负载平衡器围绕 TLS 终止等问题出现困难。

消息队列 (PubSub)

最后,服务和客户端可以使用消息队列作为通知的传送机制。该服务将通知放在队列中,客户端从队列中接收它们。队列可以由 RabbitMQ、Kafka、Celery、Google PubSub、Amazon SQS 等许多系统之一提供。有多种具有不同属性的队列系统可供选择,选择一个本身就是一个挑战。也可以使用数据库来模拟队列。

必须在服务和拥有队列的客户端之间决定,即谁为队列付费。无论哪种方式,只要服务需要向其推送通知,排队系统和队列就应该可用,否则通知将丢失(除非服务在内部缓冲它们,使用另一个队列)。

队列通常用于 service-to-service 通信,但某些技术也允许浏览器作为客户端。

值得注意的是,在上面列出的其他选项中,服务端可能会使用“隐式”内部队列。原因之一是为了防止在没有客户端可以接收通知时丢失通知。还有许多其他很好的理由,例如让客户端按自己的节奏处理通知、允许最大化处理吞吐量、允许以固定容量处理尖峰流量。

在此选项中,队列“明确”用作传递机制,即服务不会在队列前面放置任何其他机制(HTTP、gRPC 或 WebSocket 端点)并让客户端接收来自队列的通知直接。

消息传递在组织微服务通信中很流行。

共同注意事项

在所有选项中,必须决定服务、客户和企业是否可以容忍丢失通知。如果由于处理错误、不可用等原因而丢失通知是可以接受的,那么一些更简单的技术选择是可能的。

从服务端监控客户端处理错误是很有价值的。这样服务所有者就知道哪些客户更容易崩溃,而无需询问他们。

如果使用队列(隐式或显式),监视队列的长度和最旧通知的存在时间很有价值。它让服务所有者可以判断客户端中数据的陈旧程度。

如果以仅在客户端成功处理后才删除通知的方式组织通知的传递,则当客户端无法处理时,同一通知可能会陷入无限接收循环。这种通知有时被称为“毒消息”。服务或排队系统应删除有毒消息,以防止客户端陷入无限循环。一种常见的做法是将有毒消息移到一个特殊的地方,有时称为“死信队列”,以便以后进行人为干预。