
在移动互联网时代,IM产品几乎生活和工作的必需品:
比如QQ、微信、钉钉等。
本文从最基础设计要点角度,描述一个简单的IM服务雏形
重点介绍两部分的核心设计: 消息存储、推送服务
后续会在此基础上,增加更多的深入场景探讨。
需求
基本功能
发消息
包含单聊和群聊
存储用户所有消息
收消息
消息是有序的
接收消息的用户当前状态:在线和离线
推送消息
离线用户打开APP获取未读消息
历史消息
换手机或重新安装后能获取历史消息
关键指标
实时性
在线:实时达到
离线:及时通知
有序性
按(同一基准)时间顺序展示,不乱
高可靠
消息不丢
消息不重复
动手设计
根据需求分析,整体服务结构是这样的

说明:
Client主要考虑iOS和Android客户端web不常用且区别不大(离线推送部分)消息分发快速响应请求,异步处理消息用户状态用户长链接在线状态,决定了推送方式:离线推送 or 在线推送在线推送使用长链接直接和APP交互来推送消息离线推送iOS使用官方APNSAndroid使用第三方推送 或 自研集成各手机厂商推送服务未读消息未推送给用户(有限条)的消息用户消息存储用户所有的消息列表,数据为 推送消息体ID群消息存储群内的所有消息用于优化拉取消息(后续讨论)用户消息体存储由谁发给谁的信息,即用户消息和群消息消息体消息基本信息,时间、内容、类型等ID生成服务用来生成各种需求的ID服务,以保证消息全局唯一且有序
消息服务
用户消息列表
一条消息需要包含:发送者、接收者、发送时间、发送内容
那么把用户消息列表的元素定义为:userMsg

在存储用户消息列表时候,一般有两个方式 读扩散 和 写扩散
以下面的消息发送场景为例,A、B、C、D四个用户依次发消息
A => B
B => A
A => B
C => A
A => C
A => D
读扩散
A与每个聊天的人(群)都有一个消息列表,在获取聊天信息的时候需要读取所有消息列表

优点
发消息流程简单,只需要往对应的人(群)的队列写入一个消息即可
和每个人(群)的消息列表很直观,查看和检索和某个人的聊天记录很简单
对读群消息优化比较简单(本文暂不深入展开)
群消息,所有参与者使用一份引用
缺点
读消息会很复杂,有N个人(群)有新消息,那就读N个消息列表
不方便”分页“获取
这里暂不深入讨论,但是参照上面的数据结构图,比较容易发现
要按顺序,分批读取消息,每次读'最早'的m条,如不引入更复杂的数据结构
复杂度是较高的
写扩散
每个人都有一个自己的消息列表,在获取聊天消息的时候只需读取自己的一个消息列表。
优点
读消息简单,且方便做“分页”获取消息
方便做多端同步
缺点
发消息流程很重,单人聊天的消息多存一份
群聊需要扩散N-1条消息!!!N为群员数量
一般选择:写扩散
针对IM这种应用场景,消息系统通常会选择写扩散这种消息同步模式
因为在IM场景下,一条消息只会产生一次,但是会被读取多次,是典型的读多写少的场景
读写不平衡,对中间消息组件的复杂度会提出较高的要求。
若使用读扩散同步模式,整个系统的读写比例会被放大到100:1。
因次,通常会应用写扩散这种同步模式,来平衡读和写,将100:1的读写比例平衡到30:30(万人大群通常这样处理)
推送服务
推送消息三种方式
拉取(pull)方式
即轮询,客户端不断的查询服务器,检索新内容;
该方式在没有消息时浪费网络资源(费电、费流量),消息密集时获取消息延迟
一般适合web端对实时性要求不高的场景
推送(push)方式
客户端和服务器之间维持一个长连接,服务器向客户端push
该方式适合在线用户实时推送消息;
推拉结合模式
有新消息时服务器会先推一个有新消息的通知给客户端
客户端接收到通知后就向服务器拉取消息
该方式适合修补消息和离线用户获取历史消息;
推送模型
用户A(在线)与用户B(离线到在线)发送消息来详细说明整个消息推送的过程

说明:
1. 未读可以在消息列表中用标记来做,这样就增加了数据库的压力,所以建议把(有限的)未读消息放入redis中
2. 橘色部分,如果没有ACK,在拉取过程中断网等原因会导致消息丢失
后续
1、加入群聊要怎么做?
2、用户多个端登录怎么保证各端消息同步?
3、怎么优化弱网情况,保证消息不丢和严格有序?
4、上线后拉取消息过多,如何优化?
5、已读回执要怎么做?
6、微信、QQ和钉钉都做了哪些针对性优化?
……




