本文作者:mingyu

探秘IM系统背后的设计奥秘

探秘IM系统背后的设计奥秘摘要: 今天咱们来一起深入了解一下IM系统那些事儿,看看它背后的服务器端、协议以及存储设计到底是怎么个情况呀。一、服务器端设计大揭秘(一)总体架构那点事儿IM系统的总体架构可是分成了5个层...

00:00

今天咱们来一起深入了解一下IM系统那些事儿,看看它背后的服务器端、协议以及存储设计到底是怎么个情况呀。


一、服务器端设计大揭秘


(一)总体架构那点事儿


IM系统的总体架构可是分成了5个层级哦,就像盖房子一样,每一层都有它独特的作用呢。具体是啥样,大家可以看看下面的图哈。

1.png

1. 方便好用的用户端


咱们先来说说移动端呀,这可是重点关注对象呢,不管你是用IOS系统还是Android系统的手机,都能很好地支持哦。这里面有IM App,还有嵌入消息功能的瓜子App呢,说不定以后还能接入客服系统,让咱们用起来更方便啦。


2. 贴心的用户端API


针对不同的情况,API也设计得很贴心哦。要是基于TCP协议搞开发,不管是IOS还是Android,都能给你提供对应的SDK呢。要是H5页面呀,也有WebSocket接口供你使用哦。


3. 强大的接入层


接入层的任务可不简单呀,它得负责让海量的用户都能顺利连接上系统呢,这就好比是一个超级大门卫,要把大家都迎进来。而且它还要做好攻击防护,就像给系统穿上一层铠甲,保护大家的信息安全。另外呀,它还得把那么多的连接整理一下,变成少量的TCP连接,这样才能和逻辑层好好通讯呢。


4. 智慧的逻辑层


逻辑层可是IM系统功能实现的大脑哦,像单聊(c2c)、上报(c2c)、推送(s2c)、群聊(c2g)这些咱们常用的功能呀,还有离线消息、登录授权、组织机构树等等,它们的核心逻辑可都是由逻辑层负责的呢。


5. 可靠的存储层


存储层就像是一个大仓库,专门用来缓存或者存储IM系统相关的数据哦。这里面包括用户状态及路由信息(先缓存起来方便用哦),消息数据呢,可以用MySQL,也可以选择像MangoDB这样的NoSql数据库,还有文件数据呀,就放在文件服务器里啦。


(二)逻辑结构的细致剖析


1. 核心结构的关联与奥秘

2.png

这部分主要是讲IM系统核心组件之间的关系啦,有个结构图能让大家看得更清楚哦。客户端要和IM服务器进行数据交互,就得先从Iplist服务那里拿到接入层的IP地址(也可以通过域名解析的方式得到哦),然后建立连接(可能是短连接哦),这样就能愉快地和服务器聊天啦。而且呀,业务线服务器也能通过服务器端API和IM服务器搭上线,给客户端推送消息呢。客户端上报给业务服务器的消息呀,IM服务器会通过mq准确地投递给业务服务器哦。


2. tcp接入核心流程全知道


(1)登录授权那些步骤

3.png

①咱们登录的时候呀,客户端要先通过统一登录系统完成登录,这样就能拿到一个token啦,这个token可重要啦,就像是进入系统的钥匙哦。

②然后呢,客户端拿着uid和这个token向msg-gate发起授权验证请求,就好比拿着钥匙去开门,得让人家确认一下这钥匙是不是真的能开这扇门呀。

③msg-gage呢,会同步调用msg-logic的验证接口,让它也来帮忙看看这token合不合法。

④msg-logic就会请求sso系统来验证token的合法性啦,这一道道检查,就是为了保证登录的安全可靠呀。

⑤最后呀,msg-gate得到登录结果后,会设置好session状态,然后把授权结果返回给客户端,这样咱们就顺利登录啦。


(2)登出操作也不复杂

4.png

①当咱们要登出的时候,客户端发起logout请求,msg-gate收到后,就会把对应Peer设置为未登录状态,就好像把门口的牌子从“有人”换成“没人”啦。

②然后Msg-gate还会给客户端一个ack响应,告诉客户端“嘿,我知道你要走啦”。

③最后Msg-gate还会通知msg-logic说用户已经登出了,这样整个系统就都知道你已经下线啦。


(3)踢人流程要清楚哦

5.png

有时候可能会出现这样的情况,咱们在一个设备上请求授权登录的时候,另一个同类型的设备上软件已经开着并且处于登录状态了,这时候系统就得把那个设备踢下线啦。具体怎么做呢?

①前面1 - 5步呀,和登录授权的流程差不多哦,可以去参照一下。

②然后Logic会去检索Redis,看看这个用户是不是在其他地方登录着呢。

③如果在其他地方登录了,那就会发起kickout命令(要是没登录,那这个流程就结束啦)。

④Gate就会向用户发起kickout请求,并且会在短时间内(要保证客户端能收到kickout数据哦)关闭socket连接,这样那个设备就被踢下线啦。


(4)上报数据的来龙去脉

6.png

①客户端要给系统上报数据的时候,就会向gate发送数据啦。

②Gate收到后,会回一个ack包给客户端,就像是在说“嘿,我收到你的数据啦”。

③然后Gate会把数据包传递给logic,让logic来处理这些数据哦。

④logic会根据数据要投递的目的地,选择对应的mq队列进行投递,这样数据就能准确地到达该去的地方啦。

⑤最后业务服务器就能收到这些数据啦,是不是很有条理呀。


(5)推送消息是这样的哦

7.png

①业务线要是想给客户端推送消息,就会调用push数据接口sendMsg哦。

②然后Logic会去redis检索目标用户的状态。要是目标用户不在线呀,可能就会把数据丢弃掉(不过以后也可以根据业务场景来定制化这个逻辑哦);要是用户在线呢,就能查到用户连接的接入层gate啦。

③Logic就会向用户所在的gate发送数据啦。

④Gate收到数据后,就会向用户推送数据啦。(要是用户不在线,Gate还会通知logic说用户不在线哦)

⑤客户端收到数据后,会向gate发送ack反馈,就像是在说“我收到消息啦”。

⑥最后Gate会把ack信息传递给logic层,logic层可以用这些信息来做一些其他的逻辑处理,比如记录日志呀,确认消息送达等等哦。


(6)单对单聊天的详细过程

8.png

①比如说App1要给App2发信息,App1就会向gate1发送信息啦,这信息最后可是要发给App2的哦。

②Gate1收到信息后,会把信息投递给logic,让logic来处理。

③Logic收到信息后,会先把信息存储起来,就像把重要的信件放进邮箱一样。

④存储成功后,logic会向gate1发送ack,告诉gate1“我已经存好啦”。

⑤Gate1收到ack后,会把ack信息发给App1,让App1知道信息已经被处理啦。

⑥然后Logic会去检索redis,查找App2的状态。要是App2未登录,那这个流程就结束啦。

⑦要是App2登录到了gate2,logic就会把消息发往gate2啦。

⑧Gate2收到消息后,会把消息发给App2(要是发现App2不在线,丢弃消息就行啦,不过这种情况概率很低的,而且后面离线消息功能也能保证消息不丢哦)。

⑨App2收到消息后,会向gate2发送ack,告诉gate2“我收到消息啦”。

⑩Gate2收到ack后,会把ack信息发给logic,让logic知道消息已经送达啦。

⑪最后Logic会把消息状态设置为已送达,这样整个单对单聊天的流程就完成啦。


哦,对了,在第6步和第7步之间呀,还会启动一个计时器(可以用DelayedQueue或者哈希环哦,时间设成5秒就行啦),计时器时间到了之后,会去探测这条消息的状态,如果消息没送达,还可以考虑通过APNS、米推、个推这些方式来推送呢。


(7)群聊的独特玩法


群聊可是多人社交的热门项目呀,咱们这里采用的是扩散写(可不是扩散读哦)的方式呢。当一个群友在群内发了一条消息,会怎么样呢?

①在线的群友能第一时间收到消息,就像大家在一个屋子里,有人说话大家都能马上听到一样。

②离线的群友在登录后也能收到消息,这样就不会错过群里的热闹啦。

9.png

不过要知道,因为有“消息风暴扩散系数”的存在,群消息的复杂度可比单对单消息高多了哦。


这里还有几个表要给大家介绍一下呢,群基础表,像im_group_msgs(group_id, group_name,create_user, owner, announcement, create_time),是用来描述一个群的基本信息的;群成员表,im_group_users(group_id, user_id),用来描述一个群里有多少成员;用户接收消息表,im_message_recieve(msg_id,msg_from,msg_to, group_id,msg_seq, msg_content, send_time, msg_type, deliverd, cmd_id),用来描述一个用户收到的所有群消息(和单对单消息表是同一个表哦);用户发送消息表,im_message_send (msg_id,msg_from,msg_to, group_id,msg_seq, msg_content, send_time, msg_type, cmd_id),用来描述一个用户发送了哪些消息。


举个例子吧,假如一个群里有x,A,B,C,D共5个成员,成员x发了一个消息,成员A与B在线,期望实时收到消息,成员C与D离线,期望未来拉取到离线消息。那群聊的流程是怎样的呢?

①X会向gate发送信息(这信息最终要发给这个群,A、B在线)。

②Gate收到信息后,会把信息发给logic。

③Logic会把消息存储到im_message_send表,按照msg_from水平分库。

④然后会回ack,回ack哦。

⑤Logic会检索数据库(得用缓存哦),获取群成员列表。

⑥然后会存储每个用户的消息数据(用户视图),按照msg_to水平分库(并发、批量写入)。

⑦接着会查询用户在线状态及位置。

⑧Logic会向gate投递消息。

⑨Gate会向用户投递消息。

⑩App会返回收到消息的ack信息。

⑪Gate会向logic传递ack信息。

⑫会向缓存(Hash)中更新收到ack的时间。然后通过一个定时任务,每隔一定时间,将数据更新到数据库(注意只要写入时间段内有变化的数据就好啦)。


(8)拉取离线消息的步骤

10.png

咱们把gate和logic合并成im-server来讲拉取离线消息的流程哦,大家可以看看下面的图呢。

①当App端登录成功后(或者是业务触发拉取离线消息),会向IM系统发起拉离线消息请求,要传3个主要参数哦,uid用来表明用户;msgid用来表明当前收到的最大消息id(要是没收到过消息,或者拿不到最大消息id那就msgid=0);size表示每次拉取条数(这个值也可以由服务器端控制哦)。

②假如msgid==0,那就什么都不做啦(可以看看第6步骤哦)。

③Im-server会查询用户前10条离线消息。

④然后把离线消息推给用户。假如这10条离线消息最大msgid=110。

⑤App收到数据后,判断得到的数据不为空(这说明可能没有拉完离线消息,不用<10条做判断拉完条件,因为服务端需要下下次拉离线的请求来确定这次数据已送达),就会继续发起拉取操作。Msgid=110(取得到的离线消息中最大的msgid)。

⑥Im-server会删除该用户msgid<110的离线消息(或者标记为已送达)。

⑦然后查询msgid>110的前10条离线数据。

⑧再把数据返回给App。

……

N-1、查询msgid>140的离线数据,0条(没有离线数据了)。

N 、将数据返回App,App判断拉取到0条数据,结束离线拉取过程。


(9)PUSH机制的小秘密


ISO系统采用APNS来进行推送哦,而Android呢,通过真后台保活,还增加了米推、个推这些方式呢。基本思路就是先推送提示信息,然后App通过拉离线消息来获得真实消息哦。另外还有相关文档对这个问题有更详细的说明呢。


二、协议设计那些事儿


(一)TCP数据协议是啥样

11.png

TCP的数据协议包括header和body两部分哦,就像一个包裹,外面是header,里面是body呢。具体是什么样,大家可以看看下面的图呀。消息头总共20个字节,具体信息也有个表可以看哦,也是在下面的图里呢。

12.png

(二)TCP消息体设计有讲究


消息体协议采用的是ProtocolBuffer(谷歌)协议,版本3.0.0哦,这个协议在序列化效率、压缩、可扩展方面都有很不错的优势呢。下面给大家说说主要流程涉及的协议:


1. 认证(auth)

13.png


2. 登出(logout)

14.png


3. 踢人(kickout)

15.png


4. 心跳(keepalive,noop)

16.png


5. 单对单聊天(c2c)

17.png


6. 群聊(c2g)

18.png


7. 拉离线(pull)

19.png


8. 控制类(ctrl)

20.png


三、存储设计的门道


(一)MySQL数据库的那些特点


MySQL数据库采用utf8mb4编码格式哦,这样就能很好地处理emoji字符啦,是不是很贴心呢。

23.png

1. 主要表结构看一看


(1)发送消息表

这个表是用来保存某个用户发送了哪些消息的,这样就能复现用户聊天场景啦,比如说实现消息漫游功能就需要它哦,具体样子可以看看相关配图。


(2)推送消息表

它是用来保存某个用户收到了哪些消息的,同样可以看看相关配图哦。

24.png

(3)群相关表

群基本信息表,相关配图能让你知道它长啥样哦。

群用户关系表,也是看相关配图就能明白啦。

25.png

2. 水平分库的情况

相关配图能让你了解水平分库是怎么回事哦。


(二)Redis缓存的作用不小


1. 用户状态及路由信息的缓存

Redis缓存是以uid为key的哦,通过它可以检索channel(socketid),last_packet_time等信息呢。在Gate层,session是以channel(socketed)为key的,可以检索uid,及其他信息哦。它们之间的交互接口是这样的:gate->logic,通过将channel转换为uid作为key;logic->gate,将uid转换为channel作为key。

26.png

2. 其他缓存信息

其他缓存信息就可以根据实际情况自己安排怎么存啦,比较灵活哦。


(三)文件及图片存储的方式

采用商用云存储哦,这样既方便又安全呢。

27.png

(四)数据归档的选择

可以考虑采用HBase、HDFS作为数据归档的方式哦,或者也可以选择相关云存储服务呢。


好啦,今天关于IM系统的设计就给大家讲到这里啦,希望大家对IM系统背后的这些设计有了更深入的了解哦!


(注:文中提到的一些安全部分及其他非核心功能在此省略未述哦。)


阅读
分享

发表评论

快捷回复:

验证码

评论列表 (暂无评论,38人围观)参与讨论

还没有评论,来说两句吧...