[即时通讯]千人群组消息管理

消息时序

理想状态下,客户端和服务端数据是一致的。实际情况,涉及到用户上线或下线。(详见下图)

  1. 用户在线:服务实时发送消息。
  2. 用户离线:服务保存消息;用户重新上线后,向服务获取离线消息。

消息时序


  1. 群组离线消息数据(分页获取)。

    如上图:client 每条消息都是有时序的,像链表一样,串联起来,每个 node 都可以通过 next 指向上一条消息:

    1. 如果上一条消息 msg_id 是 0,说明当前结点是第一条消息(如上图 msg_id == 1 的消息)。
    2. 如果上一条消息 msg_id 不是 0,且消息存在于本地,那么消息是连续的,不需要向服务同步(如上图 msg_id == 2 的消息)。
    3. 如果上一条消息 msg_id 不是 0,但本地消息不存在,那么需要向服务器获取。(如上图 msg_id == 9 的消息)。

    终端通过消息链表方式的检查,很容易确认是否需要向服务同步数据。

  2. 群组未读消息总条数。

    从 client 的缓存中提取最新(lastest)的 msg_id,对应消息体有 recv_time。

    服务端消息的时序通过 redis 的 sortset 存储的:

    key: group_id, score: recv_time, value: msg_id

    redis 的 sortset 结构,很容易通过一个 score 获取一个区间的数据总数。


redis 设计

  1. sortset 存储存储消息时序

    key: group_id, score: recv_time, value: msg_id

  2. string 存储消息体

    key: msg_id, value: msg_body

    因为消息体数量较多,而且活跃时间比较短(因为大部分用户只关心最近接收的消息),所以把它独立出来。便于 timeout 后 redis 能删除节省内存。

  3. set 存储未读消息对象

    key: uid, member:group_id/send_uid

    每个用户都可能有 N 个群组,N 个好友。用户重新上线后,不可能遍历所有好友或群组对象。所以服务在处理离线消息时,需要记录未读消息对象。


database 设计

  1. 群组和群组成员关系

    group_id, uid

  2. 消息结构

    msg_id, group_id, send_uid, recv_uid, recv_time, msg_body


服务存储架构

im 即时通讯,服务是读多写少类型。服务端有三层存储(如下图),通过热点数据的缓存,让服务高效读取。

  1. msg server 服务进程内存 session 缓存热点数据。

缓存当前活跃的数据:头像信息,用户名称,消息实体等数据,缓存一般 5 - 30 分钟,根据具体的业务需要

  1. redis 第二层缓存热点数据。

缓存大量的热点数据,减少对 db 的访问频率,缓存时间相对较长,几个月不等。

  1. database 数据落地。

存储架构


数据读写时序

写数据

写逻辑

读数据

读逻辑


总结

基于以上分析,群组消息,每个用户发送的消息,不需要针对每个群组成员都存一条到 database,否则千人群组,每个成员存一条数据就是一千条,那么消息的存储就是个大坑。每个用户发送消息 database 只需要存一条即可。通过多级缓存的架构,服务的性能一般体量的消息实时通讯是没有问题的。当然这里面还有很多细节问题需要在实际的业务场景中调优。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章