nginx加权轮询算法

平滑加权轮询算法介绍

一个简单的Nginx负载均衡配置。

http {
    upstream cluster {
        server 192.168.100.1 weight=5;	//下面使用a表示
        server 192.168.100.2 weight=1;	//下面使用b表示
        server 192.168.100.3 weight=1;	//下面使用c表示
    }
 
    server {
        listen 80;
        location / {
            proxy_pass http://cluster;
        }
    }
}

当在nginx配置文件的upstream配置块中没有指定使用的负载均衡算法时,默认使用的是加权轮询。
按照上述配置,Nginx每收到7个客户端的请求,会把其中的5个转发给后端a,把其中的1个转发给后端b,把其中的1个转发给后端c。这就是所谓的加权轮询,看起来很简单,但是最早使用的加权轮询算法有个问题,就是7个请求对应的后端序列是这样的:{ c, b, a, a, a, a, a },会有5个连续的请求落在后端a上,分布不太均匀。
目前使用的加权轮询叫做平滑的加权轮询(smooth weighted round-robin balancing),它和前者的区别是:
每7个请求对应的后端序列为 { a, a, b, a, c, a, a },转发给后端a的5个请求现在分散开来,不再是连续的。

摘录此算法的描述:
On each peer selection we increase current_weight of each eligible peer by its weight,
select peer with greatest current_weight and reduce its current_weight by total number
of weight points distributed among peers.
To preserve weight reduction in case of failures the effective_weight variable was introduced,
which usually matches peer’s weight, but is reduced temoprarily on peer failures.

算法实现

nginx-1.10.1中的加权轮询算法实现:

static ngx_http_upstream_rr_peer_t *
ngx_http_upstream_get_peer(ngx_http_upstream_rr_peer_data_t *rrp)
{
    time_t                        now;
    uintptr_t                     m;
    ngx_int_t                     total;
    ngx_uint_t                    i, n, p;
    ngx_http_upstream_rr_peer_t  *peer, *best;

    now = ngx_time();

    best = NULL;
    total = 0;

#if (NGX_SUPPRESS_WARN)
    p = 0;
#endif

    for (peer = rrp->peers->peer, i = 0;
         peer;
         peer = peer->next, i++)
    {

        n = i / (8 * sizeof(uintptr_t));
        m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t));

        if (rrp->tried[n] & m) {
            continue;
        }

        if (peer->down) {
            continue;
        }

        if (peer->max_fails
            && peer->fails >= peer->max_fails
            && now - peer->checked <= peer->fail_timeout)
        {
            continue;
        }

        peer->current_weight += peer->effective_weight;
        total += peer->effective_weight;

        if (peer->effective_weight < peer->weight) {
            peer->effective_weight++;
        }

        if (best == NULL || peer->current_weight > best->current_weight) {
            best = peer;
            p = i;
        }
    }

    if (best == NULL) {
        return NULL;
    }

    rrp->current = best;

    n = p / (8 * sizeof(uintptr_t));
    m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t));

    rrp->tried[n] |= m;

    best->current_weight -= total;

    if (now - best->checked > best->fail_timeout) {
        best->checked = now;
    }

    return best;
}

每个后端peer都有三个权重变量,先解释下它们的含义。
(1) weight
配置文件中指定的该后端的权重,这个值是固定不变的。
(2) effective_weight
后端的有效权重,初始值为weight。
在释放后端时,如果发现和后端的通信过程中发生了错误,就减小effective_weight。
此后有新的请求过来时,在选取后端的过程中,再逐步增加effective_weight,最终又恢复到weight。
之所以增加这个字段,是为了当后端发生错误时,降低其权重。
(3) current_weight
后端目前的权重,一开始为0,之后会动态调整。动态调整:
每次选取后端时,会遍历集群中所有后端,对于每个后端,current_weight增加effective_weight,
同时累加所有后端的effective_weight,保存为total。
如果该后端的current_weight是最大的,就选定这个后端,然后把它的current_weight减去total。
如果该后端没有被选定,那么current_weight不用减小。

弄清了三个weight字段的含义后,加权轮询算法可描述为:

  1. 对于每个请求,遍历集群中的所有可用后端,对于每个后端peer执行:
    peer->current_weight += peer->effecitve_weight。
    同时累加所有peer的effective_weight,保存为total。
  2. 从集群中选出current_weight最大的peer,作为本次选定的后端。
  3. 对于本次选定的后端,执行:peer->current_weight -= total。

算法示例

上述描述可能不太直观,来看个例子。
现在使用以下的upstream配置块:

upstream backend { server a weight=4; server b weight=2; server c weight=1; }

按照这个配置,每7个客户端请求中,a会被选中4次、b会被选中2次、c会被选中1次,且分布平滑。
我们来算算看是不是这样子的。
initial current_weight of a, b, c is { 0, 0, 0 }

请求序号current_weight before selectedselect peercurrent_weight after selected
1{4,2,1}a{-3,2,1}
2{1,4,2}b{1,-3,2}
3{5,-1,3}a{-2,-1,3}
4{2,1,4}c{2,1,-3}
5{6,3,-2}a{-1,3,-2}
6{3,5,-1}b{3,-2,-1}
7{7,0,0}a{0,0,0}

通过上述过程,可得以下结论:

  1. 7个请求中,a、b、c分别被选取了4、2、1次,符合它们的权重值。
  2. 7个请求中,a、b、c被选取的顺序为a, b, a, c, a, b, a,分布均匀,权重大的后端a没有被连续选取。
  3. 每经过7个请求后,a、b、c的current_weight又回到初始值{ 0, 0, 0 },因此上述流程是不断循环的。
    这个平滑的加权轮询算法背后应该有数学论证,这里就不继续研究了)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值