IM开发干货分享:我是如何解决大量离线消息导致客户端卡顿的

业务痛点

随着业务蓬勃发展,用户的不断增多,用户创建的群、加入的群和好友不断增多和聊天活跃度的上升,某些用户不在线期间,产生大量的离线消息(尤其是针对群聊,离线消息特别多)。

等下次客户端上线时,服务端会给客户端强推全部的离线消息,导致客户端卡死在登录后的首页。并且产品提出的需求,要扩大群成员的人数(由之前的百人群扩展到千人群、万人群等)。

这样一来,某些客户端登录后必定会因为大量离线消息而卡死,用户体验极为不好。

和客户端的同事一起分析了一下原因:

(庆幸的是,在线消息目前没有性能问题)。

所以针对上述问题,结合产品对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消息后,再推送下一批消息。

这么一来,消息下发速度完全根据客户端的处理能力,分批下发。但这种方式仍然属于推方式。

▶ 【悲剧结果】:

然而,理想很丰满,现实却很骨感。

针对这个方案,客户端提出一些问题:

so,这个方案被否定了。。。

5.2 方案二

▶ 【我的思考】:

既然强推的数据量过大,我们是否可以做到,按需加载?客户端需要读取离线消息的时候服务端给客户端下发,不需要的时候,服务端就不下发。

▶ 【技术方案】:针对离线消息,我们做了如下方案的优化

1)我们增加了离线消息计数器的概念:保存了每个用户的每个会话,未读的消息的元数据(包括未读消息数,最近的一条未读消息、时间戳等数据),这个计数器用于客户端显示未读消息的的红色气泡。这个数据属于增量数据,只保留离线期间收到的消息元数据。

消息格式如下:

{

"sessionId1":{

"count":20,

"lastMsg":[

"最后N条消息"

],

"timestamp":1234567890

},

"sessionId2":{

}

}

IM开发干货分享:我是如何解决大量离线消息导致客户端卡顿的

2)客户端每次登录时,服务端不推送全量离线消息,只推送离线消息计数器(这部分数据存储在redis里,并且数据量很小),这个数量用户显示在客户端消息列表的未读消息小红点上。

3)客户端拿到这些离线消息计数器数据,遍历会话列表,依次将未读消息数量累加(注意:不是覆盖,服务端保存客户端离线后的增量数据),然后通知服务端清空离线消息计数器的增量数据。

4)当客户端进入某会话后,上拉加载时,通过消息的msgId等信息发送HTTP请求给服务端,服务端再去分页查询离线消息返回给客户端。

5)客户端收到消息并保存在本地数据库后,向服务端发送ack,然后服务端删除离线消息表的离线消息。

▶ 【预期结果】:

客户端、服务端的技术人员认可这个方案。我们通过推拉结合的方式,解决了客户端加载离线消息卡顿的问题。(改造前是强推,改造后采用推拉结合的方式)

流程图如下:

IM开发干货分享:我是如何解决大量离线消息导致客户端卡顿的

▶ 【新的问题】:

方案虽然通过了,但是引发了一个新问题:即客户端消息衔接问题。

问题描述如下:客户端登录后进入会话页面,因为客户端本身就保存着历史消息,那么客户端下拉加载新消息时,到底怎么判断要加载本地历史消息?还是要请求服务端加载离线消息呢?

经过一番思考,服务端和客户端最终达成了一致的方案:

6、消息ACK逻辑的优化

最后,我们也对消息ack的逻辑进行了优化。

优化前:服务端采用push模型给客户端推消息,不论是在线消息还是离线消息,ack的逻辑都一样,其中还用到了kafka、redis等中间件,流程很复杂(我在这里就不详细展开介绍ack的具体流程了,反正不合理)。

离线消息和在线消息不同的是,我们不存储在线消息,而离线消息会有一个单独的库存储。完全没必要用在线消息的ack逻辑去处理离线消息,反而很不合理,不仅流程上有问题,也浪费kafka、redis等中间件性能。

优化后:我们和客户端决定在每次下拉加载离线消息时,将收到的上一批离线消息的msgId或消息偏移量等信息发送给服务端,服务端直接根据msgId删除离线库中已经发送给客户端的离线消息,再返回给客户端下一批离线消息。

另外:我们还增加了消息漫游功能,用户切换手机登录后仍然可以查到历史消息,这部分内容我就不展开详细介绍给大家了。

7、设计优化方案时的文档截图(仅供参考)

下面是优化的方案文档截图,请大家参考。

IM开发干货分享:我是如何解决大量离线消息导致客户端卡顿的

IM开发干货分享:我是如何解决大量离线消息导致客户端卡顿的


IM开发干货分享:我是如何解决大量离线消息导致客户端卡顿的


IM开发干货分享:我是如何解决大量离线消息导致客户端卡顿的

IM开发干货分享:我是如何解决大量离线消息导致客户端卡顿的

展开阅读全文

页面更新:2024-04-28

标签:离线   在线   客户端   消息   干货   不合理   服务端   计数器   逻辑   加载   性能   方式   数据库   方案   数据   数码   用户

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top