暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

IM系统雏形(一)

AggrxTech 2021-09-14
785

    在移动互联网时代,IM产品几乎生活和工作的必需品:
    比如QQ、微信、钉钉等。

    本文从最基础设计要点角度,描述一个简单的IM服务雏形
    重点介绍两部分的核心设计: 消息存储、推送服务

    后续会在此基础上,增加更多的深入场景探讨。

需求

基本功能

  • 发消息

    • 包含单聊群聊

    • 存储用户所有消息 

  • 收消息

    • 消息是有序

    • 接收消息的用户当前状态:在线和离线

    • 推送消息

    • 离线用户打开APP获取未读消息

  • 历史消息

    • 换手机或重新安装后能获取历史消息

关键指标

  • 实时性

    • 在线:实时达到

    • 离线:及时通知

  • 有序性

    • 按(同一基准)时间顺序展示,不乱

  • 高可靠

    • 消息不丢

    • 消息不重复

动手设计

根据需求分析,整体服务结构是这样的



说明:

  • Client

    主要考虑iOS和Android客户端

    web不常用且区别不大(离线推送部分)

  • 消息分发

    快速响应请求,异步处理消息

  • 用户状态

    用户长链接在线状态,决定了推送方式:离线推送 or 在线推送

  • 在线推送

    使用长链接直接和APP交互来推送消息

  • 离线推送

    iOS使用官方APNS

    Android使用第三方推送 或 自研集成各手机厂商推送服务

  • 未读消息

    未推送给用户(有限条)的消息

  • 用户消息

    存储用户所有的消息列表,数据为 推送消息体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和钉钉都做了哪些针对性优化?
……


文章转载自AggrxTech,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论