IM消息送达保证机制
一、总体概述
即时通讯(IM)系统的消息投递机制旨在确保消息不丢失、不重复(即QoS机制),同时在移动网络环境下兼顾高效性(省流量、省电量)。两篇文章分别针对在线实时消息和离线消息场景,结合微信、QQ等案例和MobileIMSDK框架,阐述了实现原理、问题及优化方案。
核心目标
- 可靠性:消息从发送方到接收方无丢失。
- 去重性:接收方不收到重复消息。
- 高效性:优化移动端体验,降低资源消耗。
二、在线实时消息的可靠投递
1. 核心机制
在线实时消息针对发送方(Client-A)和接收方(Client-B)均在线的场景,通过应用层确认机制确保可靠投递。
- 报文类型:
- 请求报文(R):客户端发送给服务器。
- 应答报文(A):服务器对请求的响应。
- 通知报文(N):服务器推送给客户端。
- 投递流程(六个报文):
- Client-A发送消息请求(msg:R)给服务器。
- 服务器返回确认(msg:A)。
- 服务器推送消息通知(msg:N)给Client-B。
- Client-B发送确认请求(ack:R)给服务器。
- 服务器返回确认(ack:A)。
- 服务器推送确认通知(ack:N)给Client-A。
- 超时与重传:Client-A维护“等待ACK队列”,超时未收到ack:N则重传msg:R。
- 去重机制:消息分配唯一
msg_id
,Client-B根据msg_id
去重。
2. 存在问题
- 报文丢失:
- msg:R、msg:A丢失:可提示“发送失败”,影响较小。
- msg:N、ack:R、ack:A、ack:N丢失:Client-A无法确认消息送达,可能导致重传。
- 重传问题:
- msg:N丢失:重传有效。
- ack:N丢失:Client-B已收到消息但Client-A未收到确认,重传导致重复消息。
- 服务端负载:高并发下,服务器处理大量消息和ACK可能导致性能瓶颈。
3. 优化方案
- 客户端去重:Client-B基于
msg_id
去重,确保用户无感知。 - 服务端无状态:重传逻辑由客户端负责,减轻服务器负担。
- 群聊优化:
- 群消息视为多条个人消息,需解决时序一致性(通过
msg_id
排序)和瞬时负载(通过集群化)。 - 保持群消息与个人消息一致的投递模型。
- 群消息视为多条个人消息,需解决时序一致性(通过
4. 总结
在线消息投递依赖超时、重传、确认、去重机制,六个报文确保可靠性。群聊场景需关注消息扩散和负载,推荐集群化部署。
三、离线消息的可靠投递
1. 核心机制
离线消息针对接收方(Client-B)不在线的场景,消息存储在服务器,待Client-B上线后拉取。
- 发送流程:
- Client-A发送消息。
- 服务器检测Client-B离线,存储消息至数据库(DB)。
- 服务器返回“发送成功”ACK给Client-A(伪应答)。
- 离线消息表:
- 字段:
receiver_uid
、msg_id
、send_time
、sender_uid
、msg_type
、msg_content
。 - 查询:基于
receiver_uid
和sender_uid
检索。
- 字段:
- 拉取流程:
- Client-B上线,发送拉取请求(offline:R)。
- 服务器查询DB并删除消息。
- 服务器返回消息(offline:A)。
- 应用层ACK:Client-B收到消息后发送ACK,服务器确认后删除消息。
- 去重:Client-B根据
msg_id
去重。
2. 存在问题
- 交互次数多:逐个好友拉取消息,增加流量和电量消耗。
- 大消息量卡顿:一次性拉取大量消息导致请求包过大。
- 消息丢失:拉取后未ACK(如客户端崩溃),服务器已删除消息。
- 重复拉取:未ACK的消息可能被重复拉取。
3. 优化方案
- 减少交互:
- 方案1:先拉取消息计数,按需拉取。
- 方案2:一次性拉取所有好友消息,客户端按
sender_uid
处理,仅一次交互。
- 分页拉取:分批拉取(如按时间或数量),避免卡顿。
- 防止丢失:
- 引入应用层ACK,仅ACK后删除DB消息。
- ACK可通过实时通道或REST接口,REST更简单。
- 去重优化:Client-B基于
msg_id
去重。 - 减少ACK:
- 下一页拉取请求作为上一页ACK。
- 最后一页返回空结果,仅需一次额外ACK。
4. 总结
离线消息通过存储、拉取、ACK、去重确保可靠性。优化重点为减少交互(一次性拉取)、分页处理(避免卡顿)、ACK优化(防止丢失)和高效去重。主流IM(如微信)多采用方案2,结合分页和ACK合并。
四、群聊消息不丢不重的实现思路
1. 投递流程
- 在线推送:
- 发送者发送群消息至服务器。
- 服务器查询群成员列表,实时推送给在线用户。
- 离线存储与拉取:
- 离线用户消息存储在服务器,上线后拉取。
- 拉取后删除离线消息记录。
2. 存储优化
- 群消息表:
- 字段:
group_id
、sender_id
、time
、msg_id
、msg_detail
。 - 存储完整消息内容。
- 字段:
- 离线消息优化:
- 离线消息表仅存
msg_id
,通过关联查询获取内容,减少冗余。
- 离线消息表仅存
- 进一步优化:
- 移除离线消息表,改用群成员表记录
last_ack_msg_id
或last_ack_msg_time
。 - 上线时根据ACK点拉取后续消息,降低存储压力。
- 移除离线消息表,改用群成员表记录
3. 应用层ACK
- 在线消息:
- 消息存入群消息表,为所有用户记录
msg_id
至离线消息表。 - 在线用户收到消息后发送ACK,服务器删除对应
msg_id
。
- 消息存入群消息表,为所有用户记录
- 离线消息:
- 拉取消息后发送ACK,服务器删除
msg_id
。
- 拉取消息后发送ACK,服务器删除
- 作用:确保消息送达,防止丢失。
4. 批量ACK
- 方式:
- 按消息数量:每N条消息发送一次ACK。
- 按时间间隔:每T秒发送一次ACK。
- 效果:降低ACK请求量,缓解服务器压力。
5. 去重机制
- 客户端去重:基于
msg_id
过滤重复消息,确保用户无感知。 - 场景:
- 批量ACK未完成导致重复拉取。
- 网络抖动或重试导致重复发送。
6. 分页拉取
- 实现:按需分页拉取(如每次50条),用户滑动界面触发下一页。
- 优点:减少网络和数据库压力,提升响应速度。
7. 核心步骤
- 存储:群消息表存消息内容,群成员表记
last_ack_msg_id
,避免冗余。 - 投递:应用层ACK确保在线和离线消息送达。
- 优化:批量ACK、分页拉取降低压力。
- 去重:客户端基于
msg_id
去重。 - 扩展性:支持分表、分库应对大规模场景。
8. 注意事项
- 权衡:允许“万有一失”,优先性能和体验。
- 适配:根据业务决定单聊/群聊存储方式,群成员表按群记录
last_ack_msg_id
。
五、在线与离线消息对比
1. 共性
- 目标:消息不丢不重。
- 技术:
- 应用层ACK确保送达。
- 超时重传应对丢失。
msg_id
去重。
- 优化:省流量、省电量。
- 参考:微信、QQ案例,MobileIMSDK验证。
2. 差异
方面 | 在线实时消息 | 离线消息 |
---|---|---|
场景 | 双方在线 | 接收方不在线 |
投递方式 | 实时推送(msg:N) | 存储后拉取(offline:R/A) |
报文数量 | 6个(msg:R/A/N + ack:R/A/N) | 4个(offline:R/A + ACK) |
存储需求 | 无需持久化 | 需数据库存储 |
问题 | 报文丢失、重复 | 交互多、卡顿、丢失、重复 |
优化 | 去重、无状态、群聊负载 | 减少交互、分页、ACK优化 |
六、实际应用建议
- 技术选型:
- 在线消息:TCP协议,结合ACK和重传。
- 离线消息:HTTP REST拉取,配合数据库和分页。
- 性能优化:
- 集群化部署应对高并发。
- 客户端负责去重,服务端保持无状态。
- 用户体验:
- 提供“发送失败”提示,支持手动重传。
- 分页拉取和按需加载,降低资源消耗。
- 参考框架:
- MobileIMSDK:轻量级,适合验证。
- 微信/QQ:参考去重、ACK合并、集群化。
七、总结
IM消息投递通过应用层ACK、超时重传、去重机制和交互优化,实现高可靠性和高效率。在线消息依赖六个报文流程,离线消息通过存储和拉取解决,群聊消息则结合存储优化、批量ACK和分页拉取应对复杂场景。开发者可参考MobileIMSDK和主流IM实践,根据业务需求优化方案。
如需深入探讨群聊时序一致性、分布式扩展或其他IM技术,可参考MobileIMSDK源码或相关文章。