即时通讯系统相关基础

1. IM 常见术语

  1. 用户:系统的使用者;
  2. 消息:指用户的沟通内容;
    • 文本消息
    • 表情消息
    • 图片消息
    • 视频消息
    • 文件消息
  3. 会话:指两个用户之间因聊天而建立起的关联;
  4. 群:指多个用户之间因聊天而建立起的关联;
  5. 终端:用户使用 IM 系统的机器
    • 移动端
    • WEB 端
    • Windows 端
  6. 未读数:用户还没读的消息数量;
  7. 用户状态:指用户当前是在线、离线状态;
  8. 关系链:指用户与用户之间的关系;
    • 单向的好友关系
    • 双向的好友关系
  9. 单聊:一对一聊天;
  10. 群聊:多人聊天;
  11. 信箱:收发消息的队列

2. 读扩散和写扩散

2.1. 读扩散

A 与每个聊天的人跟群都有一个 Timeline,A 再查看聊天信息的时候需要读取所有有新消息的信箱。

读扩散优点:

  1. 写操作(发消息)很轻量,不管是单聊还是群聊,只需要往相应的信箱写一次就好了;
  2. 每一个 Timeline 天然就是两个人的聊天记录,可以方便查看聊天记录跟进行聊天记录的搜索。

读扩散缺点:

  • 读操作(读消息)很重,在复杂业务下,一条读扩散消息需要复杂的逻辑才能扩散成目标消息。

2.2. 写扩散

在写扩散中,每个人都只从自己的信箱读取消息。

写操作(发消息)的时候,对于单聊跟群聊的处理如下:

  1. 单聊:往自己的信箱跟对方的信箱都写一份消息,同时,如果需要查看两个人的聊天历史记录的话还需要再写一份;
  2. 群聊:需要往所有群成员的信箱都写一份消息,同时,如果需要查看群的聊天历史记录的话还需要再写一份,写扩散对于群聊来说大大放大了写操作。

写扩散优点:

  1. 读操作很轻量;
  2. 可以方便地做消息的多终端同步。

写扩散缺点:

  • 写操作很重,尤其对于群聊来说(群聊人数为 N,一条消息要扩散写成 N-1 条目标消息)。

3. 唯一 ID 方案

在 IM 系统中需要唯一 ID 的地方主要是:

  1. 聊天会话 ID;

  2. 聊天消息 ID;

3.1. 消息ID

3.1.1. 消息 ID 递增还是不递增?

不递增的情况会造成:

  1. 使用字符串,浪费存储空间,而且不能利用存储引擎的特性让相邻的消息存储在一起,降低消息的写入跟性能;
  2. 使用数字,但数字随机,也不能利用存储引擎的特性让相邻的消息存储在一起,会加大随机 IO,降低性能,而且随机的 ID 不好保证 ID 的唯一性。

所以消息 ID 最好是递增的。

3.1.2. 全局递增 VS 用户级别递增 VS 会话级别递增

  • 全局递增:指消息 ID 在整个 IM 系统随时间的推移是递增的。全局递增的话一般可以使用 SnowflakeSnowflakeworker 级别递增),如果此时系统为读扩散,为了防止消息丢失,那每一条消息就只能带上上一条的消息的 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

生成方案:

  1. 拼接 from_user_idto_user_id,如果都是 32 位整型数据,可拼接成 64 位的会话 ID,32 位整型 ID 看起来能容纳 21 亿用户,但用户 ID 不连续,后续拓展难度大,且完全依赖用户 ID,不可取;
  2. 会话 ID 全局递增,映射表:保存 from_user_idto_user_idconversation_id 的关系。

4. 推模式 VS 拉模式 VS 推拉结合模式

在 IM 系统中,新消息的获取通常会有三种可能的做法:

  1. 推模式:有新消息是服务器主动推给所有端;

  2. 拉模式:由前端主动发起拉取消息的请求,为了保证消息的实时性,通常采用推模式,拉模式一般用于获取历史消息;

  3. 推拉结合模式:有新消息时,服务器会先推一个有新消息的通知给前端,前端接收到通知后就向服务器拉取消息。

4.1. 推模式

如图所示,用户发的消息经过服务器存储等操作后推给接收方的所有端。

消息丢失风险:

  • 当用户伪在线(若推送服务基于长连接,长连接可能已经断开,用户已经离线,但一般需要经过一个心跳周期后服务器才能感知到)时,单纯使用推模式,是有可能会丢失消息的(使用推拉结合模式可解决)。

4.2. 拉模式

由用户主动发起的拉消息,一方面对服务器的压力大,另一方面则是无效拉取操作,浪费系统资源。

4.3. 推拉结合模式

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

评论