本文由vivo互联网服务器团队李青鑫分享,有较多修订和改动。
1、引言
本文内容来自vivo互联网服务器团队李青鑫在“2021 vivo开发者大会”现场的演讲内容整理而成(现场演讲稿可从本文末附件中下载)。
本文将要分享的是手机厂商vivo的系统级推送平台在架构设计上的技术实践和总结。这也是目前为止首次由手机厂商分享的自建系统级推送平台的技术细节,我们也得以借此机会一窥厂商ROOM级推送通道的技术水准。
学习交流:
- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM》
- 开源IM框架源码:https://github.com/JackJiang2011/MobileIMSDK(备用地址点此)
(本文已同步发布于:http://www.52im.net/thread-4008-1-1.html)
2、关于作者
李青鑫,vivo互联网服务器团队架构师。
3、为什么需要消息推送
消息推送对于移动端APP来说,是很常见的业务特征,比如新闻APP中的最新资讯、社交应用中的系统通知、IM即时通讯应用的离线聊天消息等等。
可以说,没有消息推送能力,APP就失去了实时触达的能力,对于一个应用来说,它对用户的“粘性”将大大下降。而对于用户来说,信息实时获取的能力也将大大降低,用户体验也将大幅下降。
4、消息推送的技术障碍
以我们日常最常见的IM应用来说,离线消息的推送是必备能力。
但随着Android系统的不断升级,离线推送已经不单单是一个后台服务加长连接那么理所当然了。
对于早期的Android系统来说,想要实现IM的离线消息推送并不困难,搞个后台服务再加上socket长连接就算是齐活了。
但随着Android系统的升级,针对后台进程和网络服务限制不断加码,为了继续实现离线消息的推送,开发者们不得不跟系统斗志斗勇,搞出了各种保活黑科技,比如:Android4.0之后的双进程守护、Android6.0及之后的防杀复活术、以及发展到后来的腾讯TIM进程永生技术,一时间群魔乱舞、无比风骚(有兴趣的同学,可以读读《Android进程永生技术终极揭秘:进程被杀底层原理、APP应对被杀技巧》这篇针对所有保活黑科技的总结性文章)。
随着Andriod 9.0的到来,基本从系统上堵死了各种保活黑科技的活路(详见《Android P正式版即将到来:后台应用保活、消息推送的真正噩梦》),各Android厂商的ROOM系统级推送通道也应运而生——华为推送、小米推送、魅族推送、OPPO推送、vivo推送,一时间从用户的噩梦(保活黑科技对用户困扰很大)变成了开发者的恶梦并持续至今(想要做好IM离线推送,如今的IM开发者们不得不一家家对接各手机厂商的离线推送,你说烦不烦)。
也别跟我说为什么不用Android官方的FCM服务(在国内这链接你能打开算我输,至于为什么,你懂的。。。),也别我跟提那个统一推送联盟(4、5年过去了,看样子还要继续等下去)。
于是,为了继续搞定离线消息推送,IM的开发者们目前只有两条路可选:
- 1)举白旗向系统投降,放弃保活黑科技,直接引导用户手动加白名单(详见《Android保活从入门到放弃:乖乖引导用户加白名单吧》);
- 2)一家一家对接各厂商的系统级推送通道(华为、小米、魅族、OPPO、vivo,悲剧的是,有些小众厂商并没自建推送的能力)。
随着Android系统对于开发者保活黑科技的“堵”,手机厂商们搞出自家的系统级推送通道来“疏”,也算是理所当然。而这些厂商之中,vivo的系统级推送通道出现的算是比较晚的。
本篇文章的余下技术内容,算是目前为止首次由手机厂商分享的自建系统级推送平台的技术细节,我们一起来学习。
5、从技术角度了解推送平台
推送平台是做什么的?
从技术的角度上来看,推送平台就是一个通过TCP长连接,将消息发送给用户的平台。所以推送平台的本质其实就是借助网络通道,将消息发送到用户设备上。
大家日常都收到过快递通知吧!当快递员将快递放到快递柜中,快递后台就会自动推送一条消息,通知你有快递。我相信,如果你是一位运营人员,你也会喜欢这种自动下发消息高效的方式。
大家感兴趣的,可以通过vivo开放平台入口,选择消息推送来更进一步了解更多技术细节,这里就不做展开了。
6、短连接与长连接
消息推送平台的本质,就是通过长连接将内容、服务、用户连在一起,将内容分发给用户,为终端设备提供实时、双向通信能力。
这里有个概念长连接,那么什么是长连接?
所谓的长连接就是:客户端与服务端维持的一条、在相对较长的时间里、都能够进行网络通信的网络连接(比如:基于TCP的长连接)。
为什么我们要采用长连接而不是短连接作为平台底层的网络通信?
先来看看短连接下消息下发的场景:使用短连接的方式就是轮询,即客户端定时的去询问后台有没有设备A的消息,当有设备A的消息时后台返回对应的消息,可能很多情况下都是无功而返,浪费流量。当后台有消息需要发送给设备A时,因为设备A没有过来取导致消息无法下发。
而使用长连接:当有设备A的消息时后台直接发送给设备A而不用等设备A自己过拉取,所以长连接让数据交互更加自然、高效。
7、业务需求驱动架构升级
对于系统的技术架构来说,它是动态的,不同阶段都可能会发生变化。而推动架构进行演进的推力,主要来自于业务需求,一起来回顾,平台的业务发展历程。
自2015年立项以来,随着业务量增长,不断为系统添加功能特性,丰富整个系统的能力使其满足不同的业务场景需求。比如支持内容完全审核、支持IM、支持IoT、支持WebSocket 通信等。
从图上可以看到,业务量几乎每年都有几十亿的增长,不断攀高,给系统带来了挑战,原有的系统架构存在的问题,也逐渐浮出水面,比如延迟、性能瓶颈。
架构服务于业务,2018年之前我们平台所有服务都放在云上,但是依赖的其他内部业务部署在自建机房。
随着业务量增长与自建机房的数据传输,已经出现了延迟的问题,并且在逐渐恶化,不利于我们平台功能的拓展。
所以在2018年下半年,我们对部署架构进行调整:将所有核心逻辑模块都迁移到自建机房,架构优化之后,数据延迟问题得到彻底解决,同时也为架构进一步演进奠定了基础。从上面的图中可以看到我们接入网关也进行优化三地部署。
为什么要进行三地部署而不是更多区域部署呢?
主要基于以下三点考虑:
- 1)第一是基于用户分布及成本的考虑;
- 2)第二是能为用户提供就近接入;
- 3)第三是能够让接入网关具备一定容灾能力。
大家可以设想下,如果没有三地部署,接入网关机房故障时,那么平台就瘫痪了。
随着平台业务规模的进一步扩大,日吞吐量达到10亿的量级,用户对于时效性、并发要求越来越高。而2018年的逻辑服务的系统架构已经无法业务高并发的需求或者需要更高的服务器成本才能满足高并发需求。
所以从平台功能、成本优化出发,在2019年对系统进行了重构,为用户提供更加丰富的产品功能及更稳定、更高性能的平台。
8、利用长连接能力给更多业务赋能
作为公司较大规模的长连接服务平台,团队积累了非常丰富的长连接经验。我们也一直在思考,如何让长连接能力为更多业务赋能。
我们平台服务端各个模块之间通过RPC调用,这是一种非常高效的开发模式,不用每个开发人员都去关心底层网络层数据包的。
我们设想一下,如果客户端也能通过RPC调用后台,这一定是非常棒的开发体验。
未来我们将会提供VRPC通信框架,用于解决客户端与后台通信及开发效率问题,为客户端与后台提供一致的开发体验,让更多的开发人员不再关心网络通信问题,专心开发业务逻辑。
作为一个吞吐量超过百亿的推送平台其稳定性、高性能、安全都非常重要,接下来和大家分享,我们在系统稳定性、高性能、安全方面的实践经验。
9、vivo推送平台的领域模型
从上图的领域模型可以看出,推送平台以通信服务作为核心能力,在核心能力的基础上我们又提供了,大数据服务以及运营系统,通过不同接口对外提供不同的功能、服务。
以通信服务为核心的推送平台,其稳定性和性能都会影响消息的时效性。
消息的时效性是指,消息从业务方发起用设备收到的耗时。
那么如何衡量消息的时效性呢?我们继续往下看。
10、如何实现消息时效性的监控与质量度量?
传统的消息时效性测量方法如上图左所示:发送端和接收端在两个设备上,在发送的时候取时间t1、在接收到消息的时候取时间t2,这两个时间相减得到消息的耗时。
但是这种方法并不严谨,为什么呢?因为这两个设备的时间基准,很有可能是不一致的。
我们采用的解决方案如上图右图所示:将发送端和接收端放在同一个设备上,这样就可以解决时间基准的问题。我们就是基于该方案,搭建了一套拨测系统,来主动监控消息送达耗时分布。
11、如何实现高性能、稳定的长连接网关?
过去10年讨论单机长连接性能时面对的是单机一万连接的问题(C10K问题),而作为一个上亿级设备同时在线的平台,我们要面对的是单机100万连接的问题。
作为长连接网关,主要职责是维护与设备端的TCP连接及数据包转发。
对于长连接网关:我们应该尽可能使其轻量化。
我们从以下几方面进行了自上而下的重构优化:
- 1)架构设计;
- 2)编码;
- 3)操作系统配置;
- 4)硬件特性配置。
具体的实施方法,比如:
- 1)调整系统最大文件句柄数、单个进程最大的文件句柄数;
- 2)调整系统网卡软中断负载均衡或者开启网卡多队列、RPS/RFS;
- 3)调整TCP相关参数比如keepalive(需要根据宿主机的session时间进行调整)、关闭timewait recycles;
- 4)硬件上使用AES-NI指令加速数据的加解密。
经过我们优化之后,线上8C32GB 的服务器可以稳定支持170万的长连接。
另外一大难点在于连接保活:一条端到端的 TCP连接,中间经过层层路由器、网关,而每个硬件的资源都是有限的,不可能将所有TCP连接状态都长期保存。
所以为了避免TCP资源,被中间路由器回收导致连接断开,我们需要定时发送心跳请求,来保持连接的活跃状态(为什么TCP有这样的问题?有兴趣可以读这两篇:《为什么说基于TCP的移动端IM仍然需要心跳保活?》、《彻底搞懂TCP协议层的KeepAlive保活机制》)。
心跳的发送频率多高才合适?发送太快了会引起功耗、流量问题,太慢了又起不到效果,所以为了减少不必要的心跳及提升连接稳定性,我们采用智能心跳,为不同网络环境采用差异性的频率。
有关长连接心跳机制的更详细资料,可以参阅:
- 《手把手教你用Netty实现网络通信程序的心跳机制、断线重连机制》
- 《一文读懂即时通讯应用中的网络心跳包机制:作用、原理、实现思路等》
- 《移动端IM实践:实现Android版微信的智能心跳机制》
- 《移动端IM实践:WhatsApp、Line、微信的心跳策略分析》
- 《一种Android端IM智能心跳算法的设计与实现探讨(含样例代码)》
- 《正确理解IM长连接、心跳及重连机制,并动手实现》
- 《万字长文:手把手教你实现一套高效的IM长连接自适应心跳保活机制》
- 《Web端即时通讯实践干货:如何让你的WebSocket断网重连更快速?》
12、如何实现亿级设备的负载均衡?
我们平台超过亿级设备同时在线,各个设备连接长连接网关时是通过流量调度系统进行负载均衡的。
当客户端请求获取IP时,流量调度系统会下发多个就近接入网关IP:
那么调度系统是如何确保下发的ip是可用的呢?大家可以简单思考下。
对于我来来说,我们采用四种策略:
- 1)就近接入 ;
- 2)公网探测 ;
- 3)机器负载;
- 4)接口成功率。
到底采用这几种策略呢?大家可以想下,这两个问题:
- 1)内网正常,公网就一定能联通吗?
- 2)连接数少服务器,就一定是可用的吗?
答案是否定的,因为长连接网关与流量调度系统是通过内网进行心跳保活的,所以在流量调度系统上看到的长连接网关是正常的,但是很有可能长连接网关公网连接是异常的比如没有开通公网权限等。
所以我们需要结合多种策略,来评估节点的可用性,保障系统的负载均衡、为系统稳定性提供保障。
13、如何满足高并发需求?
有这么一个场景:以每秒1000的推送速度,将一条新闻发送给几亿用户,那么有的用户可能是几天后才收到这条消息,这就非常影响用户体验,所以高并发对消息的时效性来说是非常重要的。
从上图的推送流程来看:会不会觉得TiDB会成为推送的性能瓶颈?
其实不会:初步看可能会觉得它们作为中心存储,但因为我们采用分布式缓存,将中心存储的数据,根据一定的策略缓存到各个业务节点,充分利用服务器资源,提升系统性能、吞吐量。我们线上的分布式缓存命中率99.9% 为中心存储挡住了绝大部分请求,即使TiDB短时间故障,对我们影响也比较小。
14、如何保障系统稳定性?
14.1 概述
作为推送平台,平台的流量主要分为外部调用及内部上下游之间的调用。它们大幅波动都会影响系统的稳定性,所以需要进行限流、控速,保障系统稳定运行。
14.2 推送网关限流
推送网关作为流量入口,其稳定性非常重要。
要让推送网关稳定运行,我们首先要解决流量均衡的问题即避免流量倾斜的问题。因为流量倾斜之后,很有可能会引起雪崩的情况。
我们是采用轮询的机制,进行流量的负载均衡,来避免流量倾斜问题。
但是这里有两个前提条件:
- 1)所有推送网关节点,服务器配置要保持一致,否则很有可能会因为某个处理能力不足导致过载问题;
- 2)应控制流入我们系统的并发量,避免流量洪峰穿透推送网关导致后端服务过载。
我们采用的是令牌桶算法,控制每个推送网关投放速度,进而能够对下游节点起到保护作用。
那么令牌数量设置多少才合适呢?设置低了,下游节点资源不能充分利用;设置太高了,下游节点有可能扛不住。
我们可以采用主动+被动的动态调整的策略:
- 1)当流量超过下游集群处理能力时,通知上游进行限速;
- 2)当调用下游接口超时,达到一定比例是进行限流。
14.3 系统内部限速:标签推送平滑下发
既然推送网关已经限流了,为什么内部节点之间还要限速?
这个是由于我们平台的业务特点决定的,平台支持全量、标签推送,要避免性能较好的模块,把下游节点资源耗尽的情况。
标签推送模块(提供全量、标签推送)就是一个性能较高的服务,为了避免它对下游造成影响。我们基于Redis和令牌桶算法实现了平滑推送的功能,控制每个标签任务的推送速度,来保护下游节点。
另外:平台支持应用创建多个标签推送,它们的推送速度会叠加,所以仅控制单个标签任务的平滑推送是不够的。需要在推送下发模块对应用粒度进行限速,避免推送过快对业务后台造成压力。
14.4 系统内部限速:消息下发时限速发送
为了实现应用级别的限速,我们采用Redis实现分布式漏桶限流的方案,具体方案如上图所示。
这里我们为什么采用的是clientId(设备唯一标识),而不是使用应用ID来做一致性hash?主要是为了负载均衡。
自从实现了这个功能之后,业务方再也不用担心推送太快,造成自己服务器压力大的问题。
那么被限速的消息会被丢掉吗?当然不会,我们会将这些消息存储到本地缓存、并且打散存储到Redis,之所以需要打散存储主要是为了避免后续出现存储热点问题。
14.5 熔断降级
推送平台,一些突发事件、热点新闻会给系统带来较大的突发流量。我们应该如何应对突发流量呢?
如上图左所示:传统的架构上为了避免突发流量对系统的冲击,冗余部署大量机器,成本高、资源浪费严重。在面临突发流量时,无法及时扩容,导致推送成功率降低。
我们是怎么做的呢?我们采用增加缓冲通道,使用消息队列和容器的解决方案(这种方案系统改动小)。当无突发流量时以较小量机器部署,当遇到突发流量时我们也不需要人工介入,它会根据系统负载自动扩缩容。
15、基于Karate的自动化测试系统
在日常开发中大家为了快速开发需求,往往忽视了接口的边界测试,这将会给线上服务造成很大的质量风险。
另外,不知道大家有没有注意到,团队中不同角色沟通时使用的不同媒介比如使用word、excel、xmind等,会导致沟通的信息出现不同程度折损。
所以为了改善以上问题,我们开发了一个自动化测试平台,用于提升测试效率与接口用例覆盖率,我们采用领域统一的语言减少团队中不同角色沟通信息折损。另外还可以对测试用例统一集中管理,方便迭代维护。
16、内容安全
作为推送平台,当然要为内容安全把好关,我们提供了内容审计的能力。
审计方法采用自动审核为主、人工审核为辅机制来提升审核效率。同时结合基于影响面及应用分级的策略进行内容审计。
从下图中可以看到业务请求经过接入网关转发给内容审系统进行第一层本地规则的内容审计,如果没有命中本地规则则调用我们谛听系统进行内容反垃圾审计。
17、未来规划
前面我们主要介绍了推送平台这几年的架构演进及演进过程中的系统稳定性、高性能、安全等方面的技术实践,接下来介绍未来的重点工作。
为了提供更易用、更稳定、更安全的推送,未来将在以下方面持续投入建设:
- 1)在单模块数据一致性的基础上,实现全系统数据一致性;
- 2)将继续完善各系统的熔断降级能力;
- 3)平台的易用性方面持续优化,提供更加便捷的平台服务;
- 4)建设异常流量识别的能力。
18、演讲稿附件下载
本文内容对应的演讲原稿附件下载:
vivo推送平台架构演进(52im.net).pdf(1.93 MB )
演讲原稿内容概览:
19、参考资料
[2] Android6.0及以上的保活实践(进程防杀篇)》
[3] 为何基于TCP协议的移动端IM仍然需要心跳保活机制?
[6] Android P正式版即将到来:后台应用保活、消息推送的真正噩梦
[9] 史上最强Android保活思路:深入剖析腾讯TIM的进程永生技术
[10] Android进程永生技术终极揭秘:进程被杀底层原理、APP对抗被杀技巧
[11] Web端即时通讯实践干货:如何让你的WebSocket断网重连更快速?
(本文已同步发布于:http://www.52im.net/thread-4008-1-1.html)