IM专题:分层架构IM系统(12)—消息收发逻辑实现

“消息收发” 是 IM 系统最最核心的业务逻辑模块,本篇文章是整个【IM专题】的核心!

IM,即 “即时通讯”,要求消息具备 “及时性” 和 “可靠性”:

及时性,要求消息的收发需要很低的延时,在线双方通过消息交流时,没有明显的滞后感。

可靠性,要求消息不能丢失;对于消息发送方来说,只要消息发送成功了,消息就会一直存在服务端,不会丢失(除非因产品策略,删除久远的历史消息);对于服务端来说,只要接收方在线,一定会把消息送达到接收方,如果接收方不在线,会通过其他通道来触达和通知到接收方用户,体现出 “即时通讯” 的优势 。

在 IM 单体架构中,我们分析过客户端不断对服务端进行轮询获取消息的方式(IM专题:单体架构IM系统(2)),这种方式我们称之为 “信箱模型”;信箱模型虽然实现比较简单,但是消息的及时性不好,因为存在大量的无效请求,会造成服务端的资源浪费和负载,信箱模型比较适合用户规模比较小的场景,不适合分层架构的 IM 系统。

分层架构的 IM 系统,通常基于服务端直接推送的方式实现消息从服务端到客户端的传输,见下图。

图片

客户端 A 发送消息到服务端后,服务端直接将消息推送到客户端 B,就好像服务端知道客户端 B 的电话号码一样,可以直接联系到客户端 B,我们把这样的消息推送模型叫做 “电话模型”,这里的 “电话” 往往就是 长连接。

分层架构 IM 系统的消息收发流程,为了做到 “及时性” 和 “可靠性”,我们划分为三个阶段:生产消息阶段、推送消息阶段,确认消息阶段,在这三个阶段中,通过识别用户假在线的三重保障加强了消息的及时可靠性。IM 分层架构消息收发流程见下图。

图片

以用户 uid=101 发送消息给用户uid=102 为例,描述详细流程。

一、 生产消息阶段

生产消息阶段完成客户端成功发送消息到服务端的过程。

  • 1-1:客户端 101 成功登录后,基于与入口层 Entry 之间的长连接,发送消息请求到 Entry;
  • 1-2:Entry 是入口层组件,不负责处理业务请求,收到客户端的消息请求后,基于与业务逻辑层 Logic 之间的  RPC 连接,将消息请求转发到 Logic;
  • 1-3:Logic 接收消息后的第一件事情,是对消息内容进行 “反作弊” 过滤,通过调用 Spam 服务识别出违反平台规则的消息;若消息违反平台规则,则整个流程止步于此;
  • 1-4:对于正常的消息,Logic 做的第二件事情是通过调用数据访问层 Das 将消息落库,具体是分别写入 “消息库” 两条记录和 “联系人库” 两条记录;
  • 1-5:消息成功落库后,Logic 构造回复包,通过 Entry 主动建立的 RPC 连接,将回复包发送给 Entry;
  • 1-6:Entry 将回复包发送给客户端 101。

这是生产消息阶段所要完成的事情,有几个关键的设计点需要注意:

  1. Logic 接收消息请求后,一定是 “先落库,再回复”,顺序不能反,很容易可以思考到为什么;
  2. 对于客户端来讲,消息发送后,如果在一定时间内没有收到 response,则表示消息发送失败;此时在客户端界面上,消息的旁边会出现红色的感叹号,提示用户消息发送失败,点击重发;根据网络的二将军原理,我们知道,客户端虽然没有收到 response,但消息可能已经落库成功,若重发消息,会造成消息的重复,如何幂等呢?通过客户端来生产在客户端范围内唯一的 client_msg_id 即可;(在 IM专题:分层架构IM系统(10)—Das领域模型设计 中有详细分析)
  3. Logic 接收消息落库时,为了消息定制化需要和读取的方便,同时也为了分库分表时同一用户的数据能落在一张表中,针对每一条消息会存两条消息记录和两条联系人记录。

二、推送消息阶段

推送消息阶段完成服务端推送消息到客户端的过程。

  • 2-1:Logic 访问路由层 Router,获取消息接收方 uid=102 是否在线和连接的 Entry节点;若客户端 uid=102 不在线,Logic 会封装相关数据,发送到 MQ,由 Extlogic 进行离线用户的召回逻辑处理,包括向离线客户端 uid=102 推送手机push、发送微信公众号消息(对于关注公众号的用户)、发送短信、发送VoIP等;
  • 2-2:若客户端  uid=102 在线,Logic 基于与 Entry 之间的 RPC 连接(对于这条 RPC 连接,Logic 是客户端,Entry 是服务端),将消息发送给 Entry;若用户 uid=102 处于假在线状态,即 Router 认为 102 在线,但 Entry 中已没有 102 的连接句柄(在 IM专题:分层架构IM系统(8)—Router假在线问题分析 中有详细分析),Entry 会直接回调 Logic 的 unreachabled 接口,在该接口中,Logic 一方面会写 Router,修复 uid=102 的假在线状态,一方面会封装相关数据发送到 MQ,由 Extlogic 进行离线用户的召回逻辑处理;
  • 2-3:若 Entry 可以在内存中找到 uid=102 的连接句柄,则基于客户端与 Entry 之间的长连接,将消息推送给客户端。

这是推送消息阶段所要完成的事情,有几个关键的设计点需要注意:

  1. Logic 分别通过访问 Router 和 Entry 识别出假在线用户,然后通过 Extlogic 完成离线用户的召回逻辑;对于真在线和真离线用户,都有相关的处理方式,如果不能识别出假在线用户,会极大影响 IM 系统的可靠性;
  2. Logic 除了向 Entry 提供 msg_send 接口外,还需要提供 unreachabled 接口,从整体逻辑上简化流程实现。

访问 Router 和 Entry 是识别用户假在线的两重保障,第三重保障发生在第三阶段。

三、确认消息阶段

在第二阶段中,Entry  若从内存中找到客户端的连接句柄,会基于此将消息推送;在 Entry 内存中存在句柄的客户端,仍然有可能是假在线状态;确认消息阶段完成客户端接收到消息后向服务端回复确认的过程。

  • 3-1:客户端 uid=102 接收到消息后,构造确认包,基于与 Entry 之间的长连接,发送给 Entry;
  • 3-2:Entry 基于与 Logic 之间的 RPC 连接(Entry 是客户端,Logic 是服务端),调用 Logic 的 msg_ack 接口,向  Logic 确认消息已经被客户端 uid=102 成功接收;如果在一定时间内(比如15秒),Logic 没有接收到客户端发送的该条消息的确认请求时,根据消息的级别和产品规则,要么对该消息进行重复推送,要么认为客户端 uid=102 已经离线,此时 Logic 一方面写 Router,一方面封装相关数据发送给 MQ,由 Extlogic 进行离线用户的召回逻辑处理。

这就是识别假在线用户的第三重保障。确认消息阶段所要完成的事情不多,但有一个非常关键的设计。

大家想一下,对于 Logic 推送的每一条消息,Logic 都要等待(假设15秒)其被确认;对每一条消息单独开启一个线程,我们可以很容易落地实现;如果在15秒内,有10万条消息被推送,是不是要开启10万个线程来出来呢?有没有更轻量级的实现方案呢?(我们在下篇文章中解析该方案)

最后,总结一下文中关键:

  1. IM,要求消息具备 “及时性” 和 “可靠性”;
  2. 在用户量较大的分层架构的 IM 系统中,通常基于 “电话模型” 实现消息从服务端到客户端的传输;
  3. 分层架构 IM 系统的消息收发流程,包括三个阶段:
    • 生产消息阶段,完成客户端成功发送消息到服务端的过程;
    • 推送消息阶段,完成服务端推送消息到客户端的过程;
    • 确认消息阶段,完成客户端接收到消息后向服务端回复确认的过程。
  4. 识别用户假在线状态的三重保障:
    • 访问 Router;
    • 访问 Entry;
    • 客户端回复确认包。

作者:棕生 | 来源:公众号——架构之魂

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论