SORCERERXW

Server-Side Events: 简单高效的服务端推送

介绍

通常我们在做后端推送方案选型的时候,如果不使用 WebSocket,我会选用什么方案呢?

先说最基础的 轮询 Polling,client 每隔一段时间请求 server 查询是否有更新数据,但是这样没办法保证数据的实时,而且也需要耗费大量的流量。

那我们进一步,如果没有数据更新,server 就不要响应 client,而是 hold connection,直到数据更新了,再将最新的数据返回给 client,随后 client 再建立新的连接。这就是长轮询 Long Polling 机制。相比简单的轮询,更加实时,网络开销更小,但是也不是非常优雅,需要不断重连。

那么是不是可以让服务端响应数据的时候不要断开 HTTP 连接?那我们只需要将所有响应作为 stream 下发,client 不断处理下发的事件即可。这便形成了 Server-Side Events(下面简称 SSE) 的基本原理。

相比 WebSocket,SSE 完全基于 HTTP,对于服务端来说实现更为简单轻量。

服务端实现

以下我使用 Golang+echo 实现了一个简单的 SSE 服务端:

e:=echo.New()
e.GET("/sub", func(c echo.Context) error {
  c.Response().Header().Set(echo.HeaderContentType, "text/event-stream")
  c.Response().WriteHeader(http.StatusOK)
  
  for {
      select {
      case <-c.Request().Context().Done():
        return nil
      default:
      }
      fmt.Fprint(c.Response(), "data: hi\n\n")
      c.Response().Flush()
      time.Sleep(1 * time.Second)
  }
  return nil
})

前端实现

EventSource 是 W3C 标准的正式成员,浏览器也为其提供了完整的实现,只需要直接创建 EventSource 就够直接订阅一个后端数据。

const evtSrc = new EventSource("http://example.com/eventsource");
evtSrc.onmessage = function (event) {
  console.log(event.data)
}

很多情况下后端服务或者网关会给一个 HTTP 请求设定最长连接时长,一旦超时,会自动断开 client 的连接。这就需要 client 实现自动重连机制,以确保数据的实时。而 EventSource 已经原生实现自动重连机制,一旦连接断开,浏览器不不断重试直到连接恢复。

适用场景

经过上面的介绍,可以看到 SSE 无论对于前端还是后端都及其友好,非常容易实现,可以非常好地满足事件下发的需求。对于前端展示的消息订阅,非常适合使用 SSE 实现。

 

Consuming streaming data

PowerTrack, Volume (e.g. Decahose, Firehose), and Replay streams utilize Streaming HTTP protocol to deliver data through an open, streaming API connection. Rather than delivering data in batches through repeated requests by your client app, as might be expected from a REST API, a single connection is opened between your app and the API, with new results being sent through that connection whenever new matches occur.

icon

这是 Twitter 开发者文档,可以看到 Twitter 的事件推送也是基于 HTTP Stream 实现。