Nginx学习——负载均衡

原创 2014年07月31日 20:45:07

负载均衡

Nginx提供了较多的负载均衡策略,包括加权轮询、IP哈希、fair、一致哈希等。前两个是Nginx官方源码内置的策略,而后面几个都是第三方模块,所以下面我们重点来看前两个内置策略。


Nginx默认采用round_robin加权算法,如果要采用IP哈希策略,那么必须在Nginx的配置文件里通过配置指令ip_hash明确指定。


当整个http配置块被Nginx解析完毕之后,会调用各个http模块对应的初始函数。对于模块ngx_http_upstream_module而言,对应的main配置初始函数是ngx_http_upstream_init_main_conf(),在这个函数中有这样一段代码:

for (i = 0; i < umcf->upstreams.nelts; i++) {

        init = uscfp[i]->peer.init_upstream ? uscfp[i]->peer.init_upstream:
                                            ngx_http_upstream_init_round_robin;

        if (init(cf, uscfp[i]) != NGX_OK) {
            return NGX_CONF_ERROR;
        }
}


默认采用加权轮询策略的原因就是在于上述代码中的init赋值一行。如果用户没有做任何策略选择,那么执行的策略初始函数为ngx_http_upstream_init_round_robin,也就是加权轮询策略。否则的话执行的是uscfp[i]->peer.init_upstream指针函数,如果有配置执行ip_hash ,那么就是ngx_http_upstream_init_ip_hash()。

加权轮询

加权轮询,直观上理解就是计算各个后端服务器的当前权值,然后选择得分最高的服务器处理当前请求,Nginx的处理大致如此,但是在具体实现时考虑很多其他细节,比如服务器可能具有不同的权值,某个服务器多次连接失败或处理出错后则在一定时间内不再参与被选择等。

准备工作

使用加权轮询时,upstream上下文内server配置可带的参数中,我们关心的有如下几个:
1.weight:权值,默认值为1,与加权轮询策略配合使用
2. fail_timeout和fail_timeout:他们配合使用,默认值分别为1和10s。具体含义是指,如果某台后段服务器在fail_timeout时间内发生了fail_timeout次连接失败,那么该后端服务器在这fail_timeout时间内就不在参与被选择,知道fail_timeout时间后才重新加入而有机会被选择,其直白意思也就是请先休息一会,然后再来。
3.backup:备机,平常不被选择,只有当其他所有非备机全部不可用时才被使用。值得宕略里,因为他会扰乱哈希的结果而违背ip_hash的初衷。

4.down:即主动表示其未宕机状态,不参与被选择。


需要注意的是,配置文件中出现的参数只能和某些策略配合使用,所以如果发现某参数没有生效,则应该检查这一点。在配置解析的过程中,这些选项设置都被转换为Nginx内对于的变量值,对应的结构体ngx_http_upstream_server_t如下(ngx_http_upstream.h):
typedef struct {
    ngx_addr_t                      *addrs;//指向存储IP地址的数组的指针,host信息(对应的是 ngx_url_t->addrs )
    ngx_uint_t                       naddrs;//与第一个参数配合使用,数组元素个数(对应的是 ngx_url_t->naddrs )
    ngx_uint_t                       weight;
    ngx_uint_t                       max_fails;
    time_t                           fail_timeout;

    unsigned                         down:1
    unsigned                         backup:1;
} ngx_http_upstream_server_t;

这里函数ngx_http_upstream_init_round_robin()所做的工作除了把配置项解析后的结果转存到对应的变量以外,主要还有以下几项:创建后端服务器列表,并且将非后背服务器与后背服务器分开进行各自单独的列表,每一个后段服务器用一个结构体ngx_http_upstream_rr_peer_t对应,列表最前面需要带有一些head信息,所以用ngx_http_upstream_rr_peers_t结构体对应。非后背服务器列表挂载在us->peer.data字段下,而后背服务器列表挂载在非后背服务器列表head于里的next字段下。两个列表的服务器会按初始权重进行排序,高权重的在前面。

选择后端服务器

全局初始化完成之后,当一个客户端请求过来时,Nginx就要选择合适的后端服务器来处理该请求。在正式开始选择前,Nginx还要单独为本轮选择做一些初始化(针对一个客户端请求,nginx会进行多次尝试选择,尝试全部失败后才返回502错误,所以注意一轮选择与一次选择的区别)。

在ngx_http_upstream_init_round_robin()中,有如下语句


us->peer.init = ngx_http_upstream_init_round_robin_peer; //回调指针设置 

它的调用位置是函数ngx_http_upstream_init_request中,即在针对每个请求选择后端服务器之前被调用。下面对ngx_http_upstream_init_round_robin_peer做了什么做解释,它除了完成初始化工作外,核心是设置回调函数,部分代码如下:


//回调函数设置
    r->upstream->peer.get = ngx_http_upstream_get_round_robin_peer;
    r->upstream->peer.free = ngx_http_upstream_free_round_robin_peer;
    r->upstream->peer.tries = rrp->peers->number;

对后端服务器进行一次选择的逻辑是现在ngx_http_upstream_get_round_robin_peer内,流程图和代码如下:


对于只有一台后端服务器的情况,Nginx直接选择它并返回。如果有多台后端服务器,Nginx会循环调用函数ngx_http_uostream_get_peer()按照各台服务器的当前权值进行选择。如果对非后备服务器全部选择失败的话,此时开始尝试选择后备服务器,这同样是对一个服务器列表进行选择,所以处理情况与对非后备服务器处理情况进行选择的逻辑一致。如果对后备服务器选择也失败,那么ngx_http_upstream_get_round_robin_peer返回NGX_BUSY,意味着当前没有后端服务器来处理该请求。


后端服务器权值计算在函数ngx_http_uostream_get_peer()中,这个函数中还有一个变量total,但要理解这个函数的工作原理,先要区分下表示服务的ngx_http_upstream_rr_peer_t结构体中的一下三个成员变量,

ngx_int_t                       current_weight;
    ngx_int_t                       effective_weight;
    ngx_int_t                       weight;

它们在函数ngx_http_upstream_init_round_robin中被初始化:
for (i = 0; i < us->servers->nelts; i++) {
            for (j = 0; j < server[i].naddrs; j++) {
                if (server[i].backup) {
                    continue;
                }

                peers->peer[n].weight = server[i].weight;
                peers->peer[n].effective_weight = server[i].weight;
                peers->peer[n].current_weight = 0;
                n++;
            }
        }

        /* backup servers */
        for (i = 0; i < us->servers->nelts; i++) {
            for (j = 0; j < server[i].naddrs; j++) {
                if (!server[i].backup) {
                    continue;
                }

                backup->peer[n].weight = server[i].weight;
                backup->peer[n].effective_weight = server[i].weight;
                backup->peer[n].current_weight = 0;

                n++;
            }
        }

     /* an upstream implicitly defined by proxy_pass, etc. */
    for (i = 0; i < u.naddrs; i++) {
        peers->peer[i].weight = 1;
        peers->peer[i].effective_weight = 1;
        peers->peer[i].current_weight = 0;
    }

可以看到weight、effective_weight都是初始化为配置项中的weight值。current_weight初始化为0.

下面分析这三个变量在负载均衡过程中的变化:


weight的值在整个运行过程中不发生变化。total变量记录了针对一个服务列表的一次轮询过程中轮询到的所有服务的effective_weight总和。在每一次针对服务列表的轮询之前会置为为0。遍历服务列表的过程中,每遍历到一个服务,会在该服务的current_weight上加上其对应的effective_weight。这个是累加。如果对统一的服务列表进行另一次轮询,那么会在前面计算的current_weight的基础之上再加上effective_weight。


轮询策略是取current_weight最大的服务器。每次取到后端服务(用best表示)后,都会把该对象peer的current_weight减去total的值。因为该服务刚被选中过,因此要降低权值。


关于effective_weight的变化,有两处,一个是在函数ngx_http_upstream_get_peer中:

 //服务正常,effective_weight 逐渐恢复正常    
        if (peer->effective_weight < peer->weight) {
            peer->effective_weight++;
        }

另一处是在释放后端服务的函数ngx_http_upstream_free_round_robin_peer中:
 if (peer->max_fails) {
             //服务发生异常时,调低effective_weight
            peer->effective_weight -= peer->weight / peer->max_fails;
        }

权重高的会优先被选中,而且被选中的频率也更高。权重低的也会由于权重逐渐增长获得被选中的机会,如下表所示:


selected server

current_weight beforeselected

current_weight afterselected

a

{ 5, 1, 2 }

{ -3, 1, 2 }

c

{ 2, 2, 4 }

{ 2, 2, -4 }

a

{ 7, 3, -2 }

{ -1, 3, -2 }

a

{ 4, 4, 0 }

{ -4, 4, 0 }

b

{ 1, 5, 2 }

{ 1, -3, 2 }

a

{ 6, -2, 4 }

{ -2, -2, 4 }

c

{ 3, -1, 6 }

{ 3, -1, -2 }

a

{ 8, 0, 0 }

{ 0, 0, 0 }


释放后端服务器

连接后端服务器并且正常处理当前客户端请求后需释放后端服务器。如果在某一轮选择里,某次选择的服务器因连接失败或请求处理失败而需要重新进行选择,那么这时候就需要做一些额外的处理。

整个加权轮询的流程:

整个加权轮询的流程图如下:


1)首先是全局初始化,由函数ngx_http_upstream_init_round_robin完成,它在函数ngx_http_upstream_init_main_conf中被调用。
2)收到客户请求之后,针对当前请求进行初始化,完成此功能的函数是ngx_http_upstream_init_round_robin_peer,它在函数ngx_http_upstream_init_request中被调用。
3)然后是针对每个请求选择后端服务器,实现此功能的函数是ngx_http_upstream_get_round_robin_peer。它在函数ngx_event_connect_peer中被调用。
4)之后是测试连接ngx_http_upstream_test_connect。它在函数ngx_http_upstream_send_request被调用。
5)如果测试成功,继续后续处理,并释放后端服务器。
如果测试失败,调用ngx_http_upstream_next函数,这个函数可能再次调用peer.get调用别的连接。
6)函数ngx_http_upstream_connect中会调用ngx_event_connect_peer,进而调用ngx_http_upstream_get_round_robin_peer再次选择后端服务器。

IP哈希

用IP负载均衡策略时,当一个客户端请求过来时,Nginx将调用ngx_http_upstream_init_ip_hash_peer()做初始化。之所以这样做是因为在多次哈希选择失败后,Nginx会将选择策略退化到加权轮询。这里会设置ngx_http_upstream_get_ip_hash_peer以在便收到请求时调用。同时会转存Ipv4中三个字节,因为后面在具体的哈希计算时只会用到3个字节。


ngx_http_upstream_get_ip_hash_peer在会计算哈希值,并根据哈希值得到被选中的后端服务器,判断其是否可用,如果可用则保存服务器地址,不可用则在上次哈希结果的基础上再哈希。如果哈希选择失败20次以上或质疑一台后端服务器,此时采用轮询策略。


流程图如下:

两种策略对比:

加权轮询策略
优点:适用性更强,不依赖于客户端的任何信息,完全依靠后端服务器的情况来进行选择。能把客户端请求更合理更均匀地分配到各个后端服务器处理。
缺点:同一个客户端的多次请求可能会被分配到不同的后端服务器进行处理,无法满足做会话保持的应用的需求。

IP哈希策略
优点:能较好地把同一个客户端的多次请求分配到同一台服务器处理,避免了加权轮询无法适用会话保持的需求。
缺点:当某个时刻来自某个IP地址的请求特别多,那么将导致某台后端服务器的压力可能非常大,而其他后端服务器却空闲的不均衡情况。

负载均衡(Load Balancing)学习笔记(一)

概述在分布式系统中,负载均衡(Load Balancing)是一种将任务分派到多个服务端进程的方法。例如,将一个HTTP请求派发到实际的Web服务器中执行的过程就涉及负载均衡的实现。一个HTTP请求到...
  • lihao21
  • lihao21
  • 2016年12月27日 23:42
  • 3227

负载均衡学习笔记

负载均衡学习笔记 负载均衡的目的:提高效率和性能。我认为将不同的应用或者应用的不同部分分离开,增强各自的处理能力,是负载均衡最核心的理念。比如将数据库服务器和web服务器分离就是负载均衡的一种。 ...
  • huliwho
  • huliwho
  • 2016年07月03日 18:53
  • 1992

负载均衡学习2

网站是发展初期,nginx只代理了后端一台服务器,但由于网站名气大涨访问的人越来越多一台服务器实在是顶不住,于是我们加了多台服务器,那么多台服务器又怎么配置代理呢,这里以两台服务器为案例,为大家做演示...
  • lihe460186709
  • lihe460186709
  • 2016年11月08日 19:29
  • 124

nginx作为php站点的负载均衡实践

nginx负载均衡
  • linux4fun
  • linux4fun
  • 2015年03月12日 15:26
  • 2801

Nginx反向代理实现负载均衡总结

Nginx反向代理实现负载均衡总结: 从Nginx的诞生来源就可以知道,它是为了解决大数据量高并发访问而产生的,这也要感谢Nginx的开发者,使Nginx现今成为了LNMP重要的成员。好了,废话不多说...
  • why_2012_gogo
  • why_2012_gogo
  • 2016年03月23日 22:57
  • 6157

负载均衡技术学习

http://blog.cnr.cn/18/viewspace-16814.html Q: 服务器负载均衡有哪些实现方法?  A: 实现服务器负载均衡有多种方法,常见的方法有:  1.基...
  • naughty610
  • naughty610
  • 2012年03月04日 23:54
  • 3449

几种负载均衡技术的实现

【协议层】http重定向协议实现负载均衡 原理:根据用户的http请求计算出一个真实的web服务器地址,并将该web服务器地址写入http重定向响应中返回给浏览器,由浏览器重新进行访问。   如图: ...
  • mengdonghui123456
  • mengdonghui123456
  • 2017年01月02日 20:51
  • 5130

Nginx HTTP负载均衡示例

Nginx是一个高性能的HTTP和反向代理服务器,通过其本身的Upstream模块,我们可以将其作为7层负载均衡服务器使用,其支持的负载均衡策略如下: 1,轮询:将请求依次轮询发给每个服务器 2,最少...
  • a19881029
  • a19881029
  • 2016年07月23日 01:19
  • 2165

搭建nginx负载均衡

我们今天来搭建一下nginx的负载均衡服务器。下面让我们先看一下一个简单的小图, 在我们测试的方面,我们需要使用3台服务器进行测试,一个主服务器,进行分配,其他两个作为web服务。 在主服...
  • php_younger
  • php_younger
  • 2017年02月06日 11:04
  • 1278

负载均衡学习笔记

负载均衡学习笔记最近在学习负载均衡原理,看了大量的文章和相关知识,有很大的收获,做一个简要总结,一来回顾自己所学的知识,二来对所看所学知识整理,供后来者参考借鉴,如有错误的地方,还请指出,学生阶段,考...
  • u013291818
  • u013291818
  • 2017年02月11日 18:28
  • 225
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Nginx学习——负载均衡
举报原因:
原因补充:

(最多只允许输入30个字)