长连接调度引擎ipConfig

设计目标

用于调度客户端连接哪个IM gateway的ip地址的http server,提供一个查询endpoint信息的列表接口,对外仅暴露查询接口,对内连接服务发现&配置中心&redis等服务用语实时获取统计数值计算最佳IP地址以达到长连接网关的负载均衡的目标

因此要开发一个支持在线查询,进线实时计算的http server服务

约束条件

  1. 负载均衡: 针对当前的客户端计算候选IP对其建立连接的状态是否为最佳的分值,这一数学模型的拟合度是最重要的指标,数学模型过于简单则会欠拟合计算,分值误差过大,数学模型过于复杂则会导致过拟合,现实网络环境发生一下变化则模型难以适应,导致指标劣化,其结果就是负载不均衡
  2. 查询延迟: 在线查询需要在线的计算当前客户端的ip地址对于候选ip的得分,这是一个典型的分配问题,需要一定的算法策略,这导致计算复杂度升高,查询延迟成为瓶颈,如果延迟升高此查询会影响用户客户端启动时建立连接端到端延迟,将直接影响用户体验。
  3. 查询吞吐: 为应对突发情况,当IMGateWay出现故障的时候,大量用户断开链接等情况出现时,会导致大量查询ipconfig服务,这会导致QPS等突增,ipconfig server需要应对这种情况
  4. 稳定性: ipconfig服务不可用,客户端拿不到ip地址自然无法建立长连接,整个im服务通信功能不可用,因此其稳定性应该是p0级别的
  5. 安全性: 此接口对客户端暴露使用,所以需要有一定的安全认证机制

架构设计

  1. 如何计算的更准确,才能保证长连接的负载均衡?

    长连接调度与短链接不同之处是连接是持续的,所以长连接的负载均衡问题本质上是一个持续任务的调度问题,其次长连接每个连接消耗的资源都是不同的,有一些客户端可能一直不说话,有一些客户端却在活跃的万人群聊中聊天,消耗的带宽资源是不同的,长连接建立连接的过程还会存在延迟,这一延迟是秒级的,这会导致统计的状态过期导致计算不准确,总结来说为计算准确的负载均衡分值由三个技术约束:持续负载/资源不均衡分配/计算状态的实时性

    由于约束二的存在,我们必须统计机器本身的负载情况并计算分值,以分值作为权重进行多权重分配,我们使用:ip之间的地理距离/连接数/带宽消耗/内存占用/CPU使用率/CPU load值/IMGateWay协程数等指标作为统计分值,计算融合分。

    由于约束三的存在,需要解决计算状态的过期问题,简单的思想是取每个计算状态在一个窗口内的平均值/中位数,使其更稳定,但每个窗口中时刻的点并不是等效的,距离当前时刻更近的点有效性更大,所以对于不同时刻的点应该有不同的置信度,以便于更好的刻画负载情况。

    融合分会带来新的问题,拿就是计算的复杂性,以及可解释性很差,难以评估负载是否均衡或者为什么均衡,出现不均衡的情况时难以排查问题。

    为此我妈需要寻找一种可解释性强的计算方法,根据上述三个约束的特性,可以发现gateway会存在两种不同的状态,一种是活跃状态:机器上存在大量活跃的连接收发消息进行通信,此时整个机器的性能瓶颈在于网络带块,而计算网络带宽时复杂的其干扰噪音较大,因此我们可以计算1s之内gataway手法消息的总字节数来衡量其带宽的吞吐情况,由于活跃状态的gateway最先耗尽的资源时网络带块,那当网络带宽剩余不足时,我妈就应该对其进行降权,我妈需要计算一个动态状态分值。

    其次,gateway还存在一种静态状态:机器上大量的连接处于非活跃状态,此时性能瓶颈在于内存等静态资源,一个长连接建立后要维护心跳与收发消息的协程等,哪怕在不进行通信时也要消耗网关机器大量的资源,因此未来能刻画此时情况的负载,此时应该使用gateway以及创建连接连接数量来衡量负载情况,所以我们需要计算一个静态状态分值。

    那合适使用静态分?何时使用动态分呢?活跃粉的变化时极快的,难以准确合理通常需要采样计算,这就回存在一些误差,未来平衡这种误差的影响,通常采用近似计算的方法,将gateway的收发消息字节数规约为以GB为单位的值并保留两位消暑,因此如果gateway收发消息的字节数近似规约后等于0GB,那么此时就会采用静态分进行计算,同时规约后如果gateway的动态分相等时,也会使用静态分进行比较。

    例如:如果当一台网关机动态分为1GB时,其他网关机只有0.8GB时,我们会优先调度0.8GB的机器,哪怕在1GB动态分的机器上只有2个连接(假设此机器的最大网络带宽是2GB,此时有理由假设,新加入的连接很可能先耗尽网络带宽资源,因此应该将其调度到网络带宽最富裕的机器上)

    但是:如果0.8GB的机器上连接也已经接近内存瓶颈时该怎么处理,这是就需要对二者进行加权平均,两害取其轻,简单的回归模型。

    事实上这种刻画依然是不精准的,没有考虑ip的地理位置,一个连接创建后其内存占用确定的,但其是否活跃却是预估的,这些依然是一个问题,事实上对精度的追求是无止尽的,因此需要专门的算法工程师来优化,这就超脱了架构的基本范畴。

  2. 如何降低查询的延迟?

    客户端的ip传递过来时,会根据当前各个IMGateway节点负载状态实时的计算出分值,然后进行排序取top5返回,这一操作如何能更加高效?问题的瓶颈在于计算量

    上述计算方法中计算窗口的方法是对窗口中的时刻都分配权重然后加权平均计算,事实上可以仅存储上一秒的负载分值,然后与当前分值做差后求积分并除以一个置信度系数(表示上一秒对这一秒负载的影响程度)这样就使得真个计算量大幅度减少。

    同时,ipConfig server可以对一些计算状态进行缓存避免重复计算,比如ip距离等。

  3. 如何提高整体的吞吐能力?

    ip config server本身等计算逻辑相对简单,通过上述优化后计算不再是瓶颈,那么整个吞吐的关键在于网络带宽,整体的性能趋近于网络的端到端时间,此时仅需要横向扩展机器增加集群的吞吐能力即可,亦或使用高性能网卡等。

  4. 如何保证ipconfig的稳定性?

    由于imgateway会存在不断增加机器的情况,而不同时期增加的机器可能配置是不同的,未来增加的机器可能配置更好,那么使用消耗了多少资源来表示负载和坑会导致资源使用率不足,所以我们可以使用资源的余量来表达负载情况,这样能充分考虑每台机器的真实负载的健康状态。

    关键在于评估负载均衡的监控状态,当出现不健康的情况时应当能及时报警或自动降级,其次当计算状态许久未更新的情况下,可退化为仅基于ip距离的计算或事随机策略做兜底,并具有手动设置的开关,当线上出现稳定性问题时可以快速切换流量并配置新的imgateway机器保证对外工作。

    其次需要尽可能的不好整ip config的旁路工作的特性,不要与数据中心的其他基础组件耦合,当下游出现问题时,可以进坑呢的保证ipconfig server服务的正常工作,着将有助于我们快速切流恢复线上稳定性。

    ipconf server底层依赖etcd集群,etcd基于raft算法所开发的分布式kv,本身具有高可靠设计,以此保证数据的一致性和可用性,其次所有的计算均在本地进行保证可靠性。

    imGateway也必须具有拒绝策略,应对计算状态统计延迟等问题倒置突发流量的情况,此时每个imgateway需要有一个限流拒绝策略,当单机负载不健康时,就需要给客户端直接返回拒绝连接的信号,客户端将调度下一个备选地址。

实现细节

接口设计:

Req:curl --location --request GET '127.0.0.1:6789/ip/list'

Resp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"message": "ok",
"code": 0,
"data": [
{
"ip":"127.0.0.1",
"port": "7896"
},
{
"ip":"127.0.0.1",
"port": "7897"
},
{
"ip":"127.0.0.1",
"port": "7898"
}
]
}

核心逻辑

  1. 使用web框架:hertz,基于gin封装的简洁高效的web server框架
  2. 使用ETCD作为服务发现,为追求高可用性,使用ETCD这种CP的分布式kv比较合适
  3. 使用viper作为配置解析模块
  4. 使用ETCD做服务发现,并且上报连接数和每秒收发消息的数据包字节数的剩余值作为统计值
  5. 使用5s的时间窗口取均值来屏蔽噪声,得到一个稳定的数值进行负载均衡的预估
  6. 由于ETCD的watch机制是与客户端建立长连接进行push的,所以ETCD的压力取决于机器的数量
  7. 所需的数值是在本地计算的,在线请求无需进行网络调用,因此性能极高,本身可水平扩展
  8. 稳定性有ETCD集群来保证其可靠性
  9. 定义全局使用的tiger.yaml配置,定义一个common包来用作依赖倒置,封装所有的基础设施组件
1
2
3
4
5
6
7
8
global:
env: debug
discovery:
endpoints:
- localhost:2379
timeout: 5
ip_conf:
service_path: /tiger/ip_dispatcher

架构方案