Skip to content

IM消息送达保证机制

一、总体概述

即时通讯(IM)系统的消息投递机制旨在确保消息不丢失不重复(即QoS机制),同时在移动网络环境下兼顾高效性(省流量、省电量)。两篇文章分别针对在线实时消息离线消息场景,结合微信、QQ等案例和MobileIMSDK框架,阐述了实现原理、问题及优化方案。

核心目标

  • 可靠性:消息从发送方到接收方无丢失。
  • 去重性:接收方不收到重复消息。
  • 高效性:优化移动端体验,降低资源消耗。

二、在线实时消息的可靠投递

1. 核心机制

在线实时消息针对发送方(Client-A)和接收方(Client-B)均在线的场景,通过应用层确认机制确保可靠投递。

  • 报文类型
    • 请求报文(R):客户端发送给服务器。
    • 应答报文(A):服务器对请求的响应。
    • 通知报文(N):服务器推送给客户端。
  • 投递流程(六个报文):
    1. Client-A发送消息请求(msg:R)给服务器。
    2. 服务器返回确认(msg:A)。
    3. 服务器推送消息通知(msg:N)给Client-B。
    4. Client-B发送确认请求(ack:R)给服务器。
    5. 服务器返回确认(ack:A)。
    6. 服务器推送确认通知(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上线后拉取。

  • 发送流程
    1. Client-A发送消息。
    2. 服务器检测Client-B离线,存储消息至数据库(DB)。
    3. 服务器返回“发送成功”ACK给Client-A(伪应答)。
  • 离线消息表
    • 字段:receiver_uidmsg_idsend_timesender_uidmsg_typemsg_content
    • 查询:基于receiver_uidsender_uid检索。
  • 拉取流程
    1. Client-B上线,发送拉取请求(offline:R)。
    2. 服务器查询DB并删除消息。
    3. 服务器返回消息(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_idsender_idtimemsg_idmsg_detail
    • 存储完整消息内容。
  • 离线消息优化
    • 离线消息表仅存msg_id,通过关联查询获取内容,减少冗余。
  • 进一步优化
    • 移除离线消息表,改用群成员表记录last_ack_msg_idlast_ack_msg_time
    • 上线时根据ACK点拉取后续消息,降低存储压力。

3. 应用层ACK

  • 在线消息
    • 消息存入群消息表,为所有用户记录msg_id至离线消息表。
    • 在线用户收到消息后发送ACK,服务器删除对应msg_id
  • 离线消息
    • 拉取消息后发送ACK,服务器删除msg_id
  • 作用:确保消息送达,防止丢失。

4. 批量ACK

  • 方式
    • 按消息数量:每N条消息发送一次ACK。
    • 按时间间隔:每T秒发送一次ACK。
  • 效果:降低ACK请求量,缓解服务器压力。

5. 去重机制

  • 客户端去重:基于msg_id过滤重复消息,确保用户无感知。
  • 场景
    • 批量ACK未完成导致重复拉取。
    • 网络抖动或重试导致重复发送。

6. 分页拉取

  • 实现:按需分页拉取(如每次50条),用户滑动界面触发下一页。
  • 优点:减少网络和数据库压力,提升响应速度。

7. 核心步骤

  1. 存储:群消息表存消息内容,群成员表记last_ack_msg_id,避免冗余。
  2. 投递:应用层ACK确保在线和离线消息送达。
  3. 优化:批量ACK、分页拉取降低压力。
  4. 去重:客户端基于msg_id去重。
  5. 扩展性:支持分表、分库应对大规模场景。

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优化

六、实际应用建议

  1. 技术选型
    • 在线消息:TCP协议,结合ACK和重传。
    • 离线消息:HTTP REST拉取,配合数据库和分页。
  2. 性能优化
    • 集群化部署应对高并发。
    • 客户端负责去重,服务端保持无状态。
  3. 用户体验
    • 提供“发送失败”提示,支持手动重传。
    • 分页拉取和按需加载,降低资源消耗。
  4. 参考框架
    • MobileIMSDK:轻量级,适合验证。
    • 微信/QQ:参考去重、ACK合并、集群化。

七、总结

IM消息投递通过应用层ACK超时重传去重机制交互优化,实现高可靠性高效率。在线消息依赖六个报文流程,离线消息通过存储和拉取解决,群聊消息则结合存储优化、批量ACK和分页拉取应对复杂场景。开发者可参考MobileIMSDK和主流IM实践,根据业务需求优化方案。

如需深入探讨群聊时序一致性、分布式扩展或其他IM技术,可参考MobileIMSDK源码或相关文章。