互联网技术 / 互联网资讯 · 2024年3月11日

IM消息处理的大数据优化方案

在即时通讯领域,高并发消息处理是一个关键挑战。以大型客服场景为例,当促销期间,店铺客服需要快速展示大量用户咨询,否则可能造成用户流失。这就要求在查重、数据库 IO、内存缓存、以及界面展示等层面进行优化。

消息从服务端推送到客户端,经过分发与队列处理,最终呈现到页面上。下面将按步骤说明各环节的设计与优化要点。

一、消息查重设计

客户端在处理完并将消息存入数据库后,会向服务端发送已收回执。若因处理慢或网络丢包,服务端可能重复发送同一消息。既然无法完全避免,查重成为首要任务。

1.1 重复消息过滤机制

消息队列通常为串行队列,消息涉及多表读写,数据库 IO 成本较高。为避免重复发送导致队列堆积,应在消息进入处理队列前先过滤,若已有同样消息进入处理队列则丢弃。具体设计如下图:

1.2 本地缓存过滤机制

为避免重复处理,消息进入处理队列后需判断是否已处理。缓存分为内存缓存和数据库两部分,同时在持久化时进行缓存。查重分两步:先在内存缓存命中则直接丢弃;若未命中再查询数据库。若第一步命中可减少一次数据库查询。具体设计如下图:

二、写入性能优化

2.1 消息批处理写入

消息处理完成后需要入库。可分两种方式:即时入库,或开启事务进行批量入库。频繁写入会造成文件 IO 高开销。通过开启事务将数据缓存到内存,提交时一次性写入,降低 IO 次数。这在 SQLite 的数据操作中尤为明显。

以下数据表显示在 iPhone 6s 上两种写入策略在不同数据量下的耗时对比:

通过上述对比,数据量越大,事务化写入的性能提升越明显。实践中是否始终开启事务取决于具体场景:IM 场景下多为逐条下发,批量化特性需要额外逻辑,增加复杂度,且网络波动会影响最终效果。可根据实际情况权衡。

此外,SQLite 3.7.0 引入 WAL(Write-Ahead Log)模式,在某些场景下能显著提升写入性能。

2.2 开启 WAL 模式

原子提交(Atomic Commit)是 SQLite 的核心特性之一;WAL 模式通过日志文件提升并发性与写入效率。可通过配置切换为 WAL 模式。下面简要分析两种模式的提交流程以了解 WAL 的优势。

2.2.1 ROLLBACK 模式

默认情况下,SQLite 使用 Rollback(回滚)模式。写操作前会拷贝数据库文件的必要部分,写入后再进行多次 fsync,确保持久性。失败时日志回滚,成功时删除日志。以下是 Rollback 模式下关键节点。

简单描述过程,实际细节请参考官方文档。

2.2.2 WAL 模式

回顾官方对 WAL 的优点与缺点:速度更快、并发性更高、减少 fsync 次数等,但需要支持共享内存等条件,且在某些场景对 IO 有不同影响。开启 WAL 的基本思路如下。

写入流程:数据追加到 WAL 日志,原有数据库内容保持不变,事务成功后在后续时间点将记录写回数据库。这一过程称为 Checkpoint。WAL 使写操作更集中,显著降低了磁盘同步频率。

要开启 WAL,可以用以下 SQL 设置:
PRAGMA journal_mode = WAL;

读流程:

在 WAL 模式下,读取时会优先在 WAL 中查找最近的写入点,若在则读取 WAL;否则读取数据库文件。为提高效率,WAL 使用 WAL-index 文件辅助定位。

以下为读写测试表述:写入与读取在 iPhone 6s 上的对比,WAL 对写的提升明显,读提升有限。解析结论可据实际版本决定开启 WAL。

三、查询性能优化

3.1 对常用列添加索引

合适的索引能显著提升查询效率。SQLite 的索引基于 B+树,叶节点含数据,非叶节点只存键以降低高度。部分引擎对叶节点增加指向相邻叶的指针,提升区间查询性能。

3.1.1 几种索引方式

常见的索引类型包括普通、唯一、隐式、组合索引。

下面以 table_name 的列组合索引为例:
ALTER TABLE ‘table_name’ ADD INDEX index_name (col1, col2, col3);

组合索引遵循最左前缀原则,将最常用的列放在最前,后续列按需排列。不同查询语句和索引的顺序需保持一致,以确保命中。

3.1.2 添加索引的性能影响

下列表格显示在 iPhone 6s 上,不同数据量下有无索引的对比。添加索引对读取性能提升显著,特别是本地数据表增大时。然而索引也占用额外磁盘空间,在写入时索引结构也会带来额外代价,且不合理的查询可能不命中索引。建议结合官方文档进行优化。

3.2 扩展内存缓存层提升查询性能

虽然通过索引可以提升查询,但引入内存缓存层可进一步提升命中率和速度。 [[[IMG_1]]]