本文原题为“大规模群消息推送如何保证实时性?”,来自瓜子二手车IM负责人:封宇,本次内容有修订,感谢原作者(原文链接在文末)。
1、编者注
众所周之,群聊是移动端IM的服务端技术难点所在,难在哪?大量的群聊消息,是一条条推给群内成员还是可以使用什么样的优化策略?试想一个2000人大群,一条消息的发出,如果瞬间被扩散写成2000条一对一消息的投递,对于接收方而言不过是一条消息而已,而服务端是以对相对比单聊消息的2000倍处理压力后的结果。那么服务端在保证消息投递的同时,面对这么大的压力该如何解决好效率问题?解决不好效率问题那实时性就不能保证!
当然,实际在生产环境下,群消息的发送都会想尽办法进行压缩,并开展各种改善性能的处理办法,而不是像上述举例里的直接扩散写(即2000人群里,一条消息被简单地复制为2000条一对一的消息投递)。具体有哪些优先策略?本文或许可以带给你一些启发。
(本文同步发布于:http://www.52im.net/thread-1221-1-1.html )
2、相关资料
3、本文背景
公司IM的第一版红包功能上线后,收集到不少问题。核心问题是消息延迟,导致群里有些人先看到红包,有些人晚看到红包,同时导致消息顺序混乱。这是个典型的群聊消息优化问题。
4、问题产生的原因
先大致分析一下问题产生的原因。
1)消息量瞬间大增:
抢红包时大家都比较活跃,不停在群里发消息,尤其群成员比较多的群(500人),每条消息都会给服务端带来大量的计算工作。
2)后台逻辑不够优化:
比如红包消息没有单独的通道,时效性会收到其他消息影响、没有采用批处理方式、异步处理有些环节还不到位等等。
5、优化前的系统架构和消息处理流程
先看一下系统架构和消息处理流程(如下图) :
(本文作者在另一篇文章《一套海量在线用户的移动端IM架构设计实践分享(含详细图文)》,对这个架构作了详细记录和总结,有兴趣的同行可以前往阅读。)
6、精确定位问题的原因
回顾我们的架构设计(见上节中的大图),我们尝试精确定位问题的根本原因,原因分析如下。
1)c2g模块没有采取批处理方式:
1条群(500人群)消息到达c2g模块后,c2g模块为每个人写收件箱(这里时间延迟较大,优化点),然后在把这条消息变成500条投递消息(需要批处理,就给Kafka放入一条消息),通过Kafka送给Deliver节点投递。
2)Deliver模块的处理没有批量合并:
Deliver模块会到Redis中逐条(500条)检索接收消息用户的在线状态(这个点需要批处理,根据用户Id分布,一次检索若干用户的在线状态),在线的投递消息(批处理),离线的发送第三方push(批处理)。
3)离线推送流程不优化:
整体流程上,每条消息是先写了离线收件箱,再推送。这样效率也不高,需要对这个流程细化以及异步化。
我们来看看微信在这个逻辑上的一些优化思想:
在微信团队分享的《微信后台团队:微信后台异步消息队列的优化升级实践分享》一文中,提到:
上图是群消息投递业务的简化流程示意。随着微信群消息体量的高速膨胀,其带来的成本压力越来越大,业务同学提出了批量并行化的优化方式。简单来说,就是将每个步骤中产生的 RPC 访问按实际访问机器聚合成一系列的批量操作,然后并行化执行。 通常来说,单次的批量并行化并不难写,一般而言,业务同学可能会选择裸写。但如果涉及多次的批量并行化,其中还存在嵌套的话,事情就不那么简单了。最终代码将变得异常复杂,业务开发的同学苦不堪言。MQ 能否从框架上解决这类问题?
(具体内容详见文章《微信后台团队:微信后台异步消息队列的优化升级实践分享》)
总结一下就是:
微信在这块的一个重要优化思想是批处理,做法是单次批量操作(我们本次优化目标)裸写,多条消息的聚合(MapReduce过程)下沉到了MQ中间件中。
7、我们具体怎么做
7.1 群聊红包逻辑单独部署
现阶段,当消息(尤其是大群消息)量大的时候,Deliver节点会成为瓶颈。红包对时效性要求很高,架构上采用独立为红包部署Deliver节点的方式确保红包消息走单独通道进行推送。即使其他消息出现延迟,红包消息依然能保证即使送达。
优化后的架构简述为下图所示:
7.2 裸写批处理逻辑
处理一条群消息,服务端要进行大量的工作,需要查询所有群成员的路由表、在线状态,在线人员需要推送及时消息,离线人员需要推送第三方push(比如iOS的apns推送通道)。这些工作逐条执行,性能会非常差,如果遇到大群,系统会不可用。
批处理可以较好解决这个问题。比如用户状态及路由表数据,采用hash算法分布在几台服务器上。收到群消息后,根据群成员,计算出用户状态及路由表数据的分布情况,从缓存服务器中一次检索出该服务器可能存在的所有群成员状态及路由信息。这样可以极大减少RPC调用次数,及计算量。
推送操作也类似,批量向接入层投递消息即可。
7.3 离线消息异步写收件箱
在处理大群消息推送时,写离线消息也是一个非常影响性能的地方。现有的逻辑是先为每个人写一条离线消息,再执行推送。这样做的初衷是确保消息投递绝对可靠(参看《一个海量在线用户即时通讯系统(IM)的完整设计》的离线消息章节)。由于大群人数较多,写离线消息也有较多时间开销。
优化思路是现将消息及时推送给用户,再异步写离线消息,同时处理好写离线消息和推送消息的ack时序。
具体步骤如下图:
对上图的解读如下:
1)Deliver节点收到一条群消息,检索用户在线状态及路由信息,用户在线(离线的逻辑相对简单,略过);
2)批量推送消息(2、批处理逻辑);
3)异步将消息写入消息总线,同时写入第三方push的延迟推送任务;
4)异步写离线消息(不影响在线用户收到消息的速度);
5)第(2)步推送消息的ack信息回到服务端;
6)c2g模块将ack信息放入消息总线。(确保消息时序性,ack需要在写离线消息之后处理,否则可能出现消息重复);
7)删除对应的离线消息;
8)第(3)步写入的延迟推送任务,在规定时间(如10秒)后生效,判断是否存在此条离线消息(如果ack回来了,离线消息会被删掉),如果离线消息还存在,发送第三方push。
通过以上3个方面的优化,能够确保在并发消息量较大时,推送消息依然及时。
附录:更多即时通讯技术资料
[1] 有关IM/推送的通信格式、协议的选择:
《强列建议将Protobuf作为你的即时通讯应用数据传输格式》
《全方位评测:Protobuf性能到底有没有比JSON快5倍?》
《详解如何在NodeJS中使用Google的Protobuf》
[2] 有关IM/推送的心跳保活处理:
《应用保活终极总结(一):Android6.0以下的双进程守护保活实践》
《应用保活终极总结(二):Android6.0及以上的保活实践(进程防杀篇)》
《应用保活终极总结(三):Android6.0及以上的保活实践(被杀复活篇)》
《Android端消息推送总结:实现原理、心跳保活、遇到的问题等》
《微信团队原创分享:Android版微信后台保活实战分享(进程保活篇)》
《微信团队原创分享:Android版微信后台保活实战分享(网络保活篇)》
《移动端IM实践:WhatsApp、Line、微信的心跳策略分析》
[3] 有关WEB端即时通讯开发:
《Web端即时通讯技术盘点:短轮询、Comet、Websocket、SSE》
《Comet技术详解:基于HTTP长连接的Web端实时通信技术》
《WebSocket详解(一):初步认识WebSocket技术》
《WebSocket详解(二):技术原理、代码演示和应用案例》
《WebSocket详解(三):深入WebSocket通信协议细节》
《LinkedIn的Web端即时通讯实践:实现单机几十万条长连接》
《Web端即时通讯技术的发展与WebSocket、Socket.io的技术实践》
《Web端即时通讯安全:跨站点WebSocket劫持漏洞详解(含示例代码)》
《开源框架Pomelo实践:搭建Web端高性能分布式IM聊天服务器》
《详解Web端通信方式的演进:从Ajax、JSONP 到 SSE、Websocket》
[4] 有关IM架构设计:
《一套海量在线用户的移动端IM架构设计实践分享(含详细图文)》
[5] 有关IM安全的文章:
《即时通讯安全篇(一):正确地理解和使用Android端加密算法》
《即时通讯安全篇(四):实例分析Android中密钥硬编码的风险》
《即时通讯安全篇(五):对称加密技术在Android平台上的应用实践》
《传输层安全协议SSL/TLS的Java平台实现简介和Demo演示》
《理论联系实际:一套典型的IM通信协议设计详解(含安全层设计)》
《微信新一代通信安全解决方案:基于TLS1.3的MMTLS详解》
《来自阿里OpenIM:打造安全可靠即时通讯服务的技术实践分享》
《Web端即时通讯安全:跨站点WebSocket劫持漏洞详解(含示例代码)》
[6] IM开发综合文章:
《IM消息送达保证机制实现(一):保证在线实时消息的可靠投递》
《开源IM工程“蘑菇街TeamTalk”的现状:一场有始无终的开源秀》
《QQ音乐团队分享:Android中的图片压缩技术详解(上篇)》
《QQ音乐团队分享:Android中的图片压缩技术详解(下篇)》
《腾讯原创分享(一):如何大幅提升移动网络下手机QQ的图片传输速度和成功率》
《腾讯原创分享(二):如何大幅压缩移动网络下APP的流量消耗(上篇)》
《腾讯原创分享(二):如何大幅压缩移动网络下APP的流量消耗(下篇)》
《如约而至:微信自用的移动端IM网络层跨平台组件库Mars已正式开源》
《基于社交网络的Yelp是如何实现海量用户图片的无损压缩的?》
[7] 更多即时通讯技术好文分类:
(本文同步发布于:http://www.52im.net/thread-1221-1-1.html )