分层架构 IM 系统之 Entry 部署模式
在前面的一篇技术短文(分层架构 IM 系统之架构解读)中,对【分层架构】进行了详细分析;今天我们聊一下【入口层】Entry 的部署模式。
Entry 作为 IM 系统整个后端集群的入口,直接与客户端建立 TCP 长连接,Entry 的部署方式决定了客户端如何寻找到 Entry 地址;在实践中,我们前后对 Entry 采用了三种部署方式:DNS、IPList、TGW。
一、基于 DNS 部署
基于 DNS 部署见下图。所有的 Entry 节点都有一个独立的公网 IP,这些 IP 地址与域名注册到 DNS 服务器上。客户端会先访问 DNS,通过域名(如 im.zz.com)获取一个入口 Entry 的地址,然后客户端与该 Entry 通过三次握手建立 TCP 连接。
基于 DNS 部署 Entry,是最常见也是成本最低的一种方式,适用于用户规模较小的应用场景,比如几万日活。Entry 是集群化部署,其负载均衡完全依赖于 DNS,而 DNS 的负载均衡策略较为简单,最常用的是随机和轮询,即向 DNS 输入一个域名,其从配置的 IP 列表中随机或轮询返回一个 IP 地址。
另外,DNS 中配置的 IP 具有一定的缓存时效,当增加或减少一个 Entry 节点时,并不会立刻生效;DNS 对所有的 Entry 节点也没有监测机制,因此基于 DNS 部署 Entry的方式,其高可用完全依赖于人工介入。
二、基于 IPList 部署
基于 IPList 部署方式见下图。IPList 是一个 http 服务,所有的 Entry 节点都有一个独立的公网 IP,这些 IP 会注册到 IPList 服务上;客户端 APP 在启动时,会首先访问 IPList,获取所有的 Entry 节点的 IP列表,然后从中选择一个 Entry 节点,通过三次握手与之创建 TCP 连接。
基于 IPList 部署方式,需要自研 IPList 服务;IPList 服务因为自研,对资源会有更多的控制权,在增加或减少一个 Entry 节点时,直接在 IPList 中配置即可,可以立即生效,没有 DNS 缓存时效的问题。
在 IPList 部署方式下,需要考虑三个关键问题:
-
负载权重如何实现?在 Entry 集群中,并不是所有的 Entry 节点的处理能力是一致的,比如:x.x.1.1 和 x.x.1.2 的 Entry是 4C8G的机器,而 x.x.1.3 的 Entry 是 8C16G 的机器,这类权重问题如何低成本解决呢?只需要在 IPList 的配置中增加几条记录即可,即:通过如下配置
entry_1: x.x.1.1
entry_2: x.x.1.2
entry_3: x.x.1.3
entry_4: x.x.1.3
实现 三个 Entry之间 1:1:2 的权重比。当然,IP 列表的配置拉取到客户端后,对 Entry 节点访问的负载均衡依赖于客户端。
-
节点宕机如何处理?在增加一个 Entry 节点时,通过在 IPList 中配置一个新的数据项可以立刻生效,但是如果一个 Entry 节点突然挂掉后,仍然需要人工介入修改 IPList 配置,其可用性仍然是不高的,怎样做才能避免人工介入呢?将 IPList 服务设计成一个简化版的 “注册中心” 即可,Entry 节点启动后,自动将自身信息注册到 IPList,并通过 “心跳” 机制保持在 IPList 中的活性;当 Entry 节点突然宕机后,IPList 中的 Entry 节点活性降低为零,客户端获取不到失活的 Entry 节点。
-
怎样降低移动端流量?客户端 APP 每次启动时都会访问 IPList 获取所有的 Entry 节点列表,但在系统实际运行过程中,Entry 集群其实变动的频率非常低,那么这对移动端的流量是一种浪费,如何解决这个问题呢?通过版本号的方式解决,也就是 IPList每一次配置变动时,将版本号加1;客户端 APP 每次启动时,首先向 IPList 获取版本号,然后与本地缓存的版本号进行比对,只有本地缓存的版本号比较低时,才通过访问 IPList 获取 Entry 的 IP列表; 版本号只有一个字节大小,如此设计会大大降低移动端流量。
仔细分析一下 IPList 部署方式有这样几个缺点:首先对 Entry 集群访问时的负载均衡,最终由客户端进行控制,从全局和整体上看,并没有真正实现负载均衡;虽然增加和减少一个 Entry 节点时, IPList 会立即生效,但客户端并不会立即获取到最新的 IP 列表;如果客户端存在些许 Bug,在不升级版本的情况下,会导致对后端访问的诸多问题。在用户规模不断扩大时,Entry 集群的负载均衡和可用性问题最终还应是在服务端进行解决。
三、基于 TGW 部署
基于 TGW 部署方式见下图。TGW 是腾讯的一个网关系统,全称是 Tencent GateWay,在这里提供 4 层反向代理的能力,所有的 Entry 节点不需要公网 IP;客户端访问 TGW,然后 TGW 根据策略将连接请求转发到内网的一个 Entry 节点,最终客户端仍然是与 Entry 通过三次握手建立了 TCP 连接,TGW 在这里起到了 4 层负载均衡和反向代理的作用。
TGW 本质上可以看做是一个 LVS 集群,本身具备高可用;对于 APP 来说,只访问 TGW 的一个外网地址即可,不会关注到 Entry 集群的信息。
启动一个新的 Entry 节点后,TGW 会逐步将新流量缓慢分配到新的 Entry 节点上,并根据每一个 Entry 节点的负载情况分配新的连接请求;当 Entry 节点挂掉后,TGW 会瞬时感应到,并不再向异常的 Entry 分配流量。TGW 作为反向代理,同 Nginx 一样,提供了丰富的负载均衡策略。TGW 部署 Entry方式,非常适合大规模的用户访问。
另外,有一个实践经验供大家借鉴:在网络中,对于并不常用的端口的长连接,非常容易被运营商的网络设备切断,所以采用 “常用端口” 做长连接通讯是一个不错的选择;如上图所示,由 TGW 监听 80 或 443端口,Entry 监听的是则是 56100 端口;对于客户端来讲,建立连接的请求和业务数据,在网络中发送到的目标端口是 80 或 443,实际最终由监听 56100 端口的 Entry来接收和处理。
最后,总结文中关键:
1、部署入口层 Entry 集群时,需要重点考虑集群如何增加或减少一个节点、对集群访问的负载均衡问题和集群的高可用问题;
2、在实践中,Entry 有三种部署方式:DNS、IPList 和 TGW;
3、DNS 部署 Entry 方式最常见,但是生效缓慢、可用性差、负载均衡策略也简单,适合小规模应用;
4、IPList 部署 Entry 方式,需要重点考虑负载权重如何实现、节点宕机如何处理、怎样降低流量等三个问题;
5、TGW 部署 Entry 方式,扩容/缩容简单,可用性强,负载均衡策略丰富,适合大规模用户访问。
通过前面的分析,我们已经了解到,Entry 的核心职责就是维护与终端之间的长连接,而对长连接的保活,通常是基于心跳机制,大家思考一下:
对于不同规模的用户,有哪些比较合适的心跳实现机制呢?