1、引言
好久没写技术文章了,今天这篇不是原理性文章,而是为大家分享一下由笔者主导开发实施的IM即时通讯聊天系统,针对大量离线消息(包括消息漫游)导致的用户体验问题的升级改造全过程。
文章中,我将从如下几个方面进行介绍:
- 1)这款IM产品的主要业务及特点;
- 2)IM系统业务现状和痛点;
- 3)升级改造之路;
- 4)消息ACK逻辑的优化。
下述内容都是根据笔者开发IM的亲身经历总结下来的宝贵经验,干货满满,期待你的点赞。
学习交流:
- 即时通讯/推送技术开发交流5群:215477170 [推荐]
- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM》
本文已同步发布于“即时通讯技术圈”公众号,欢迎关注:

▲ 本文公众号链接是:https://mp.weixin.qq.com/s/XHdt1IpCrdmaMDxL8WKn3w,原文链接是:http://www.52im.net/thread-3036-1-1.html
2、IM开发干货系列文章
本文是系列文章中的第25篇,总目录如下:
《IM消息送达保证机制实现(一):保证在线实时消息的可靠投递》
《一种Android端IM智能心跳算法的设计与实现探讨(含样例代码)》
《IM开发基础知识补课(一):正确理解前置HTTP SSO单点登陆接口的原理》
《IM开发基础知识补课(二):如何设计大量图片文件的服务端存储架构?》
《IM开发基础知识补课(三):快速理解服务端数据库读写分离原理及实践建议》
《IM开发基础知识补课(四):正确理解HTTP短连接中的Cookie、Session和Token》
《IM群聊消息究竟是存1份(即扩散读)还是存多份(即扩散写)?》
《IM开发基础知识补课(五):通俗易懂,正确理解并用好MQ消息队列》
《IM开发基础知识补课(六):数据库用NoSQL还是SQL?读这篇就够了!》
《IM里“附近的人”功能实现原理是什么?如何高效率地实现它?》
《IM开发基础知识补课(七):主流移动端账号登录方式的原理及设计思路》
《IM开发基础知识补课(八):史上最通俗,彻底搞懂字符乱码问题的本质》
《IM的扫码登功能如何实现?一文搞懂主流应用的扫码登陆技术原理》
另外,如果您是IM开发初学者,强烈建议首先阅读《新手入门一篇就够:从零开发移动端IM》。
3、此IM产品的主要业务及特点
和传统互联网行业有所不同,笔者所在的公司(名字就不透露了)是一家做娱乐社交app的公司,包括小游戏、聊天、朋友圈feed等。
大家应该都有体会:游戏业务在技术上和产品形态上与电商、旅游等行业有着本质上的区别。
大部分做后端开发的朋友,都在开发接口。客户端或浏览器h5通过HTTP请求到我们后端的Controller接口,后端查数据库等返回JSON给客户端。大家都知道,HTTP协议有短连接、无状态、三次握手四次挥手等特点。而像游戏、实时通信等业务反而很不适合用HTTP协议。
原因如下:
- 1)HTTP达不到实时通信的效果,可以用客户端轮询但是太浪费资源;
- 2)三次握手四次挥手有严重的性能问题;
- 3)无状态。
比如说,两个用户通过App聊天,一方发出去的消息,对方要实时感知到消息的到来。两个人或多个人玩游戏,玩家要实时看到对方的状态,这些场景用HTTP根本不可能实现!因为HTTP只能pull(即“拉”),而聊天、游戏业务需要push(即“推”)。
4、IM系统业务现状和痛点
4.1 业务现状
笔者负责整个公司的实时聊天系统,类似与微信、QQ那样,有私聊、群聊、发消息、语音图片、红包等功能。
下面我详细介绍一下,整个聊天系统是如何运转的。
首先:为了达到实时通信的效果,我们基于Netty开发了一套长链接网关gateway(扩展阅读:《Netty干货分享:京东京麦的生产级TCP网关技术实践总结》),采用的协议是MQTT协议,客户端登录时App通过MQTT协议连接到gateway(NettyServer),然后通过MQTT协议把聊天消息push给NettyServer,NettyServer与NettyClient保持长链接,NettyClient用于处理业务逻辑(如敏感词拦截、数据校验等)处理,最后将消息push给NettyServer,再由NettyServer通过MQTT push给客户端。
其次:客户端与服务端想要正常通信,我们需要制定一套统一的协议。拿聊天举例,我们要和对方聊天,需要通过uid等信息定位到对方的Channel(Netty中的通道,相当于一条socket连接),才能将消息发送给正确的客户端,同时客户端必须通过协议中的数据(uid、groupId等),将消息显示在私聊或者群聊的会话中。
协议中主要字段如下(我们将数据编码成protobuf格式进行传输):
{
"cmd":"chat",
"time":1554964794220,
"uid":"69212694",
"clientInfo":{
"deviceId":"b3b1519c-89ec",
"deviceInfo":"MI 6X"
},
"body":{
"v":1,
"msgId":"5ab2fe83-59ec-44f0-8adc-abf26c1e1029",
"chatType":1,
"ackFlg":1,
"from":"69212694",
"to":"872472068",
"time":1554964793813,
"msg":{
"message":"聊天消息"
}
}
}
补充说明:如果你不了Protobuf格式是什么,请详读《Protobuf通信协议详解:代码演示、详细原理介绍等》。
如上json,协议主要字段包括:

如果客户端不在线,我们服务端需要把发送的消息存储在离线消息表中,等下次对方客户端上线,服务端NettyServer通过长链接把离线消息push给客户端。
4.2 业务痛点
随着业务蓬勃发展,用户的不断增多,用户创建的群、加入的群和好友不断增多和聊天活跃度的上升,某些用户不在线期间,产生大量的离线消息(尤其是针对群聊,离线消息特别多)。
等下次客户端上线时,服务端会给客户端强推全部的离线消息,导致客户端卡死在登录后的首页。并且产品提出的需求,要扩大群成员的人数(由之前的百人群扩展到千人群、万人群等)。
这样一来,某些客户端登录后必定会因为大量离线消息而卡死,用户体验极为不好。
和客户端的同事一起分析了一下原因:
- 1)用户登录,服务端通过循环分批下发所有离线消息,数据量较大;
- 2)客户端登录后进入首页,需要加载的数据不光有离线消息,还有其他初始化数据;
- 3)不同价位的客户端处理数据能力有限,处理聊天消息时,需要把消息存储到本地数据库,并且刷新UI界面,回复给服务端ack消息,整个过程很耗性能。
(庆幸的是,在线消息目前没有性能问题)。
所以针对上述问题,结合产品对IM系统的远大规划,我们服务端决定优化离线消息(稍微吐槽一下,客户端处理能力不够,为什么要服务端做优化?服务端的性能远没达到瓶颈。。。)。
5、升级改造之路
值得庆幸的是,笔者100%参与这次系统优化的全部过程,包括技术选型、方案制定和最后的代码编写。在此期间,笔者思考出多种方案,然后和服务端、客户端同事一起讨论,最后定下来一套稳定的方案。
5.1 方案一(被pass掉的一个方案)
▶ 【问题症状】:
客户端登录卡顿的主要原因是,服务端会强推大量离线消息给客户端,客户端收到离线消息后会回复服务端ack,然后将消息存储到本地数据库、刷新UI等。客户端反馈,即使客户端采用异步方式也会有比较严重的性能问题。
▶ 【于是我想】:
为什么客户端收到消息后还没有将数据存储到数据库就回复给服务端ack?很有可能存储失败,这本身不合理,这是其一。其二,服务端强推导致客户端卡死,不关心客户端的处理能力,不合理。
▶ 【伪代码如下】:
int max = 100;
//从新库读
while(max > 0) {
List
offlineMsgListNew = shardChatOfflineMsgDao.getByToUid(uid, 20); if(CollectionUtils.isEmpty(offlineMsgListNew)) {
break;
}
handleOfflineMsg(uid, offlineMsgListNew, checkOnlineWhenSendingOfflineMsg);
max--;
}
▶ 【初步方案】:
既然强推不合理,我们可以换一种方式,根据客户端不同机型的处理能力的不同,服务端采用不同的速度下发。
我们可以把整个过程当成一种生产者消费者模型,服务端是消息生产者,客户端是消息消费者。客户端收到消息,将消息存储在本地数据库,刷新UI界面后,再向服务端发送ack消息,服务端收到客户端的ack消息后,再推送下一批消息。
这么一来,消息下发速度完全根据客户端的处理能力,分批下发。但这种方式仍然属于推方式。
▶ 【悲剧结果】:
然而,理想很丰满,现实却很骨感。
针对这个方案,客户端提出一些问题:
- 1)虽然这种方案,客户端不会卡死,但是如果当前用户的离线消息特别多,那么收到所有离线消息的时间会非常长;
- 2)客户端每次收到消息后会刷新界面,很有可能客户端会发生,界面上下乱跳的画面。
so,这个方案被否定了。。。
5.2 方案二
▶ 【我的思考】:
既然强推的数据量过大,我们是否可以做到,按需加载?客户端需要读取离线消息的时候服务端给客户端下发,不需要的时候,服务端就不下发。
▶ 【技术方案】:针对离线消息,我们做了如下方案的优化
1)我们增加了离线消息计数器的概念:保存了每个用户的每个会话,未读的消息的元数据(包括未读消息数,最近的一条未读消息、时间戳等数据),这个计数器用于客户端显示未读消息的的红色气泡。这个数据属于增量数据,只保留离线期间收到的消息元数据。
消息格式如下:
{
"sessionId1":{
"count":20,
"lastMsg":[
"最后N条消息"
],
"timestamp":1234567890
},
"sessionId2":{
}
}

2)客户端每次登录时,服务端不推送全量离线消息,只推送离线消息计数器(这部分数据存储在redis里,并且数据量很小),这个数量用户显示在客户端消息列表的未读消息小红点上。
3)客户端拿到这些离线消息计数器数据,遍历会话列表,依次将未读消息数量累加(注意:不是覆盖,服务端保存客户端离线后的增量数据),然后通知服务端清空离线消息计数器的增量数据。
4)当客户端进入某会话后,上拉加载时,通过消息的msgId等信息发送HTTP请求给服务端,服务端再去分页查询离线消息返回给客户端。
5)客户端收到消息并保存在本地数据库后,向服务端发送ack,然后服务端删除离线消息表的离线消息。
▶ 【预期结果】:
客户端、服务端的技术人员认可这个方案。我们通过推拉结合的方式,解决了客户端加载离线消息卡顿的问题。(改造前是强推,改造后采用推拉结合的方式)
流程图如下:

▶ 【新的问题】:
方案虽然通过了,但是引发了一个新问题:即客户端消息衔接问题。
问题描述如下:客户端登录后进入会话页面,因为客户端本身就保存着历史消息,那么客户端下拉加载新消息时,到底怎么判断要加载本地历史消息?还是要请求服务端加载离线消息呢?
经过一番思考,服务端和客户端最终达成了一致的方案:
- 1)在未读消息计数器的小红点逻辑中,服务端会把每个会话的最近N条消息一起下发给客户端;
- 2)客户端进入会话时,会根据未读消息计数器的最近N条消息展示首页数据;
- 3)客户端每次下拉加载时,请求服务端,服务端按时间倒排离线消息返回当前会话最近一页离线消息,直到离线消息库中的数据全部返回给客户端;
- 4)当离线消息库中没有离线消息后,返回给客户端一个标识,客户端根据这个标识,在会话页面下一次下拉加载时不请求服务端的离线消息,直接请求本地数据库。
6、消息ACK逻辑的优化
最后,我们也对消息ack的逻辑进行了优化。
优化前:服务端采用push模型给客户端推消息,不论是在线消息还是离线消息,ack的逻辑都一样,其中还用到了kafka、redis等中间件,流程很复杂(我在这里就不详细展开介绍ack的具体流程了,反正不合理)。
离线消息和在线消息不同的是,我们不存储在线消息,而离线消息会有一个单独的库存储。完全没必要用在线消息的ack逻辑去处理离线消息,反而很不合理,不仅流程上有问题,也浪费kafka、redis等中间件性能。
优化后:我们和客户端决定在每次下拉加载离线消息时,将收到的上一批离线消息的msgId或消息偏移量等信息发送给服务端,服务端直接根据msgId删除离线库中已经发送给客户端的离线消息,再返回给客户端下一批离线消息。
另外:我们还增加了消息漫游功能,用户切换手机登录后仍然可以查到历史消息,这部分内容我就不展开详细介绍给大家了。
7、设计优化方案时的文档截图(仅供参考)
下面是优化的方案文档截图,请大家参考。



即时通讯-单聊功能表-聊天功能-聊天app-聊天信息加密沟通-聊天文本-聊天技术-聊天构架源码-聊天 记录同步云储存-聊天消息测回-哇谷即时通讯IM源码


附录:更多IM开发相关文章汇总
[1] IM开发热门文章:
《移动端IM开发者必读(一):通俗易懂,理解移动网络的“弱”和“慢”》
《移动端IM开发者必读(二):史上最全移动弱网络优化方法总结》
《现代移动端网络短连接的优化手段总结:请求速度、弱网适应、安全保障》
《小白必读:闲话HTTP短连接中的Session和Token》
《IM开发基础知识补课:正确理解前置HTTP SSO单点登录接口的原理》
《IM消息送达保证机制实现(一):保证在线实时消息的可靠投递》
《开源IM工程“蘑菇街TeamTalk”的现状:一场有始无终的开源秀》
《QQ音乐团队分享:Android中的图片压缩技术详解(上篇)》
《QQ音乐团队分享:Android中的图片压缩技术详解(下篇)》
《腾讯原创分享(一):如何大幅提升移动网络下手机QQ的图片传输速度和成功率》
《腾讯原创分享(二):如何大幅压缩移动网络下APP的流量消耗(上篇)》
《腾讯原创分享(三):如何大幅压缩移动网络下APP的流量消耗(下篇)》
《如约而至:微信自用的移动端IM网络层跨平台组件库Mars已正式开源》
《基于社交网络的Yelp是如何实现海量用户图片的无损压缩的?》
《腾讯技术分享:腾讯是如何大幅降低带宽和网络流量的(图片压缩篇)》
《腾讯技术分享:腾讯是如何大幅降低带宽和网络流量的(音视频技术篇)》
《字符编码那点事:快速理解ASCII、Unicode、GBK和UTF-8》
《子弹短信光鲜的背后:网易云信首席架构师分享亿级IM平台的技术实践》
《IM开发基础知识补课(五):通俗易懂,正确理解并用好MQ消息队列》
《微信技术分享:微信的海量IM聊天消息序列号生成实践(算法原理篇)》
《自已开发IM有那么难吗?手把手教你自撸一个Andriod版简易IM (有源码)》
《IM开发基础知识补课(六):数据库用NoSQL还是SQL?读这篇就够了!》
《适合新手:从零开发一个IM服务端(基于Netty,有完整源码)》
《适合新手:手把手教你用Go快速搭建高性能、可扩展的IM系统(有源码)》
《IM里“附近的人”功能实现原理是什么?如何高效率地实现它?》
《IM开发基础知识补课(七):主流移动端账号登录方式的原理及设计思路》
《IM开发基础知识补课(八):史上最通俗,彻底搞懂字符乱码问题的本质》
《IM“扫一扫”功能很好做?看看微信“扫一扫识物”的完整技术实现》
《IM的扫码登录功能如何实现?一文搞懂主流应用的扫码登录技术原理》
《IM消息ID技术专题(一):微信的海量IM聊天消息序列号生成实践(算法原理篇)》
《IM消息ID技术专题(二):微信的海量IM聊天消息序列号生成实践(容灾方案篇)》
《IM消息ID技术专题(三):解密融云IM产品的聊天消息ID生成策略》
《IM消息ID技术专题(四):深度解密美团的分布式ID生成算法》
《IM消息ID技术专题(五):开源分布式ID生成器UidGenerator的技术实现》
《IM开发宝典:史上最全,微信各种功能参数和逻辑规则资料汇总》
《IM开发干货分享:我是如何解决大量离线聊天消息导致客户端卡顿的》
>> 更多同类文章 ……
[2] IM群聊相关的技术文章:
《IM群聊消息究竟是存1份(即扩散读)还是存多份(即扩散写)?》
《一套高可用、易伸缩、高并发的IM群聊、单聊架构方案设计实践》
《[技术脑洞] 如果把14亿中国人拉到一个微信群里技术上能实现吗?》
《阿里钉钉技术分享:企业级IM王者——钉钉在后端架构上的过人之处》
>> 更多同类文章 ……
[3] 有关IM架构设计的文章:
《一套海量在线用户的移动端IM架构设计实践分享(含详细图文)》
《IM开发基础知识补课(二):如何设计大量图片文件的服务端存储架构?》
《IM开发基础知识补课(三):快速理解服务端数据库读写分离原理及实践建议》
《IM开发基础知识补课(四):正确理解HTTP短连接中的Cookie、Session和Token》
《WhatsApp技术实践分享:32人工程团队创造的技术神话》
《王者荣耀2亿用户量的背后:产品定位、技术架构、网络方案等》
《IM系统的MQ消息中间件选型:Kafka还是RabbitMQ?》
《腾讯资深架构师干货总结:一文读懂大型分布式系统设计的方方面面》
《子弹短信光鲜的背后:网易云信首席架构师分享亿级IM平台的技术实践》
《知乎技术分享:从单机到2000万QPS并发的Redis高性能缓存实践之路》
《IM开发基础知识补课(五):通俗易懂,正确理解并用好MQ消息队列》
《微信技术分享:微信的海量IM聊天消息序列号生成实践(算法原理篇)》
《微信技术分享:微信的海量IM聊天消息序列号生成实践(容灾方案篇)》
《新手入门:零基础理解大型分布式架构的演进历史、技术原理、最佳实践》
《一套高可用、易伸缩、高并发的IM群聊、单聊架构方案设计实践》
《阿里技术分享:阿里自研金融级数据库OceanBase的艰辛成长之路》
《社交软件红包技术解密(一):全面解密QQ红包技术方案——架构、技术实现等》
《社交软件红包技术解密(二):解密微信摇一摇红包从0到1的技术演进》
《社交软件红包技术解密(三):微信摇一摇红包雨背后的技术细节》
《社交软件红包技术解密(四):微信红包系统是如何应对高并发的》
《社交软件红包技术解密(五):微信红包系统是如何实现高可用性的》
《社交软件红包技术解密(六):微信红包系统的存储层架构演进实践》
《社交软件红包技术解密(七):支付宝红包的海量高并发技术实践》
《社交软件红包技术解密(九):谈谈手Q红包的功能逻辑、容灾、运维、架构等》
《社交软件红包技术解密(十):手Q客户端针对2020年春节红包的技术实践》
《即时通讯新手入门:一文读懂什么是Nginx?它能否实现IM的负载均衡?》
《即时通讯新手入门:快速理解RPC技术——基本概念、原理和用途》
《多维度对比5款主流分布式MQ消息队列,妈妈再也不担心我的技术选型了》
《从游击队到正规军(一):马蜂窝旅游网的IM系统架构演进之路》
《从游击队到正规军(二):马蜂窝旅游网的IM客户端架构演进和实践总结》
《IM开发基础知识补课(六):数据库用NoSQL还是SQL?读这篇就够了!》
《瓜子IM智能客服系统的数据架构设计(整理自现场演讲,有配套PPT)》
《阿里钉钉技术分享:企业级IM王者——钉钉在后端架构上的过人之处》
《从游击队到正规军(三):基于Go的马蜂窝旅游网分布式IM系统技术实践》
《IM开发基础知识补课(九):想开发IM集群?先搞懂什么是RPC!》
>> 更多同类文章 ……
(本文同步发布于:http://www.52im.net/thread-3036-1-1.html)






热门搜索:
什么是私有云? 私有云、公有云还是混合云?企业该如何选? 企业聊天APP有什么作用,可以带来哪些便利? 企业选择混合云的优势 聊天APP应该具备哪些功能?怎么确保信息不被泄露? 企业即时通讯的使用价值有哪些 企业IM是什么,有什么使用优势? JM沟通优势有哪些,安全性怎么样? JM沟通APP功能强大且安全性高 IM云系统即时通讯公有云、私有云、企业云、海外云-哇谷IM团队 对于IM即时通讯的性能与并发性问题xmpp等各种通信协议 sso登录统一账号体系和集中认证授权 MongoDB数据库百万并发设计使用方法 WebRTC视频会议服务器性能10万并发 办公协同解决方案的意义 有什么优势? 美国FBI遭黑客攻击 超10万人收到垃圾邮件!这家零佣金券商也被攻击 用户数据泄露-企业即时通讯安全 工作台支持私有化部署-企业办公通信管理-软件开发管理系统-哇谷云 Application scenarios of wagu im cloud service A typical IM architecture might look like this 哇谷IM实现原理讲解-哇谷IM即时通讯云 JM沟通功能图片演示-哇谷IM即时通讯云 硬件配置选择-哇谷IM即时通讯云 IM私有化价格服务系统-哇谷IM即时通讯云 哇谷云服务项目表 哇谷IM定制私有化搭建资料准备工单-哇谷IM即时通讯云 APP中红包功能技术最常见的形式-哇谷im即时通讯云 私有云、公有云还是混合云?企业该如何选? 租用海外云服务器时应注意的事项 私有云比起公有云安全性大大提高 哇谷云服务应用场景十分丰富多元 聊天APP功能强大 用户群体越来越广泛 企业打造私有云平台需要注意的几点 企业聊天办公软件如何挑选,需要具备哪些功能? IM的作用可不只是单纯的聊天工具 企业使用即时通讯的好处有哪些? 企业通讯常见的困扰:有什么办法提高通讯质量? 聊天APP应该具备哪些功能?怎么确保信息不被泄露? 语音会议软件越来越多:到底哪个软件适合开会? 使用手机能否召开视频会议?哪些软件值得选择? JM沟通与传统通讯软件有何区别?是否只能用于社交? 语音会议效果难以保障 你有准备专业会议软件吗 专业企业通讯软件功能齐全 让办公变得更加轻松快捷 业应不应该开发聊天APP 三个不容拒绝的理由 企业通讯软件如何选择 隐私保护终于一切 视频会议没有专用软件 很多事情都无法处理 哪些是常用的微服务框架? 企业如何搭建私有云? 即时通讯软件是否具有聊天回执的功能? 企业即时通讯软件支持多设备登陆吗? 企业即时通讯适合中小企业的即时通讯软件? 盘点全球几大即时通讯软件 介绍两款国外大佬级别的即时通讯APP Short video features 即时通讯短视频功能的基本特点主要有哪些 Features of chat function 聊天功能在即时通讯软件中的产品特色体现在哪里 Increase the function of red envelope 为什么在即时通讯软件中会增加红包功能 instant messaging 即时通讯 办私密软件公 Office privacy software Cloud service 云服务 Im definition IM定义 How to meet office requirements Main features of instant messaging 即时通讯云软件应该如何达到集团办公的需求 企业即时通讯软件设计的特点主要有哪些 Instant messaging cloud red packet 福利发送新办法,IM即时通讯云红包功能的大势所趋 LAN communication 区域网通讯可以促进企业发展,使信息传输更快捷 Production guide of enterprise specific app 企业专属聊天APP的生产指南,这里应有尽有! Waguyun privacy protection 企业通讯无法得到私密保障?哇谷云上办公为您提供专业服务! Choose Wawa Guyun for internal communication 企业内部交流怎么办?现在有了哇谷云! Advantages of LAN 局域网通讯企业真的需要吗?它有哪些优点? Enterprise instant messaging voice conference app 新发展、新机遇 全球疫情下的企业即时通讯语音会议 Provide efficient enterprise communication 专业呼叫中心 提供高效企业通信解决方案 New layout of global economic integration 协同办公解决方案,全球一体化经济新布局 There is more than one way for IM tools 还在使用微信?IM工具不止有一条途径 Power source for efficient operation 选择办公协同解决方案,现代企业高效运转的动力源 Efficient enterprise specific instant messaging 企业聊天APP,私密度高的高效企业专属即时通讯APP 新一代私有云与早期私有云的区别 The difference between private clouds What are the advantages of private cloud 新一代私有云有哪些优势? 单聊功能 群聊功能红包功能源码 实时音视频 短视频 音视频会议源码 低延迟直播 哇谷IM云服务 IM私有化搭建源码 私有化搭建准备文件 TF签名知识 企业即时通讯 IM 即时通讯 IM定制功能 语音聊天室 在线教育 金融服务 sso单点登录系统 政府加密沟通 企业通信办公 KTV点唱系统 局域网通讯 知识库 企业即时通讯 sso单点登录系统 KTV点唱系统版本Demo JM沟通2.0版本Demo 哇谷IM企业办公版本 哇谷工作台 短视频源码 音视频会议源码 直播聊天源码 ios超级签名服务 哇谷云 哇谷科技 即时通讯



