单体架构 IM 系统之 Server 节点状态化分析

基于 http 短轮询模式的单体架构的 IM 系统见下图,即客户端通过 http 周期性地轮询访问 server 实现消息的即时通讯,也就是我们前面提到的 "信箱模型"。"信箱模型" 虽然实现非常容易,但是消息的实时性不高。

我们在上一篇文章(单体架构 IM 系统之长轮询方案设计)中提出了优化方案,即通过 http 长轮询方式模拟出长连接的效果。

基于 http 长轮询方式实现的 IM 系统的单体架构中, server 节点还是无状态化的吗?所谓 "无状态化" 节点,是指进程在内存和硬盘中没有独立的数据;很明显,不同的 server 节点会 hold 住不同客户端的 http 请求,也就是不同的 server 节点中会存储不同客户端的数据, server 节点是有状态化的;此时,点对点的消息发送逻辑肯定需要进行调整。

大家可以先思考几个问题:

  1. 在 http 长轮询模式下, server 节点是有状态的,如何实现 server 节点的高可用呢?

  2. 客户端 x 发消息给 y,如果 x 和 y 访问的是不同的 server 节点,应该如何处理呢?

  3. 在 http 长轮询模式下,怎样判断消息接收方是否在线呢?

我们直接给出在 http 长轮询模式下,消息点对点的发送流程;以客户端 x 发消息给客户端 y 为例,如下:

  • 客户端 x 向 server 端发送 http 消息请求;

  • server 首先将消息直接落库,分别写 "云消息表" 和 "离线表";

  • 然后 server 访问缓存,获取消息接收方 y 的在线状态,若 y 不在线,则整个流程结束;

  • 如果消息接收方 y 在线,通过访问缓存获取 y 连接的是哪一个 server 节点;

  • 如果 y 和 x 连接的同一个 server 节点,则 server 将该消息通过 http 长轮询返回给客户端 y;

  • 如果 y 连接的是另一个 server 节点,此时需要当前 server节点将消息推送到目标 server 节点;

  • 最后目标 server 节点将消息通过 http 长轮询返回给客户端 y。

在上述流程中,有两个地方需要特别注意:

  1. 客户端每一次发起 http 长轮询请求,相当于一次心跳,表示用户的在线状态,需要在缓存中记录客户端的在线数据;在 http 短轮询模式中,缓存中记录的 session 数据是 map<uid, {type, cmd, time}> ,在 http 长轮询模式中,需要记录客户端请求的是哪一个 server 节点,所以 session 类型为 map<uid, {type, cmd, time, serverip}>。

  2. 不管消息接收方在线与否,server 节点接收消息后,都需要写 "离线表",这样设计的原因是为了提高消息的可靠性;因为即使用户 "在线",在 http 长轮询返回时,客户端有可能接收不到消息,同时,在一次完整的 http 长轮询请求的间隙中,消息都是有丢失的可能的,所以持久化 "离线表" 是可靠性的保证;因此,在每一次 http 长轮询请求中,都需要访问 "离线表",一是删除客户端已经收到的消息,二是从 "离线表" 中获取还未收到的消息。

在 http 长轮询模式下, server 节点是有状态的,那么其高可用如何保证呢?这个问题很容易解决:首先 server 节点肯定要集群化部署,然后由 反向代理 nginx 转发请求到 server ; nginx 通过配置实现客户端ip的会话保持,即将相同的客户端请求始终转发到固定的 server 节点; 当 server 节点挂掉之后,nginx 将请求转发到其他 server 节点即可,服务仍将持续提供,只需变更缓存中客户端状态信息即可。

单体架构 IM 系统,从架构到设计,从协议到逻辑,其关键点都进行了 一 一 分析;最后,我们简单聊一下 server 的整体设计,server 通过 Go语言进行了实现,见下图。

  1. 主协程,不处理任何的业务逻辑,用于接收外部信号,如关闭程序等;

  2. 子协程,用于接收客户端连接,针对每一个客户端连接,子协程都会生成两个协程来维护该连接,即:每一条连接会有一个独立的协程组来维护(该协程组中有两个协程,一个用于读,一个用于写);

  3. 连接管理器,实现对所有连接的管理,从连接中读取请求交由业务逻辑模块处理;

  4. 业务逻辑模块,实现核心的业务逻辑,包括:登录、登出、心跳、发消息等;

  5. 在线用户管理器,维护连接当前 server 节点所有的客户端;如果消息接收方在当前 server 节点,在线用户管理器通过 管道(chan)将消息传输给连接管理器中消息接收方的连接;

  6. 通讯协议,是公共协议定义,由【连接管理器】【业务逻辑模块】【在线用户管理器】共同引用。

关于 "每一条连接会有一个独立的协程组来维护",是 Go 语言通用的高效网络编程模型,见下图。

  • 客户端与服务端建立连接时,在服务端其实创建了一个 socket (即 fd 或句柄);

  • 然后为该 socket 生成一个协作组,该协程组包括两个协程: 协程1-1,负责对 socket 进行读; 协程1-2,负责对 socket 进行写;这两个协程,一个读一个写互不影响,高效协作;

  • 当需要向客户端写消息时,不管是当前socket 请求的数据,还是从其他 socket 中读取的数据,必须通过协程组的管道(channel) 作为入口,然后协程1-2会从 channel 中读取数据然后写入到 socket 中。

最后,总结文中关键:

1、基于 http 长轮询方式实现的 IM 系统的单体架构中, server 节点是有状态的;

2、基于 http 长轮询发消息流程:消息到达 serer 后,先落库;若消息接收方在当前 server 节点,直接返回,否则需要将消息推送到目标 server 节点;

3、 基于 http 长轮询方式实现的 IM 系统,缓存中需要记录客户端连接的是哪一个 server 节点;

4、 在 http 长轮询模式中,不管消息接收方在线与否,server 节点接收消息后,都需要写 "离线表";

5、 Go 语言通用的高效网络编程模型:每一条连接会有一个独立的协程组来维护;协程1-1,负责对 socket 进行读; 协程1-2,负责对 socket 进行写。

至此,"单体架构 IM 系统" 核心问题全部讲完了,你是否还记得如下关键点:

为什么要采用单体架构?

单体架构有怎样的优势?

单体架构的IM系统是怎样的?

单体架构 IM 系统的消息收发逻辑是如何实现的?

什么是 "信箱模型" ,有什么优势和缺点?

"信箱模型" 消息的实时性如何提升?

http 长轮询方式的两种落地方案:"定时器" 和 "时间轮" 如何实现?

上述问题都可从以下四篇文章中找到答案:

单体架构 IM 系统之架构设计

单体架构 IM 系统之核心业务工作实现

单体架构 IM 系统之长轮询方案设计

单体架构 IM 系统之 Server 节点状态化分析

分层架构 IM 系统的关键问题,后续文章马上更新跟进......

相关推荐
棕生3 天前
单体架构 IM 系统之长轮询方案设计
单体架构·im系统·信箱模型·http 长轮询·定时器方案·时间轮方案
棕生5 天前
单体架构 IM 系统核心业务功能实现
单体架构·im系统·im业务功能·信箱模型
棕生10 天前
单体架构的 IM 系统设计
技术选型·单体架构·im系统
冰 河10 个月前
自己手写了一套高性能分布式IM即时通讯系统,出去面试嘎嘎聊,都把面试官整不会了!
分布式·微服务·面试·程序员·im系统
忆梦九洲10 个月前
微服务概述之单体架构
微服务·云原生·架构·单体架构·mvc模型