揭秘 vivo 百亿级厂商消息推送平台的高可用技术实践
本文由 vivo 互联网服务器团队 Yu Quan 分享,本文收录时有内容修订和重新排版。
1、引言
如今,Android 端的即时通讯 IM 这类应用想实现离线消息推送,难度越来越大(详见《Android P正式版即将到来:后台应用保活、消息推送的真正噩梦》、《Android保活从入门到放弃:乖乖引导用户加白名单吧》)。
于是,使用手机厂商自建的 ROOM 级消息推送通道进行 IM 离线消息推送是个不得不面对的问题,我们也正好借此文机会,一窥主流手机厂商的 ROOM 级推送通道的技术实现吧。
vivo 手机的厂商级消息推送系统的现状是最高推送速度 140w/s,单日最大消息量 200 亿,端到端秒级在线送达率 99.9%。同时推送系统具备不可提前预知的突发大流量特点。
本文将要分享的是 vivo 技术团队针对消息推送系统的高并发、高时效、突发流量等特点,从长连接层容灾、逻辑层容灾、流量容灾、存储容灾等方面入手,如何保证百亿级厂商消息推送平台的高可用性的。
* 推荐阅读:vivo 技术团队分享的另一篇消息推送技术文章《vivo手机上的系统级消息推送平台的架构设计实践》。
技术交流:
- 移动端 IM 开发入门文章:《新手入门一篇就够:从零开发移动端IM》
- 开源 IM 框架源码:https://github.com/JackJiang2011/MobileIMSDK(备用地址点此)
(本文已同步发布于:http://www.52im.net/thread-4416-1-1.html)
2、推送系统介绍
vivo 推送平台是 vivo 公司向开发者提供的消息推送服务,通过在云端与客户端之间建立一条稳定、可靠的长连接,为开发者提供向客户端应用实时推送消息的服务,支持百亿级的通知/消息推送,秒级触达移动用户。
推送系统主要由 3 部分组成:
1)接入网关;
2)逻辑推送节点;
3)长连接。
其中,长连接负责与用户手机终端建立连接,及时把消息送达到手机终端。
推送系统的特点是:
1)并发高;
2)消息量大;
3)送达及时性较高。
下面将针对这几个方面来分享我们的技术实践。
3、长连接层容灾的技术实现
长连接是推送系统最重要的部分,长连接的稳定性直接决定了推送系统的推送质量和性能。因此,需要对长连接层做好容灾和实时调度能力。
3.1 面临的问题
原有推送系统架构是长连接层都部署在华东,所有 vivo IDC 逻辑节点通过 VPC 与华东的 Broker 建立连接,手机端跟华东的 broker 进行长连接通信。
这种部署方式存在以下问题。
1)问题一:华北、华南手机都需要连接华东的 Broker,地域跨度大,长连接网络稳定性和时效性相对较差。
2)问题二:逻辑层跟华东的 Broker 之间由一条 VPC 连接,随着业务的发展,推送流量越来越大,带宽会出现瓶颈,有超限丢包的风险。另外当该 VPC 出现故障时,会造成全网消息无法送达。
注:长连接层节点名为 Broker。
原始长连接架构图:
3.2 解决方法
基于以上架构存在问题,我们对架构进行了优化。即将 Broker 进行三地部署,分别部署在华北、华东、华南。
华北、华东、华南三地用户采用就近接入方式。
优化后的架构,不仅可以保证长连接网络稳定性和时效性。同时具有较强的容灾能力,华东、华南 Broker 通过云网跟华北 Broker 连接,华北 Broker 通过 VPC 与 vivo IDC 连接。当华北、华东、华南某个地区 Broker 集群故障或者公网故障,不会影响到全网设备收发消息。
三地部署后的架构图:
3.3 进一步优化
但是上述这种方式还是存在一个问题,就是某个地区 Broker 集群故障或者公网故障,会出现该区域部分设备无法收到推送消息的情况。
针对上述单个地区异常导致该区域部分设备无法收到推送消息的问题,我们设计了一套流量调度系统,可以做到实时流量调度和切换。global scheduler 节点负责策略调度和管理。
vivo phone 进行注册时:dispatcher 会下发多个地区的 ip 地址,默认情况下,进行就近连接。单多次连接失败后,尝试连接其他 ip。当某个地区 Broker 出现长连接数瓶颈或者 VPC 出现故障,可以通过 global scheduler 节点下发策略,让该故障地区的设备重新从 dispatcher 获取新的 ip 集的 ip,与其他地区 Broker 建立长连接,逻辑节点下发消息到重连后的 Broker。等到该地区恢复后,可以重新再下发策略,进行回调。
流量调度系统图:
4、逻辑层容灾的技术实现
长连接层做好容灾后,逻辑层也需要做相应容灾。
之前我们逻辑层都部署在一个机房,不具备机房间容灾能力,当一个机房出现断电风险,会出现服务整体不可用问题,因此我们做"同城双活"部署方案改造。
逻辑层单活架构:
逻辑层分别在 vivo IDC1 和 vivo IDC2 进行部署,网关层根据路由规则将流量按照一定比例分别下发到两个 IDC,实现逻辑层同城双活。
我们发现:数据中心还是只有一个,部署在 vivo IDC1,根据成本、收益,以及多数据中心数据同步延迟问题综合考虑,数据中心暂时还是以单数据中心为主。
逻辑层双活架构:
5、流量容灾的技术实现
5.1 概述
做好系统架构的容灾能力后,推送系统的网关层还需要应对突发流量做相应的应对措施,做好流量控制,保证系统稳定性。历史上,我们曾经因为热点和突发新闻事件,并发推送流量巨大,导致服务出现异常,可用性降低问题。
为了应对突发大流量,保证突发流量的情况下,系统可用性不变,同时能兼顾性能和成本。为此,我们分别对比了设计了以下两种方案。
5.2 常规方案
常规的方案是一般是根据历史情况估算冗余部署大量机器,来应对突发流量。
单这种方式成本较高,突发流量可能只持续 5 分钟或更短时间,而系统为了满足 5 分钟突发流量,需要冗余部署大量机器。
一旦流量超过了部署机器可承担的上限,无法及时扩容,可能导致可用性下降,甚至出现雪崩效应。
传统方案下的推送架构:
那如何设计一套既可以控制成本,面对突发大流量弹性扩容,又保证消息不漏并兼顾推送性能的方案呢?
5.3 优化方案
优化后的方案:
1)在原有架构的基础上,在接入层增加缓冲通道,当流量洪峰到来时,对于系统可处理的上限能力外的流量,打入缓冲队列;
2)通过消息队列形式,增加 bypass 接入层,限速消费消息队列;
3)在流量洪峰过去后,提升 bypass 消费速度,处理缓存队列消息;
4)bypass 接入层通过 docker 部署,支持动态扩缩容,默认最小化集群,当消息队列积压很多,并且下游有能力处理时,提升消费速度,bypass 根据 CPU 负载动态扩容,快速消费消息队列;
5)处理完毕后动态缩容。
消息队列:选用吞吐量较大的 KAFKA 中间件,并且与离线计算 KAFKA 集群共用,能充分利用资源。
bypass 接入层:采用 docker 部署,支持根据 CPU 负载和时间动态扩缩容。默认最小集群部署。对于已知的流量高峰时段,可以提前扩容服务,保证流量快速处理。未知时段流量高峰,可以 bypass 接入层,根据 CPU 负载情况进行动态扩缩容。
增加缓存队列后的推送架构:
5.4 进一步优化
进行上述改造后:还存在一个问题,就是如何进行接入层全局控速。
我们采用的方式是:收集下游推送节点的推送流量情况。
比如:流量达到系统可承受上限的 80%时下发限速指令,调整接入层推送速度。让消息先积压在消息队列,等到下游流量降低之后,下发解除限速指令,让 bypass 接入层加速消费消息队列,进行推送。
增加控速后的推送架构:
优化后方案与传统方案对比:
6、存储容灾的技术实现
6.1 问题
做好并发流量控制后,能很好的预发突发热点问题。但在推送系统内部,由于使用 Redis 集群缓存消息,出现过因为 Redis 集群故障导致消息无法及时送达问题。
因此:我们考虑对 Redis 集群做相关容灾方案设计,实现系统在 Redis 集群故障期间,也能及时推送消息并保证消息不丢失。
推送消息体缓存在 Redis 集群中,推送时从 Redis 中获取消息体,如果 Redis 集群宕机,或者内存故障,会导致离线消息体丢失。
6.2 方案
原有消息流程:
1)方案一:
引入另一个对等 Redis 集群,采用推送双写方式,双写两个 Redis 集群。该方案需要冗余部署规模对等的备 Redis 集群。推送系统需要双写 Redis 操作。
2)方案二:
原有 Redis 集群,采用 RDB+AOF 方式同步到另一个备 Redis 集群。
该方案不在需要推送系统双写 Redis 改造,直接利用将原有 Redis 集群数据同步到另一个备 Redis 集群。也需要冗余部署规模对等的备 Redis 集群。可能存在部分数据同步延迟导致推送失败问题。
3)方案三:
应用另一个分布式存储系统,磁盘 KV,兼容 Redis 协议,同时具有持久化能力。可以保证消息体不丢失。但是为了节省成本,不再直接使用 Redis 集群对等资源。
而是根据推送特点,推送分为单推、群推。单推是一对一推送,一个用户一条消息体。群推是一对多推送,一个消息体对应多个用户。
群推往往是任务级别推送。因此我们使用一个相对小一些的磁盘 KV 集群,主要用于冗余存储,群推消息体,即任务级别的消息。对于单推,还是只保存到 Redis 中,不进行冗余存储。
如果 Redis 集群故障,对于单推消息,推送系统可以携带消息体往下游推送,确保消息可以继续下发。对于群推消息,因为消息体冗余存储在磁盘 KV 中,当 Redis 集群故障后,可以降级到读取磁盘 KV。
6.3 优化
方案三还存在一个问题,就是磁盘 KV 的写入性能和 Redis 集群不是一个数量级,特别是时延,磁盘 KV 在平均在 5ms 左右。
而 Redis 集群却在 0.5ms。如果在推送系统对群推消息体进行双写。这个时延是不能接受的。
因此只能采用异步写入磁盘 KV 的方式。
这里将备份群推消息体,先写入消息中间件 KAFKA,由 bypass 节点消费 KAKFA 进行异步写入磁盘 KV。这样在使用的灾备磁盘 KV 资源较少的前提下,保证推送系统的高并发能力,同时可以保证群推消息体不丢失,Redis 异常时,单推消息携带消息体推送,群推消息体读取磁盘 KV。
存储容灾方案对比:
7、本文小结
本文从长连接层容灾、逻辑层容灾、流量容灾、存储容灾等几个方面讲述了推送系统容灾建设过程。系统容灾需要根据业务发展,成本收益,实现难度等多方面考虑。
当前我们长连接层已具备三地部署,逻辑层具备同城双活,数据中心为单数据中心。后续我们会持续研究和规划双数据中心,两地三中心部署架构方式来逐步加强推送系统容灾能力。
8、参考资料
[2] 魅族2500万长连接的实时消息推送架构的技术实践分享
[3] 专访魅族架构师:海量长连接的实时消息推送系统的心得体会
[7] 长连接网关技术专题(四):爱奇艺WebSocket实时推送网关技术实践
[9] 微信直播聊天室单房间1500万在线的消息架构演进之路
[11] 消息推送技术干货:美团实时消息推送服务的技术演进之路
[12] 技术干货:从零开始,教你设计一个百万级的消息推送系统
9、vivo 技术团队分享的其它文章
《IM消息ID技术专题(七):深度解密vivo的自研分布式ID服务(鲁班)》
《直播系统聊天技术(八):vivo直播系统中IM消息模块的架构实践》
《IM跨平台技术学习(三):vivo的Electron技术栈选型、全方位实践总结》
(本文已同步发布于:http://www.52im.net/thread-4416-1-1.html)
评论