设计一个可扩展的安全的长连接订阅/推送服务

April 02, 2017

提起设计一个基于 HTTP/WebSocket 的长连接订阅/推送服务,大家马上能想到市面的不少开源产品。像 SocketIO、Faye 等产品已经提供了相当成熟的实现。但是,这类服务出于其通用性和方便上手考虑,往往只包含推送服务的基本模型。当我们将其应用于具体业务时,会发现还是有以下问题需要解决:

  • 如何保证连接的业务安全(禁止非业务认证的连接订阅消息)
  • 如何扩展能够支持更多的消息和连接

订阅/推送模型

首先,我们先来铺垫背景,一个简单的订阅推送模型应该是什么样的呢?

alt

客户端订阅指定的主题(Topic),生产者将消息推送到该主题时,所有订阅主题的客户端都将收到该消息。很简单对吧?对,基于这个简单的模型,我们开始着手解决问题。

建立安全的业务连接

我们总是希望,当连接者对主题有查看权限时,才能订阅主题,否则应该拒绝其连接。如何实现呢,我们先介绍第一种实现方式。

临时的通道

我们引入一个新的概念:通道(Channel)。通道是一种临时的订阅凭证,只有内部服务才允许创建,并且会在一段时间后失效。为了让订阅更有效率,同一个通道将可以绑定多个主题。

假设,我们拥有有业务的鉴权服务器。鉴权服务器负责验证访问者对主题的权限,并且向订阅/推送服务请求创建通道。整个模型是下图这样的:

alt

这里有一点需要注意,通道是有被伪造的可能性。通道标识的随机和通道有限的使用时间,都可以极大地减少了通道被伪造的风险。

搭建网关过滤

除了使用临时通道,我们还可以通过引入网关(Gate)来创建安全连接。网关不同于通道,可以在连接建立之前(逻辑上的连接而非指 TCP 连接),对连接进行处理。比如,在订阅主题之前,验证是否有权限。

在上述的场景下,我们可以在网关拦截订阅请求,转发到鉴权服务器,得到确认答复后才订阅主题。网关模型如下图:

alt

模型对比

两种设计模型都能实现业务的安全连接,作用于不同流程,各有利弊:

  • 通道模型相对复杂,需要推送服务维护更多的对象,处理更加复杂的应用场景,而网关模型就相对简单得多。
  • 接入多个业务时,网关模型需要维护不同业务的不同鉴权方式,增加了推送服务与其他业务的耦合度,而通道模型总是能保持自身服务的独立性。

实现服务的扩展

我们来思考第二个问题。当网站的用户越来越多时,当需要订阅的主题越来越大量时,对稳定性有着更高的要求时,单一节点就已经无法提供有效的服务。这时,我们就需要分布式的推送系统,应该如何设计呢?

我们将订阅与推送看成两个不同的模块。订阅端有天然的可分布性,我们可以很容易地将连接分发到集群的不同的节点(通过负载均衡服务就可以实现)。这样一来,推送端就需要关心:本次推送的主题都有哪些节点订阅了。所以,我们需要引入一个负责调度的网关。

当客户端订阅主题时,需要通知网关,哪个节点订阅了哪个主题。推送时,消息会经过网关,由网关负责分发到对应节点。整体的设计模型如下:

alt

故障转移

分布式系统不仅需要高扩展性,同时还需要保证服务的高可用。当某个节点发生故障时,我们需要保证提供的推送服务不被中断。如何设计呢?我们还是区分订阅端和推送端来设计。

当节点故障时,订阅端的连接会断开,这时只需要客户端支持断开重连机制。一般的负载均衡服务都会提供心跳检查,自动摘除后端不可用的节点,重连的订阅会被分配到新的可用节点上。

这时,网关会收到有新的节点订阅了主题,而故障的节点发生了什么,网关并不知情(进程异常退出或者服务器网络中断通常无法发出通知)。当有消息需要推送到故障节点时,网关会发觉节点出现故障。这时,需要标记该节点状态为异常:

alt

故障恢复后,节点会重新加入到负载均衡器的后端。当有新的订阅到来时,节点会通知网关,这时,网关可以标记该节点状态为正常:

alt

结语

至此,一个分布式的订阅/推送系统算是初具雏形了。然而系统设计并非一蹴而就,在不断迭代完善系统的过程中还会面临各种各样的问题。其中关于技术选型,客户端连接分发等等细节,还有不少值得研究,我会在将来单开话题探讨分享。

留下评论