6

IM群聊消息如此复杂,如何保证不丢不重?

 2 years ago
source link: http://yunxin.163.com/blog/52im-17/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

群聊已经成为主流IM软件的基本功能,不管是QQ群、还是微信群,一个群友在群内发了一条消息,那么对于IM服务器来说需要保证:

  • 在线的群友能第一时间收到消息;
  • 离线的群友能在登陆后收到消息。

由于“消息风暴扩散系数”的存在(概念详见《IM单聊和群聊中的在线状态同步应该用“推”还是“拉”?》),群消息的复杂度要远高于一对一的单聊消息。群消息的实时性、可达性、离线消息是今天将要讨论的核心话题。

2、IM开发干货系列文章(详情参见即时通讯网)

3、常见的群消息流程

开始讲群消息投递流程之前,先介绍两个群业务的核心数据结构:

群成员表:用来描述一个群里有多少成员
t_group_users(group_id, user_id)
群离线消息表:用来描述一个群成员的离线消息
t_offine_msgs(user_id, group_id, sender_id,time, msg_id, msg_detail)

业务场景举例:

  • 1)一个群中有x,A,B,C,D共5个成员,成员x发了一个消息;
  • 2)成员A与B在线,期望实时收到消息;
  • 3)成员C与D离线,期望未来拉取到离线消息。

系统架构简介:

  • 1)客户端:x,A,B,C,D共5个客户端用户;
  • 2)服务端:
      2.1)所有模块与服务抽象为server;
    2.2)所有用户在线状态抽象存储在高可用cache里;
    2.3)所有数据信息,例如群成员、群离线消息抽象存储在db里。

IM群聊消息如此复杂,如何保证不丢不重?_1.jpg

典型群消息投递流程,如上图步骤1-4所述:

  • 步骤1:群消息发送者x向server发出群消息;
  • 步骤2:server去db中查询群中有多少用户(x,A,B,C,D);
  • 步骤3:server去cache中查询这些用户的在线状态;
  • 步骤4:对于群中在线的用户A与B,群消息server进行实时推送;
  • 步骤5:对于群中离线的用户C与D,群消息server进行离线存储。

IM群聊消息如此复杂,如何保证不丢不重?_2.jpg

典型的群离线消息拉取流程,如上图步骤1-3所述:

  • 步骤1:离线消息拉取者C向server拉取群离线消息;
  • 步骤2:server从db中拉取离线消息并返回群用户C;
  • 步骤3:server从db中删除群用户C的群离线消息。

存在的问题:
上述流程是最容易想,也最容易理解的,存在的问题也最显而易见:对于同一份群消息的内容,多个离线用户存储了很多份。假设群中有200个用户离线,离线消息则冗余了200份,这极大的增加了数据库的存储压力。

4、群消息优化1:减少存储量

为了减少离线消息的冗余度,增加一个群消息表,用来存储所有群消息的内容,离线消息表只存储用户的群离线消息msg_id,就能大大的降低数据库的冗余存储量,思路如下。

群消息表:用来存储一个群中所有的消息内容
t_group_msgs(group_id, sender_id, time,msg_id, msg_detail)
群离线消息表:优化后只存储msg_id
t_offine_msgs(user_id, group_id, msg_id)

IM群聊消息如此复杂,如何保证不丢不重?_3.jpg

这样优化后,群在线消息发送就做了一些修改:

  • 步骤3:每次发送在线群消息之前,要先存储群消息的内容;
  • 步骤6:每次存储离线消息时,只存储msg_id,而不用为每个用户存储msg_detail。

IM群聊消息如此复杂,如何保证不丢不重?_4.jpg

拉取离线消息时也做了响应的修改:

  • 步骤1:先拉取所有的离线消息msg_id;
  • 步骤3:再根据msg_id拉取msg_detail;
  • 步骤5:删除离线msg_id。

存在的问题(如同单对单消息的发送一样):

  • 1)在线消息的投递可能出现消息丢失,例如服务器重启,路由器丢包,客户端crash;
  • 2)离线消息的拉取也可能出现消息丢失,原因同上。

需要和单对单消息的可靠投递一样,加入应用层的ACK,才能保证群消息一定到达。

5、群消息优化2:应用层ACK

IM群聊消息如此复杂,如何保证不丢不重?_5.jpg

应用层ACK优化后,群在线消息发送又发生了一些变化:

  • 步骤3:在消息msg_detail存储到群消息表后,不管用户是否在线,都先将msg_id存储到离线消息表里;
  • 步骤6:在线的用户A和B收到群消息后,需要增加一个应用层ACK,来标识消息到达;
  • 步骤7:在线的用户A和B在应用层ACK后,将他们的离线消息msg_id删除掉。

IM群聊消息如此复杂,如何保证不丢不重?_6.jpg

对应到群离线消息的拉取也一样:

  • 步骤1:先拉取msg_id;
  • 步骤3:再拉取msg_detail;
  • 步骤5:最后应用层ACK;
  • 步骤6:server收到应用层ACK才能删除离线消息表里的msg_id。

存在的问题:

  • 1)如果拉取了消息,却没来得及应用层ACK,会收到重复的消息么?
    答案是肯定的,不过可以在客户端去重,对于重复的msg_id,对用户不展现,从而不影响用户体验
  • 2)对于离线的每一条消息,虽然只存储了msg_id,但是每个用户的每一条离线消息都将在数据库中保存一条记录,有没有办法减少离线消息的记录数呢?

6、群消息优化3:离线消息表

其实,对于一个群用户,在ta登出后的离线期间内,肯定是所有的群消息都没有收到的,完全不用对所有的每一条离线消息存储一个离线msg_id,而只需要存储最近一条拉取到的离线消息的time(或者msg_id),下次登录时拉取在那之后的所有群消息即可,而完全没有必要存储每个人未拉取到的离线消息msg_id。

群成员表:用来描述一个群里有多少成员,以及每个成员最后一条ack的群消息的msg_id(或者time
t_group_users(group_id, user_id, last_ack_msg_id(last_ack_msg_time))
群消息表:用来存储一个群中所有的消息内容,不变
t_group_msgs(group_id, sender_id, time,msg_id, msg_detail)
群离线消息表:不再需要了

IM群聊消息如此复杂,如何保证不丢不重?_7.jpg

离线消息表优化后,群在线消息的投递流程:

  • 步骤3:在消息msg_detail存储到群消息表后,不再需要操作离线消息表(优化前需要将msg_id插入离线消息表);
  • 步骤7:在线的用户A和B在应用层ACK后,将last_ack_msg_id更新即可(优化前需要将msg_id从离线消息表删除)。

IM群聊消息如此复杂,如何保证不丢不重?_8.jpg

群离线消息的拉取流程也类似:

  • 步骤1:拉取离线消息;
  • 步骤3:ACK离线消息;
  • 步骤4:更新last_ack_msg_id。

存在的问题:
由于“消息风暴扩散系数”的存在,假设1个群有500个用户,“每条”群消息都会变为500个应用层ACK,将对服务器造成巨大的冲击,有没有办法减少ACK请求量呢?

7、群消息优化4:批量ACK

由于“消息风暴扩散系数”的存在,如果每条群消息都ACK,会给服务器造成巨大的冲击,为了减少ACK请求量,很容易想到的方法是批量ACK。

批量ACK的方式又有两种:

  • 1)每收到N条群消息ACK一次,这样请求量就降低为原来的1/N了;
  • 2)每隔时间间隔T进行一次群消息ACK,也能达到类似的效果。

新的问题:批量ACK有可能导致:还没有来得及ACK群消息,用户就退出了,这样下次登录会拉取到重复的离线消息。
解决方案:msg_id去重,不对用户展现,保证良好的用户体验。

还可能存在的问题:群离线消息过多:拉取过慢。
解决方案:分页拉取(按需拉取),分页拉取的细节在《IM消息送达保证机制实现(下篇):保证离线消息的可靠投递》一章中有详细叙述,此处不再展开。

8、本文小结

群消息还是非常有意思的,可达性、实时性、离线消息、消息风暴扩散等等等等,做个总结:

  • 1)不管是群在线消息,还是群离线消息,应用层的ACK是可达性的保障;
  • 2)群消息只存一份,不用为每个用户存储离线群msg_id,只需存储一个最近ack的群消息id/time;
  • 3)为了减少消息风暴,可以批量ACK;
  • 4)如果收到重复消息,需要msg_id去重,让用户无感知;
  • 5)离线消息过多,可以分页拉取(按需拉取)优化。

(原文链接:http://mp.weixin.qq.com/s?__biz=MjM5ODYxMDA5OQ==&mid=2651959643&idx=1&sn=844afa6a31770fa587474ecd73c3b3b3&chksm=bd2d04878a5a8d91c5c93ad8e85254185c63eb419457efbedcba54a9b8a9053da1e8980a694a&scene=21#wechat_redirect)


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK