[color=rgba(0, 0, 0, 0.75)]
- 识别异常任务:流速控制和跛脚鸭任务
(a)流速控制机制也是一种非常简单的负载均衡机制:如果某个后端任务过载了,请求处理开始变慢,客户端会自动避开这个后端,从而将任务分配给其他的后端。
(b)跛脚鸭状态,后端任务正在监听端口,并且可以服务请求,但是已经明确要求客户端停止发送请求。
在Google的RPC框架实现中,不活跃的客户端(没有建立TCP连接的客户端)也会定期发送UDP健康检查包。这就使跛脚鸭状态可以相对较快地传递给所有的客户端—通常在一到两个RTT周期内—无论它们处于什么状态下。
允许任务处于这种半正常的跛脚鸭状态的好处就是让无缝停止任务变得更容易,处于停止过程中的任务不会给正在处理的请求返回一个错误值。能够无影响地停止一个活跃的后端任务可以让处理代码推送、设备维护活动,和机器故障问题导致的任务重启变得对用户透明。这个停止过程通常按照以下步骤进行:
(1)任务编排系统发送一个SIGTERM信号给该任务。
(2)后端任务进入跛脚鸭状态,同时请求它的所有客户端发送请求给其他后端任务。这通过SIGTERM信号处理程序中调用RPC实现中的API完成。
(3)任何在后端进入跛脚鸭状态时正在进行的请求(或者在进入状态之后,但是其他客户端收到通知之前)仍会继续进行。
(4)随着请求回复被发送回客户端,该后端任务的活跃请求逐渐降低为0。
(5)在配置的时间过后,该后端程序要么自己干净地退出,要么任务编排系统主动杀掉它。该时间应该被设置为一个足够大的值,以便一般的请求可以有足够的时间完成。每个服务的该数值都不同,一般来说取决于客户端的复杂程度,10s到150s是一个不错的选择。
- 利用划分子集限制连接池大小在健康管理之外,负载均衡另外要考虑的一个因素就是子集划分:限制某个客户端任务需要连接的后端任务数量。
(a)子集选择算法一:随机选择。一个最简单的子集选择算法是让所有客户端任务将后端列表随机排列一次,同时将其中的可解析/可服务状态的后端提取出来。一次性随机排列并顺序选取可以很好地处理重启和任务失败情况(在这些情况下连接变动很小),因为这种算法限制了所考虑的后端的数量。
(b)子集选择算法二:确定性算法。将客户端任务划分在多“轮”中,每一轮i包含了subset_count个连续的客户端,从客户端subset_count * i开始,同时subset_count是子集的个数(也就是后端数量除以想要的子集大小)。在每一轮计算中,每个后端都会被分配给一个而且仅有一个客户端任务(最后一轮除外,这时可能客户端数量不够,所以有些后端没有被分配)。
每个连接都需要双方消耗一定数量的内存和CPU(由于定期健康检查导致)来维护。虽然这个消耗理论上很小,但是一旦数量多起来就可能变得很可观。子集化可以避免一个客户端连接过多后端任务,或者一个后端任务接收过多客户端连接。
- 负载均衡策略可以是非常简单的,不考虑任何后端状态的算法(例如,轮询),或者是基于后端状态的算法(例如最小负载轮询,或者带权重的轮询)。
(a)简单轮询算法:让每个客户端以轮询的方式发送给子集中的每个后端任务,只要这个后端可以成功连接并且不在跛脚鸭状态中即可。该策略的负载均衡结果可能很差。这里的实际数据取决于很多因素,例如不同的请求处理成本和物理服务器的差异等。
(b)最闲轮询策略:让每个客户端跟踪子集中每个后端任务的活跃请求数量,然后在活跃请求数量最小的任务中进行轮询。最闲轮询策略的最危险的一个坑:如果一个任务目前不健康,它可能会开始返回100%的错误。取决于错误的类型,错误回复可能延迟非常低;一般来说返回一个“我不健康”的错误比实际处理请求要快得多。于是,客户端任务错误地认为该任务可用,从而给该异常任务分配了大量的请求。
(c)加权轮询策略:加权轮询策略理论上很简单:每个客户端为子集中的每个后端任务保持一个“能力”值。请求仍以轮询方式分发,但是客户端会按能力值权重比例调节。在收到每个回复之后(包括健康检查的回复),后端会在回复中提供当前观察到的请求速率、每秒错误值,以及目前资源利用率(一般来说,是CPU使用率)。客户端根据目前成功请求的数量,以及对应的资源利用率成本进行周期性调节,以选择更好的后端任务处理。失败的请求也会记录在内,对未来的决策造成影响。
|