即时通讯系统相关基础
1. IM 常见术语
- 用户:系统的使用者;
- 消息:指用户的沟通内容;
- 文本消息
- 表情消息
- 图片消息
- 视频消息
- 文件消息
- 会话:指两个用户之间因聊天而建立起的关联;
- 群:指多个用户之间因聊天而建立起的关联;
- 终端:用户使用 IM 系统的机器
- 移动端
- WEB 端
- Windows 端
- 未读数:用户还没读的消息数量;
- 用户状态:指用户当前是在线、离线状态;
- 关系链:指用户与用户之间的关系;
- 单向的好友关系
- 双向的好友关系
- 单聊:一对一聊天;
- 群聊:多人聊天;
- 信箱:收发消息的队列
2. 读扩散和写扩散
2.1. 读扩散

A 与每个聊天的人跟群都有一个 Timeline,A 再查看聊天信息的时候需要读取所有有新消息的信箱。
读扩散优点:
- 写操作(发消息)很轻量,不管是单聊还是群聊,只需要往相应的信箱写一次就好了;
- 每一个 Timeline 天然就是两个人的聊天记录,可以方便查看聊天记录跟进行聊天记录的搜索。
读扩散缺点:
- 读操作(读消息)很重,在复杂业务下,一条读扩散消息需要复杂的逻辑才能扩散成目标消息。
2.2. 写扩散

在写扩散中,每个人都只从自己的信箱读取消息。
写操作(发消息)的时候,对于单聊跟群聊的处理如下:
- 单聊:往自己的信箱跟对方的信箱都写一份消息,同时,如果需要查看两个人的聊天历史记录的话还需要再写一份;
- 群聊:需要往所有群成员的信箱都写一份消息,同时,如果需要查看群的聊天历史记录的话还需要再写一份,写扩散对于群聊来说大大放大了写操作。
写扩散优点:
- 读操作很轻量;
- 可以方便地做消息的多终端同步。
写扩散缺点:
- 写操作很重,尤其对于群聊来说(群聊人数为 N,一条消息要扩散写成 N-1 条目标消息)。
3. 唯一 ID 方案
在 IM 系统中需要唯一 ID 的地方主要是:
聊天会话 ID;
聊天消息 ID;
3.1. 消息ID
3.1.1. 消息 ID 递增还是不递增?
不递增的情况会造成:
- 使用字符串,浪费存储空间,而且不能利用存储引擎的特性让相邻的消息存储在一起,降低消息的写入跟性能;
- 使用数字,但数字随机,也不能利用存储引擎的特性让相邻的消息存储在一起,会加大随机 IO,降低性能,而且随机的 ID 不好保证 ID 的唯一性。
所以消息 ID 最好是递增的。
3.1.2. 全局递增 VS 用户级别递增 VS 会话级别递增
- 全局递增:指消息 ID 在整个 IM 系统随时间的推移是递增的。全局递增的话一般可以使用 Snowflake(Snowflake 是 worker 级别递增),如果此时系统为读扩散,为了防止消息丢失,那每一条消息就只能带上上一条的消息的 ID,前端根据上一条消息判断是否有丢失消息,有消息丢失的话需要重新拉一次;
- 用户级别递增:指消息 ID 只保证在单个用户中是递增的,不同用户之间不影响并且可能重复。如果系统是写扩散的话,Timeline 时间线 ID 跟消息 ID 需要分开设计,信箱时间线 ID 用户用户级别递增,消息 ID 全局递增;如果是读扩散,使用用户级别递增的必要性不是很大;
- 会话级别递增:指消息 ID 只保证在单个会话中是递增的,不同会话直接按不影响并且可能重复。
3.1.3. 连续递增 VS 单调递增
连续递增是指 ID 的序列是连续的,比如 1,2,3,4,...,n;而单调递增指只要保证后面生成的 ID,比前面生成的 ID 大就可以了,不需要连续。
- 如果是读扩散且用户级别递增或会话级别递增,消息 ID 使用连续递增就是很不错的方案,每次拉取消息都带上前一条消息的 ID,就可以判断消息是否丢失。
3.1.4. 小结
- 写扩散:Timeline 时间线 ID 使用用户级别递增,消息 ID 全局递增;
- 写扩散:消息 ID 可以使用会话级别递增并且最好是连续递增。
3.2. 会话 ID
生成方案:
- 拼接 from_user_id 和 to_user_id,如果都是 32 位整型数据,可拼接成 64 位的会话 ID,32 位整型 ID 看起来能容纳 21 亿用户,但用户 ID 不连续,后续拓展难度大,且完全依赖用户 ID,不可取;
- 会话 ID 全局递增,映射表:保存 from_user_id、to_user_id 跟 conversation_id 的关系。
4. 推模式 VS 拉模式 VS 推拉结合模式
在 IM 系统中,新消息的获取通常会有三种可能的做法:
推模式:有新消息是服务器主动推给所有端;
拉模式:由前端主动发起拉取消息的请求,为了保证消息的实时性,通常采用推模式,拉模式一般用于获取历史消息;
推拉结合模式:有新消息时,服务器会先推一个有新消息的通知给前端,前端接收到通知后就向服务器拉取消息。
4.1. 推模式

如图所示,用户发的消息经过服务器存储等操作后推给接收方的所有端。
消息丢失风险:
- 当用户伪在线(若推送服务基于长连接,长连接可能已经断开,用户已经离线,但一般需要经过一个心跳周期后服务器才能感知到)时,单纯使用推模式,是有可能会丢失消息的(使用推拉结合模式可解决)。
4.2. 拉模式
由用户主动发起的拉消息,一方面对服务器的压力大,另一方面则是无效拉取操作,浪费系统资源。
4.3. 推拉结合模式

- 用户发新消息时,服务器推送一个通知;
- 前端请求最新消息列表,为了防止有消息丢失,可以引入 ACK 确认机制。
评论