基于 Server-Sent Event 实现多 Clients 状态同步

client(browser)如何从 Server 端获取异步任务即时状态在很多架构中是需要解决的问题。在分布式架构中,尤其需要考虑多实例(client)之间的状态同步。

本文主要介绍微服务中基于服务提供的 Server-Sent Event 接口实现多个 client 间的状态同步。

基于 HTTP 协议的异步状态获取

基于HTTP 协议的异步任务状态获取方式一般有以下3种:

polling:client 不断的轮询 server。即 client 将请求请求发给 server 后,若 server 端有数据即刻返回,否则告知一个示意并立刻关闭请求释放连接;client 在没有获取到异步任务的终态时会等待一会儿继续向 server 建立连接并发送请求。

long-polling:client 轮询 server,附带一个 wait time。因为 HTTP协议是短链接,polling 方式很可能会产生大量的短连接请求,对 server 端资源造成浪费,甚至冲击 server。long-polling 会让 server 在没有任务状态数据的时候将请求 hold 住最多 wait time 时间。期间如果任务状态数据达到,会立刻把数据交给等待的请求并返回 client,同时断开连接。

server-sent event:client 连接 server,server 通过建立的连接向 client 发送数据(event)。虽然 client 向 server 请求建立的是一个 http 连接,但其实是一个设置了 no-cache&keep-alive 属性的 http 连接。设置了 keep-alive 属性的 HTTP 连接本质是建立了 tcp 长连接,多次请求复用连接。再加上 SSE client 一般会实现网络断开后的 tcp 重连,所以 server 可以把 event 持续通过连接推送给 client。

从上面的分析可以看出,SSE 基于HTTP协议实现了 server 到 client 的单向实时通信,同时在协议层规定了连接复用和断线重连提升了可用性和易用性。

下节主要从多 clients 的角度讲解如何应用 SSE 实现状态同步。

基于 SSE 获取服务的状态

Server-Sent Event 隶属于 W3C 制定的 HTML5 的规范,本质上是定义了一种 browser 和 server 间的 Pub-Sub 通信模式。在 browser 与 server 建立起 SSE 连接后,server 会持续将 events 推送到 browser,并且是广播给所有与之建立了 SSE 连接的 browsers。

服务提供SSE接口反馈异步任务状态给浏览器是一个典型且简单的架构。但在微服务盛行的当下,服务间基于 HTTP API 相互依赖。如果某些服务是提供了 SSE 接口(HTTP Stream API) 推送异步任务的即时状态,则服务架构类似: alt multi-business-mode

业务服务的每个实例(SSE client)启动的时候,需要遍历所有依赖的 SSE Server,并与他们一一建立 SSE 连接。这样当 SSE Server 中有状态更新,都会立刻通知到所有关心最新状态的业务实例。

动态跟踪 SSE Server

虽然 SSE Server 提供的特性能很好的将异步状态反馈给所有的订阅 clients,但考虑到 SSE Server 也可能是动态增删 ,业务系统的多个实例需要感知到这一变化。可以引入 Redis 来协同多个实例间的状态: alt multi-with-redis

当有新的 SSE Server 通过业务的某一个实例加入系统的时候,该实例会给 Redis 发送一条“Server Add”的消息,订阅了该事件的所有业务实例在收到 Redis 的推送消息后需要去与新的 SSE Server 建立 SSE 连接。这里其实是借助了 Redis 的 Pub-Sub 功能完成多个业务实例间的事件同步。

获取异步状态的其他技术

WebSocket

WebSocket 是现在比较流行的 Web 通信协议,而且可以基于 HTTP 协议“Upgrade”。相比之前讨论的几种技术,最大的不同是 WebSocket 是一个双向实时通信协议。client 和 server 间可以灵活的制定“通信协议”。相比 SSE,WebSocket 更适合交互强的场景,但也有额外的复杂性,例如需要处理连接的建立、断线重连等。

Consul etc.

现在有很多的软件和服务提供 Pub-Sub 功能,耳熟能详的有 Consul、Eureka、Redis、Zookeeper、SNS、Service Bus。这些都可以用来做服务间的协同,肯定也是可以作异步任务的状态同步的。

  • Zookeeper, Redis:提供的是非 HTTP 的服务接口,所以在微服务中不被建议用来做服务间通信。
  • Consul, Eureka: 提供了 HTTP 接口,是微服务的首选。例如 Eureka 是 Spring Cloud 默认的服务发现组件。
  • SNS, Service Bus:是云服务商提供的 SaaS 服务,可以作为 Redis 等的 Pub-Sub 替代方案。在微服务中可以考虑使用。

在一个服务的 API 设计时,需要考虑给外部暴露易用的、一致的接口。异步状态的接口优先使用 HTTP Stream API,例如 Mesosphere Marathon 提供了基于 SSE 的事件流接口 、Docker Cloud 提供了基于 WebSocket 的事件流接口。而在服务内部,可以使用 Consul etc. 来实现多实例间的状态事件同步。Stream API 的 server 端实现可以订阅 SNS 的 Topic,或者订阅 Consul 的 Path。

Reference

jferic

Read more posts by this author.

中国浙江省杭州市

Subscribe to The Terminus Blog

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!