海量推送系统
- 一、技术要素分析
- 1、WebSocket
- 2、长连接服务器
- 3、分布式推送服务器
- 4、路由算法
- 二、系统架构设计
- 1、整体架构
- 2、建立连接
- 3、消息推送接口API
- 4、消息推送细节
- 5、平滑升级
- 6、平滑扩容
内容总结自《亿级流量系统架构设计与实战》
一、技术要素分析
1、WebSocket
在HTML5下定义了一种全新的通信协议:WebSokcet,它是一种基于TCP的全双工通信协议,允许在客户端与服务端之间建立全双工通信连接,这样客户端和服务端都可以主动将数据推送到另一端
由于WebSocket协议很适合消息推送的场景,所以我们可以在客户端开发一个基于WebSocket协议的长连接通信SDK,客户端通过调用这个SDK来串门用于接收服务器推送消息的通道
2、长连接服务器
服务器搭建架构:
- accept+fork多进程模型
- accept+thread多线程模型,早期Tomcat就是这种设计
- 单线程多路复用模型,也称Reactor模型,如6.0之前的Redis
- 多进行多路复用模型,如Nginx
- 多线程多路复用模型,如Memcached
由于推送服务属于典型的IO密集型,所以多路复用模型比较合适。由于推送服务器的逻辑较为轻量级,这些服务器模型均可较为优秀的支持数十万个长连接并发访问。我们暂且称这台负责与客户端建立连接以及推送消息的单体长连接服务器为pusher。
3、分布式推送服务器
由于单机pusher能力有限,在用户量级超过单机极限后,需要考虑给pusher搭建集群。多个pusher节点,使用常规的注册中心进行管理
4、路由算法
推送系统有两处会利用到路由算法(设备和pusher的对应关系):
- 用户设备发起连接请求,pusher集群需要通过路由算法,选择一个合适的pusher节点与用户设备建立连接
- 当向某个用户设备进行消息推送时,pusher集群需要通过路由算法,找到这个设备与哪个pusher节点建立连接
常见路由算法:
- Random(随机调度算法):借助三方介质存储对应关系
- Round-Robin(轮询调度算法):借助三方介质存储对应关系
- 一致性hash算法:可以直接使用hash算法进行计算
二、系统架构设计
1、整体架构
2、建立连接
3、消息推送接口API
请求
- 消息类型
- 目标设备id集合
- 消息唯一id
- 消息场景
- 消息内容
- 消息编码格式
响应
- 是否成功
- 错误描述
4、消息推送细节
单点消息
消息发送给pusher-gateway,根据设备id,计算出pusher节点,消息转发到具体的pusher上。pusher节点在本地内存中查询长连接文件描述符fd。pusher将消息发送到fd上,下行消息推送完成
全局消息
全局消息消息涉及所有用户推送,直接遍历所有用户流量带宽瞬间增大,出现“写放大”问题。可以借助分批发送的思路,将消息分批发送,这样原本的带宽压力可以分摊到多次请求中
多点消息
如果是hash路由的,则直接hash计算即可。
如果是随机或轮询的方式,如果多点消息用户数很少,可以直接通过第三方介质进行响应的逻辑计算即可。
如果用户数很多,这部分计算压力也会很大。此时可以巧妙借助全局消息的特点,所有的pusher都会收到一个消息,但是在发送消息前,如果当前pusher含有目标设备的连接则发送,没有就丢低消息
5、平滑升级
pusher本身也是一个服务,也会涉及迭代和bugfix,这就避免不了让原pusher进程退出和启动新的pusher进程。但是由于pusher服务器是一台长连接服务器,pusher进程退出会导致所有长连接被关闭。
在对pusher节点进行服务更新时,所有连接会断开,服务启动后,大量请求又感知到长连接被重置,于是几乎同一时间开始重新建立连接,导致推送系统的QPS激增
业界成熟的解决方案:
- 信号技术:使用自定义信号加SIGUSR2作为进程升级事件,而非直接杀死进程
- fork+exec技术:父进程调用fork()创建子进程,子进程调用exec()载入最新程序二进制文件,原父进程的文件打开信息会被自动继承下来
- UnixSocket协议:支持在进程间传输文件描述符
6、平滑扩容
之前的随机和轮询算法并不能很好的让扩容出来的pusher进入工作
我们可以使用带权重的Round-Robin算法,对每个pusher节点都引入权重,权重高的pusher优先建立连接
如果使用hash算法,在扩容时会出现大量连接重建的问题,此时需要控制重建的频率,避免大量请求重建。可以以新增的pusher为起点,逐步断开连接并重建,拉长重新连接的时间线,减少瞬时请求量