IM 内容分享(五): Router介绍

目录

Router能力分析

一、 业务接口

二、 通知回调

Router维护

一、主从模式

二、主主模式

Router假在线问题分析

总结文中关键


Router能力分析

在分层架构 IM 系统中,路由层 Router 的核心职责是作为中央存储记录在线用户的连接状态,见下图。

Router 其本质是一个巨大的  Map<uid,  EntryIp>,其 key 是表示客户端用户的 uid, 其 value 是客户端连接的 Entry 节点;要判断一个用户是否在线,该用户客户端连接到了哪一个 Entry 节点,Router 是门儿清的。

明确了 Router 的核心职责后,那么Router 需要提供哪些能力呢?

先分析一下,在分层架构 IM 系统中 Entry、Router 和  Logic 三者之间的调用关系,见下图。

  1. Entry 通过 RPC 调用 Logic,实现业务逻辑的处理;

  2. Logic 通过 RPC 调用 Entry,实现消息向用户侧的推送;

  3. Logic 通过 RPC 调用 Router,维护在线用户信息;

  4. Router 回调 Logic,实现心跳失活用户的及时通知。

所以,Router 需要提供两方面能力:业务接口 和 通知回调。

一、 业务接口

Router 的核心数据结构是一个 Map,其 key 是唯一标识用户的 uid,而其 value 为了方便功能上扩展,可以落地为一个 结构体对象,代码如下:

Map<uid, userInfo>userInfo {  int userStat;//用户状态 0—离线 1—在线 2—......  long heartbeat;//最近一次心跳时间  int deviceType;//设备类型 0—安卓 1—IOS 2—......  string deviceToken;//用户移动设备token,用于手机推送  string entryAddr;  //Entry节点地址, ip:port}

Router 基于该核心数据结构,向 Logic 提供四个业务接口;这四个业务接口,纯内存操作,而且逻辑简单。

  1. 用户登录

    Logic 校验完成用户登录请求后,构建 userInfo 对象,写入到 Router 中。

  2. 用户登出

    用户登出时,Logic 直接从 Router 中根据用户 uid 删除记录,或将 userStat 字段置为 0。

  3. 设置用户信息

    在多种业务场景下,Logic 会调用该接口:客户端发送心跳请求时,修改 heartbeat 字段值;客户端在线状态下,使用了新的设备进行登录,修改 deviceType  和 deviceToken 字段值,如果连接的是新的 Entry 节点,修改 entryAddr 字段值。

  4. 查询用户信息

    Logic 需要向用户主动推送消息时,在 Router 中查询用户是否在线,如果在线,则获取用户客户端连接的 Entry 节点地址。

二、 通知回调

Router 中保存了所有的在线用户;而 Router 不仅仅只是一个内存数据库,它能根据客户端心跳数据判断出失活用户,然后通过回调方式及时通知 Logic 进行处理;Logic 处理的方式是通知相关 Entry 节点及时关闭与客户端之间的连接,释放资源。

我们在前面的文章中分析过,Entry 也会根据客户端心跳数据识别出失活用户,这和 Router 的识别失活用户工作并不冗余,而是相辅相成。

Router 的核心数据结构是一个很大的 Map。通常我们会通过定时扫描的方式,扫描出很长时间未发送心跳包的客户端;在扫描的时候,为了安全,会给这个很大的 Map 加上一把大锁,这会非常影响 Router 的服务性能;如何进行优化呢?

将一个大的 Map 划分成 16 个小 Map,也可以形象地称之为 16 个桶,标记为 0~15;根据公式 uid % 16 可以简单算出用户会落入到哪个桶中。扫描线程按桶进行扫描,扫描到哪个桶,就只对哪个桶进行加锁,如此则大大降低了锁的粒度。根据在线客户端数目,可以对桶的数量进行调整。

Router维护

通过前面文章的分析,我们已经明确,Router 的核心职责是作为中央存储记录在线客户端的连接状态,Router 在本质上是一个内存数据库。

内存是一种易失性的存储,既如此,Router 的可用性如何保障呢?

副本是分布式存储系统容错技术的唯一手段!所以解决 Router 的可用性问题,为其增加冗余的副本即可。

分布式存储系统的副本,有两类常用模式:主从模式和主主模式。

一、主从模式

对于主从模式来说,服务向 “主节点” 写数据,“从节点” 从 “主节点” 中同步数据;“主节点” 和 “从节点” 都是可以提供 “读数据” 服务的,不过只有主节点才能提供 “写数据” 服务,所以主从模式对于写操作来说,是单点方式,要解决 “写高可用” 是非常麻烦的。

二、主主模式

对于主主模式来说,服务可以向任何一个 “主节点” 写数据,也可以从任何一个 “主节点” 读数据,然后两个主节点之间互相同步数据,所以主主模式同时满足数据 “读高可用” 和 数据 “写高可用”;但是要注意,主主模式不适用的业务场景:对同一条数据从两个 “主节点” 同时进行写操作。

举一个例子: 主主模式集群中有一条数据为 x = 5,两个服务节点分别对两个 “主节点” 同时进行修改,一个 “主节点” 修改为 x = 7,一个 “主节点” 修改为 x = 9,此时两个 “主节点” 在互相同步时就会出现冲突。

所以,在使用主主模式时,一定要避免对相同数据的并发写。

Router 采用了 主主模式,见下图。

Logic 在写 Router 时,通过公式 uid % 2 计算要写入的 Router 节点,避免了相同用户记录在两个主 Router 节点上的并发写操作。另外,对于同一用户的操作,会由相同的 Logic  节点来处理(后面的技术文章中会详细分析),这也会避免相同用户记录的并发写入。

单个 Router 节点保存了所有在线客户端的用户数据,随着在线用户量的增多,Router 会很容易到达存储瓶颈;解决这个问题的常用方案是分片。

将所有在线用户分配到 N 个分片中,需要部署 N 组Router,每一组 Router 可以主主模式部署也可以主从模式部署; Logic 在写 Router 时,通过公式 uid % N 计算要写入的 Router 分组。这样通过分组的方式,实现了在线用户的横向线性扩容;每一组内部通过主主模式 或 主从模式实现了高可用。

这里有一个实践性很强的问题,大家思考一下: 将最开始的一组 Router 扩容为四组 Router 时,怎样将原来的数据进行迁移呢?

答案是不必迁移。一组 Router 扩容为四组 Router 后,原来这一组 Router 中大概会有四分之三的在线用户数据会被遗弃,不过不必担心,在上一篇文章中,我们分析过,Router 的心跳扫描线程会扫描出心跳失活的用户记录进行清理;另外,其它三组 Router 最开始是空白数据,但随着在线用户客户端的心跳到来,会逐步将这空白的三组 Router 数据进行修复。

心细的同学会提出疑问:即使如此,当从这三组 Router 中读数据时,读不到怎么办呢?这就涉及到 IM 系统的容错性了。将一个在线用户,按离线方式去处理,并不会对用户造成不好的体验。当然,对 Router 进行扩容,选择在凌晨时分处理,肯定是最合适的。

将一个刚启动的 Router 节点作为 “从节点” 部署时,“主节点” 是如何进行数据同步的呢?见下图。

Router 在内存中维护了很多个 Map,当有其他 Router 节点以 “从节点” 的角色连接过来时,“主节点” Router 会将内存数据生成一个 “快照”,然后将 “快照” 数据发送到 “从节点”。

“主节点” Router 生成 “快照” 后,再接收到的所有的 “写操作” 将全部写入到 “增量数据队列” 中;Router “从节点” 消化完 “快照” 后,就会再次发请求到 “主节点”,然后不断从 “主节点” 的 “增量数据队列” 中读数据完成主从同步。

关于 “快照” 如何生成,我们在后面的技术文章中进行分析!

Router假在线问题分析

通过对分层架构 IM 系统的分析,Router 的核心职责是作为中央存储记录在线客户端与 Entry 节点之间的映射关系,在本质上 Router 是一个内存数据库。

什么是 Router 假在线呢?见下图。

客户端已经离线,Entry 还未感知,或者 Entry 已经感知并且切断了连接,但是 Router 中仍记录着该客户端的在线状态数据;如图中,uid 是 102 的用户已经离线,但是 Router  中仍然记录着 102 与 Entry 节点的映射关系。这样造成的直接后果就是,当要向 102 推送消息时,Router 提供的数据则是无效的。

怎么定性 Router 假在线问题呢?

造成这个问题是不是由于整个 IM 系统的分层架构设计有缺陷呢?不是的, Router 假在线问题是一个合理的异常现象,说 【合理】是因为造成这个问题是正常的,是合理的,不是因为整体设计有问题;说【异常】是因为这毕竟会导致不好的结果,的确是一个问题,是需要解决的!

Router 假在线问题是怎么造成的呢?

客户端是移动设备,处于弱网络的环境中,客户端与 Entry 之间的 TCP 连接是很容易断开的;TCP 连接中断后,其两端节点的反应具有延迟性,到 Entry 捕捉住 TCP 连接中断事件时具有一个窗口期,所以 Router 的数据状态与实际情况不一致就是必然的。

更主要的情况是:Entry 感知到连接中断后,需要通过 Logic 写入到 Router 中进行更新,Logic 是业务模块,升级迭代和进程重启非常频繁,会很容易丢掉 Entry 发送的 “连接中断” 事件,所以 Router 假在线问题就产生了。

怎么避免或解决  Router  假在线问题呢?

既然经常重启的 Logic  导致了 Entry 不能百分百成功写 Router,那是否可以由 Entry 直接写 Router 呢?见下图。

由 Entry 直接写 Router  是不可取的。我们在前面的文章中分析过,Entry  的核心职责是维护与客户端之间的长连接,不负责处理任何的业务逻辑;而  Router 中存储了与业务逻辑相关的很多字段(比如:deviceType、deviceToken等),由 Entry 直接访问  Router 会造成极大的耦合性,破坏最初的架构原则。

常见的解决 Router 假在线问题的手段有:

  1. Router 通过自身的心跳扫描机制,扫描并清理掉过期的数据;

  2. Entry 推送消息时,若发现用户已经离线,则 Entry 回调 Logic 的 “unreachable” 接口,由 Logic 清理Router 中的假在线数据;

  3. Entry 推送消息时,若没有收到客户端回复的 ACK 时,Logic 也会修复 Router 中的假在线数据。

对 Router 假在线问题进一步抽象:假在线其实是一个分布式系统的 “数据一致性” 问题,为什么不采用 “强一致性” 方式来彻底消除假在线问题呢?也就是采用 CAP 模型中的 CP 模型。

因为 CP 模型会导致系统的可用性大大降低,在访问 Router 时,Router 需要通过类似于一致性协议的手段与Entry 进行通信确认在线状态,其效率可想而知;在互联网中,尤其是非金融领域, AP模型才是最合适的。

IM 以及其他互联网系统,在研发过程中遇到的所有问题,在实践中需要通过低成本的方式来解决,而不能只站在理论层次来纸上谈兵,即【降本增效】。

总结文中关键

1、Entry、Logic、Router 三者之间的调用关系为:

   a. Entry 接收客户端请求调用 Logic;

   b. Logic 向客户端推送消息调用 Entry;

   c. Logic 维护在线用户数据调用 Router;

   d. Router 扫描出失活用户,回调通知 Logic。

2、Router 向 Logic 提供四个业务接口:

   a. 用户登录

   b. 用户登出

   c. 设置用户信息

   d. 查询用户信息

3、Router 通过定时扫描 Map 的方式获取失活用户,为了提升服务性能,将一个大  Map 划分成 多个桶,然后对桶加锁,降低锁的粒度。

4、 分布式存储系统的副本模式有两种实现方式:主从模式和主主模式,在使用主主模式时,需要避免相同数据记录在两个主节点上被并发写入;

5、 Router 采用了主主模式,当在线用户量达到单个 Router 的存储瓶颈时,通过分片方式实现横向扩容;

6、 在对 Router 进行横向扩容时,在 IM 这个业务场景下,不需要进行数据迁移;

7、 Router 进行主从数据同步时,先生成和同步快照,然后同步增量数据。

8、什么是 Router 假在线?简单理解为: Router 认为用户在线,而 Entry 中用户已经离线。

9、怎么定性 Router 假在线?合理的异常现象。

10、 Router  假在线问题是如何造成的?主要因素是 中间节点 Logic 的 “不稳定性”。

11、 怎么解决  Router 假在线问题? Router 自身扫描机制 或 由 Entry 通知 Logic 及时修复。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

之乎者也·

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值