Nginx负载均衡算法-RoundRobin

RoundRobin权重的实现算法

在Nginx中,上游服务可以通过server指令声明其IP地址或者域名,并通过upstream块指令定义为一组。这一组server中,将使用同一种负载均衡算法,从请求信息(比如HTTP Header或者IP Header)或者上游服务状态(比如TCP并发连接数)中计算出请求的路由:

如果上游服务器是异构的,例如上图中server 1、3、4、5都是2核4G的服务器,而server 2则是8核16G,那么既可以在server 2上部署多个不同的服务,并把它配置到多个usptream组中,也可以通过server指令后的weight选项,设置算法权重:

upstream rrBackend {
                server localhost:8001 weight=1;
                server localhost:8002 weight=2;
                server localhost:8003 weight=3;
}
location /rr {
                proxy_pass http://rrBackend;
}

在上面这段配置指令中,并没有显式指定负载均衡算法,此时将使用Nginx框架唯一自带的RoundRobin轮询算法。顾名思义,它将按照server在upstream配置块中的位置,有序访问上游服务。需要注意,加入weight权重后,Nginx并不会依照字面次序访问上游服务。仍然以上述配置为例,你可能认为Nginx应当这么访问:8001、8002、8002、8003、8003、8003(我在本机上启动了这3个HTTP端口,充当上游server,这样验证成本更低),但事实上,Nginx却是按照这个顺序访问的:8003、8002、8003、8001、8002、8003,为什么会这样呢?实际上这是为了动态权重的实现而设计的。我们先从实现层面谈起。
Nginx为每个server设置了2个访问状态:

struct ngx_http_upstream_rr_peer_s {
   ...
    ngx_int_t                       current_weight;
    ngx_int_t                       effective_weight;
...
};

其中current_weight初始化为0,而effective_weight就是动态权重,它被初始化为server指令后的weight权重。我们先忽略转发失败的场景,此时RoundRobin算法可以简化为4步:

  1. 遍历upstream组中的所有server,将current_weight的值增加effective_weight;
  2. 将全部的权重值相加得到total;
  3. 选择current_weight最大的server;
  4. 将这个选中server的current_weight减去total值。
    这样,前6次的运算结果就如下表所示:
current_weight/ current_weight加effective_weight/ total/ 选中server/ current_weight
[0,0,0] [1,2,3] 6 8003 [1,2,-3]
[1,2,-3] [2,4,0] 6 8002 [2,-2,0]
[2,-2,0] [3,0,3] 6 8003 [3,0,-3]
[3,0,-3] [4,2,0] 6 8001 [-2,2,0]
[-2,2,0] [-1,4,3] 6 8002 [-1,-2,3]
[-1,-2,3] [0,0,6] 6 8003 [0,0,0]
[0,0,0] [1,2,3] 6 8003 [1,2,-3]

由于总权重为6(1+2+3),所以每6次转发后就是一个新的轮回。我再把简化的RoundRobin源代码列在下方,你可以对照理解:

    ngx_http_upstream_rr_peer_t* best = NULL;
    ngx_int_t  total = 0;
    for (peer = rrp->peers->peer, i = 0;
         peer;
         peer = peer->next, i++)
    {
        //在每一轮计算中,current_weight要加上effective_weight
peer->current_weight += peer->effective_weight;
//total值是所有effective_weight的累加和
        total += peer->effective_weight;
        if (best == NULL || peer->current_weight > best->current_weight) {
            //每一轮找出current_weight最大的那个server
best = peer;
        }
}
//选出server后,要将最大的
    best->current_weight -= total;
    return best;

可以看到,这段代码只做一遍循环就完成了server选择,执行效率很高。


网络错误该如何处理?

在上例中我们假定不会转发失败,所以effective_weight是不变的。但现实中分布式系统出错才是常态,接下来我们看看RoundRobin算法是怎样处理错误的。
在server指令后,可以加入max_fails和fail_timeout选项,它们共同对转发失败的次数加以限制:

  1. 在fail_timeout秒内(默认为10秒),每当转发失败,server的权重都会适当调低(通过effective_weight实现);
  2. 如果转发失败次数达到了max_fails次,则接下来的fail_timeout秒内不再向该server转发任何请求;
  3. 在等待fail_timeout秒的空窗期后,server将会基于最低权重执行算法,尝试转发请求;
  4. 每成功转发1次权重加1,直到恢复至weight配置。
    这就是动态权重发挥作用的机制,它不只对RoundRobin算法有效,对于最少连接数、一致性哈希算法同样有效。接下来我们再从实现层面上回顾下这一流程,加深你对动态权重的理解。Nginx为这一功能准备了6个状态变量:
struct ngx_http_upstream_rr_peer_s {
...
    ngx_int_t                       weight;
    time_t                          accessed;
    ngx_uint_t                      max_fails;
    ngx_uint_t                      fails;
    time_t                          checked;
    time_t                          fail_timeout;
...
};

其中,weight、max_fails、fail_timeout都是server指令后的选项,它们是固定不变的。fails是窗口期的转发失败次数,accessed表示最近一次转发失败时间,而checked则是最近一次转发时间(无论成功或者失败)。
这样,当访问上游server失败时,将会把accessed和checked置为当前时间,并将fails次数加1:

peer->fails++;
peer->accessed = now;
peer->checked = now;

如果max_fails功能没有关闭(0表示关闭,默认值是1),就会通过effective_weight适当降低权重:

if (peer->max_fails) {
    peer->effective_weight -= peer->weight / peer->max_fails;
}

当执行负载均衡算法时,如果在fail_timeout秒内连续失败了max_fails次,则不再访问该server:

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

在过了fail_timeout秒的空窗期后,一旦算法再次选择了该server,就会将checked重置为当前时间:

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

一旦最终转发请求成功,就会通过accessed将fails失败次数清零:

if (peer->accessed < peer->checked) {
    peer->fails = 0;
}

注意,此时effective_weight还需要通过不断的成功来缓慢恢复权重:

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

RoundRobin算法还能控制并发连接数,你可以通过server指令后的max_conns选项设置(默认为0表示不加限制)。它的实现原理非常简单,这里不再介绍。
可见,RoundRobin算法可以柔性恢复转发错误。如果上游Server进程是复制扩展的(处理的数据相同),那么RoundRobin就是最简单有效的负载均衡算法。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇