当前位置:首页 > 热点 > 指尖上的大数据 - IM消息处理优化方案

指尖上的大数据 - IM消息处理优化方案

2026-11-14 [焦点] 来源:人文荟萃网

在即时通讯领域,高并发消息处理是个很重要的话题。以京东客服系统举例,每当促销时,促销店铺的每个客服短时间内可能接收到大量的用户咨询,如果不能及时快速地展示出用户咨询的信息,那么就无法对用户的咨询进行快速的回复,进而可能会造成一定的用户流失,这是商家和京东所不能接受的。处理这种高并发的场景时,我们需要在消息查重、数据库IO性能、内存缓存、UI显示等多维度进行优化。

 

消息从服务端推到客户端,然后被分发到消息队列,在消息队列中经过一番处理后,最终展示到页面上。这张图只是简单地描述了消息的处理流程,下面我们将分步骤说明每个流程的设计和优化。

一、消息查重设计

客户端在消息处理完(存入数据库)后会给服务端发送已收回执,当服务端收到回执后便不再重复给客户端下发此消息,然而现实场景中如果我们处理的过慢或者网络丢指尖包,那服务端就会重复给客户端发送该消息。既然我们无法避免重复消息,那查重流程就是我们首先要考虑的。

1.1**重复消息处理过滤机制**

通常情况消息处理队列为一个串行队列,一条消息进入处理队列后,会涉及到Message表、User表,Conversation表等多个表的读写,而我们知道数据库的IO是非常耗时的。由于消息处理是个串行队列,消息会按照时间接收顺序在队列中排队,如果消息处理的不够快,那么服务端会因长时间收不到客户端回执而重复下发该消息,极端情况下这会导致消息队列中出现大量的重复消息,队列压力会越来越大,内存暴增导致OOM。另外因为重复的消息导致队列变长,新消息也不能及时被处理。解决这个问题很简单,我们可以在消息进入处理队列前先进行过滤,如果已经有同样的消息进入处理队列,就直接丢掉。具体设计如下图:

1.2**上的本地缓存过滤机制**

为了避免相同消息重复处理的情况,消息在进入处理队列后,首先要判断该消息是否已经处理过(标志就是缓存是否已经同样的消息),如果缓存有则不重复处理。其中缓存分为内存缓存和数据库两部分,当消息在持久化时,同时在内存和数据库中进行缓存。消息查重分为两步,首先判断内存缓存中是否有,如果有则直接丢弃该消息,而如果没有再通过sql来查询数据库,如果第一步内存缓存命中,就可以少一次数据库的查询。具体设计如下图:

二、写入性能优化 2.1**消息批处理写入**

消息处理完需要入库持久化,在这里可以分为两种方式,一种是消息处理完立即入库,一种是开启事务批量入库。其中第一种比较好理解实现起来也比较简单,第二种我们在消息积攒到一定量或者一个时间段结束后批量入库。SQLite的数据操作实质上是对数据文件的IO操作,大数频繁地插入数据会导致文件IO经常开闭,非常损耗性能。通过开启事务将数据先缓存在内存中,当提交事务时再把所有的更改更新到数据文件,此时数据文件的IO只需要开闭一次,也避免了长期占用文件IO所导致性能低下的问题。

以下数据表记录了在iPhone 6s设备上,这两种方式不同数据量写入数据库消耗的时间:

通过上表,我们可以看到数据量越大,开启事务后性能提升就越明显。那是不是在实践中一定要开启事务呢?不一定。对于IM消息来说,大部分服务端都是一条一条下发给客户端,并不存在多条消息同时到达客户端的情况,如果我们想用到事务的特性,需要先将处理完的消息缓存到内存中,定时或者定量进行批处理入库,而这都需要额外的逻辑实现,会增加代码的复杂度,进而增加维护成本。另外由于消息到达先后特性,最终的效果会因为网络等状况并没有上面的数据那么好。大家据 可以根据自身的情况抉择。

除了利用事务来提高写入性能外,SQLite在3.7.0版本引入了WAL(Write-Ahead Log)模式,在特定情况下可以大幅提升写入性能。

2.2**开启WAL模式**

“原子提交(atomic commit)”是SQLite一个重要特性,原子提交意味着单个事务的所有更改要么全部完成,要么全部不完成,不会出现单个事务内的操作执行到一半的情况。为了实现这个特性,SQLite需要临时文件的辅助,比如rollback模式的journal文件;WAL模式的wal文件和shm文件。

SQLite默认为rollback模式,我们可以通过修改配置更改为WAL模式。下面通过对两种模式的事务提交流程分析,来看看WAL模式怎么提高写性能的。

2.2.1ROLLBACK ģʽ

SQLite数据库连接默认为rollback- 模式(journal_mode = DELETE;)。 rollback模式工作原理大致为:写操作进行前进行数据库文件拷贝,然后对数据库进行写操作。如果发生Crash或者Rollback则将日志中的原始内容回滚到数据库文件进行恢复操作,否则在Commit完成时删除日志文件。以下为rollback模式下写入的重要的节点:

首先,在系统缓存中创建rollback journal文件,把需要修改的原始内容保存到这个文件中,然后修改用户空间的数据库; 然后,将rollback journal文件头和文件内容通过两次fsync()从系统缓存同步到磁盘中(这个步骤非常耗时); 下一步,先将修改后的数据同步到系统缓存,再同步到磁盘中; 最后,删除rollback journal文件;

以上只列举了单个事务提交成功的流程,由于篇幅的原因,如提交失败IM(设备断电、系统崩溃等)rollback流程等细节内容可以参考SQLite官方文档,文档很完善,强烈建议抽时间学习下。

2.2.2WAL**ģʽ**

首先,我们看下官方文档中对WAL模式的优缺点描述:

优点有:

在大多数情况下,使用WAL模式速度更快; WAL模式进一步提升了数据库的并发性,因为读不会阻塞写,而写也不会阻塞读,读和写可以并发执行; 使用WAL模式,磁盘I/O操作更有秩序; 使用WAL模式减少了fsync()操作次数,因此不易受到系统上的fsync()系统调用(system call)中断的影响;

缺点有:

WAL模式通常要求VFS支持共享内存原语(shared-memoryprimitives); 使用数据库的所有进程必须位于同一台主机上, WAL无法在网络文件系统上运行; 在读取操作远多于写入操作的应用程序中,WAL消息可能比传统的日志模式稍慢(可能慢1%或2%); 每个数据库文件都关联了额外的.wal文件和.shm共享内存文件; **写流程:**

WAL模式相较于rollback则采用了相反的做法。在进行数据库写操作时,将数据append到-wal日志文件中而原有数据库内容保存不变。如果事务失败,-wal文件中的记录会被忽略;如果事务成功,它将在随后的某个时间被写回到数据库文件中,该步骤被称为Checkpoint。WAL模式下写数据库操作比rollback模式下更为集中,而且该模式下显著降低了磁盘同步fsync()的频率,所以相对来说写性能更优秀。我们可以使用以下代码开启WAL模式:

1. PRAGMA journal_mode = WAL; 

**读流程:**

在WAL模式下读处理的时候,SQLite会先在WAL文件中搜索,找到最后一个写入点,记住它,并忽略在此之后的写入点(这保证了读写和读读可以并发执行)。随后,它确定所要读的数据的所在页是否在-wal文件中,如果在,则读-wal文件中的数据,如果不在,则直接读数据库文件中的数据。为了避免每个读取操作扫描整个-wal文件来寻找页面(-wal文件可以增长到几兆字节,具体取决于Checkpoint运行的频率,默认情况下,当-wal文件达到1000页的阈值大小时,SQLite会自动执行Checkpoint,我们也可以修改SQLITE_DEFAULT_WAL_AUTOCHECKPOINT来指定不同的阈值),SQLite提供了WAL-index文件来辅助页面的查找。WAL-index文件使用了进程间共享内存的技术,共享内存是一个以.shm结优化尾并且和数据库文件在同一个目录下的文件,这个文件比较特别,内存和文件存在映射关系,取到这个文件的地址后可以像内存一样对其读写,而一般文件需要调用read、write函数才能读写。WAL-index可以帮助读取操作快速定位WAL文件中的页面,极大地提高了读取的性能。

**读、写测试:**

以下数据表记录了在iPhone 6s设备上,这两种模式不同数据量的写和读耗时:

写入测试 读测试

从上面两个表的测试数据可以看到WAL模式对读性能影响有限,而写入性能相对于rollback模式提升了3**~4倍左右**。iOS系统从5.1.1版本开始SQLite版本便升级到3.7.7,而我们现在大部分应用支持的最低版本为iOS8,所以我们可以直接开启WAL模式来提高写入性能。

三、查询性能优化 3.1**对常用列查询添加索引**

为了防止方案查询数据时每次都遍历整张表,常见的关系型数据库均提供了索引,适当地添加索引可以大大提高数据库的读性能。SQLite索引结构为B+树,也被存在数据库文件里,结构如下图(该图来自维基百科) :

提升查找速度的关键在于尽可能减少磁盘I/O,那么可以知道,每个节点中的key个数越多,树的高度就越小,需要I/O的次数也就越少。因为B+树的非叶节点中不存储data,所以可以存储更多的key。很多存储引擎在B+树的基础上进行了优化,添加了指向相邻叶节点的指针,形成了带有顺序访问指针的B+树,这样做可以提高区间查找的效率,只要找到第一个值那么就可以顺序的查找后面的值。

3.1.1**几种索引方式**

SQLite主要有以下四种索引方式:

普通索引(只基于表的一个列创建的索引) 唯一索引(除了普通索引的特性,索引列重复的数据不允许插入到表中)指尖 隐式索引(数据库隐式为主键创建的唯一索引) 组合索引(基于一个表的两个或多个列创建的索引)

这里重点说下组合索引,例如为table_name表创建了col1,col2,col3组合索引:

1. ALTER TABLE 'table_name' ADD INDEXindex_name('col1','col2','col3'); 

组合索引遵循”最左前缀”原则,把最常用作为检索或排序的列放在最左,依次递减,上面的组合索引相当于建立了col1,col1col2,col1col2col3三个索引,而col2或者col3是不能使用索引的,这里一定要注意查询语句和索引的顺序要一致,否则索引无法正常命中。

3.1.2**添加索引性能提升**

以下数据表记录了在iPho上的ne 6s设备上,不同数据量有无索引情况下的性能表现:

从上面表来看,添加索引对数据库的读性能提升很大,尤其是当本地数据表越来越大,有索引与没有索引读性能对比是天壤地别。但是在使用索引时一定要要了解每种索引的适用、命中原则情况,不要一股脑的添加索引。首先,索引是需要额外的磁盘空间存储;其次,在insert/update数据时索引结构可能会发生变化消耗一部分写入性能;再次,不合理的查询语句会命中不了索引。查询优化还是建议大家翻阅官方文档。

3.2**增加内存Cache层提升查询性能**

虽然我们可以通过添加索引的方式,提升数据库的查询性能。但毕竟在系统磁盘缓存未命中时还是需要进行磁盘IO,而我们知道磁盘IO是非常耗时,所以减少对库的操作对读性能提升也很有帮助。为了实现这点,我们可以在DB层上面增加内存Cache层,在读数大数据时优先从内存Cache层读,如果命中便可以少一次读库操作。内存缓存可以使用简单的key-value结构,key为主键(或者其他唯一键,这个键应当经常被当作查询条件),下图为增加内存Cache层后的查询和缓存逻辑:

四、消息**UI刷新设计**

当消息处理完后,下一步需要把消息展示在UI上。如果每条消息处理完就立即刷新页面,在普通低并发场景下没有太大问题,但是在高并发场景下就会造成短时间内UI刷新次数过多,从而导致页面卡顿,在这里我们可以通过两种方式进行优化。

4.1**延迟刷新**

消息到达UI队列时,可以延迟特定时间(比如100ms)再刷新UI,每条消息都将UI刷新的时间延迟100ms刷新。为了防止UI刷新操作因新消息的到来而一直被延迟,可以设置延迟阈值(比如2s),当达到延时阈值时,直接提交刷新UI操作。

4.据 2**滑动列表时不刷新UI**

当用户滑动会话列表/会话页消息列表时,列表不刷新,等到列表停止滑动时再刷新,这样可以保证列表的滑动流畅度。iOS实现起来很方便,只要把Timer加到NSDefaultRunLoopMode就可以了。下图为具体的实现逻辑:

五、最终完整的设计

我们通过上面几点,将消息处理的每个步骤的优化点一一做了说明,下图详细地展示了消息从接收到展示的完整处理流程:

六、最后

我们通过消息查重设计、写入性能优化、查询性能优化、消息UI刷新设计四个维度,分别介绍了高并发消息处理的优化逻辑。希望通过此文章,可以给你在设计客户端高并发消息处理方案时提供一种新的思路。

 

(责任编辑:技术动态)

推荐文章
  • 如今,可实时扫描100万神经元:大脑细胞活动成像技术新突破

    如今,可实时扫描100万神经元:大脑细胞活动成像技术新突破 几年前,计算机同时记录一万个神经元活动就会成为新闻。转眼间,现在已经有人做到一百万了。 在科幻小说《三体》中,面壁者希恩斯冬眠八年后,人类研究出了比他冬眠前计算能力高一万倍的计算机,这样人类就可以对人...[详细]
  • 标签: 新东方

    标签: 新东方 撰文 | 文 林 编辑 | 杨博丞 题图 | IC Photo 在新东方迎来成立32周年纪念日时,创始人俞敏洪从南极发出一封内部贺信。信中描绘了“浩瀚的洁白”“翡翠般的冰山”等南极景观,并以企鹅“相互...[详细]
  • Facebook内容审核员:“科技界最糟糕的工作”?

    Facebook内容审核员:“科技界最糟糕的工作”? 新浪科技讯 北京时间4月15日下午消息,据报道,本周三,一位Facebook内容审核员在辞职前,向外界发出了自己的声音,称公司的内容审核系统不健全,且未能对审核员的工作提供足够支持。 有媒体人员在Tw...[详细]
  • 详解PyTorch基本操作

    详解PyTorch基本操作 什么是 PyTorch? PyTorch是一个基于Python的科学计算包,提供最大灵活性和速度的深度学习研究平台。 张量 张量类似于NumPy 的n 维数组,此外张量也可以在 GPU 上使用以加速计...[详细]
  • 标签: Synopsys Ansys EDA AI芯片设计

    标签: Synopsys Ansys EDA AI芯片设计 云梦级5月29日云梦级消息,Synopsys(新思科技)当地时间 27 日发布 2026 财年(2025 年 11 月 1 日~2026 年 10 月 31 日)第二财季(2026 年 2 月 1 日...[详细]
  • 标签: SpaceX IPO ITAR 美国国防

    标签: SpaceX IPO ITAR 美国国防 云梦级6月8日云梦级消息,据外媒报道,SpaceX 的承销商已经禁止中国内地和香港投资者参与其计划中的首次公开募股。负责发行的主要银行已通知承销团成员,不要接受来自中国内地和香港客户的认购订单,包括私...[详细]
  • 谷歌母公司Alphabet将于4月28日发布2021年第一季度财报

    谷歌母公司Alphabet将于4月28日发布2021年第一季度财报 新浪科技讯 北京时间4月12日下午消息,谷歌母公司Alphabet(NASDAQ: GOOG, GOOGL)宣布,将于美国东部时间4月27日股市收盘后(北京时间4月28日)发布2021年第一季度财报。...[详细]
  • 标签: 腾讯 马维斯Marvis AI助手 WindowsAI

    标签: 腾讯 马维斯Marvis AI助手 WindowsAI 云梦级4月28日云梦级消息,《读佳》获知,腾讯悄悄内测一款叫做“马维斯Marvis”的AI产品,可能是国内首个真正“接管电脑”的AI助手,产品除了桌面版外,还有APP版本。 据了解,不同于市面上只会套...[详细]
  • 阿里云张勇:已有20万企业用户申请接入通义千问AI大模型测试

    阿里云张勇:已有20万企业用户申请接入通义千问AI大模型测试 4 月 26 日消息,在今日的 2023 阿里云合作伙伴大会上,阿里巴巴集团董事会主席兼首席执行官,阿里云智能集团首席执行官张勇发表演讲。 张勇透露, 已有 20 万企业用户申请接入通义千问测试 。同...[详细]
  • 阿里云还会继续降价吗?

    阿里云还会继续降价吗? “百模大战”成为行业热潮之后,作为算力、算法底层支撑的云计算,不可避免成为互联网大厂之间的角力重点。 陆玖商业评论从相关渠道获悉,继四月底“史上最大规模”的降价之后,阿里云的公有云产品未来仍有持续降价...[详细]