Nginx学习——负载均衡

负载均衡

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地址的请求特别多,那么将导致某台后端服务器的压力可能非常大,而其他后端服务器却空闲的不均衡情况。

Nginx学习——负载均衡,布布扣,bubuko.com

时间: 2024-10-21 21:13:22

Nginx学习——负载均衡的相关文章

Nginx + Tomcat 负载均衡群集 【内附源码包】

前言: 1·在之前都学习 Linux 的一些服务和搭建,都很熟悉 !这篇文章主要讲Nginx.Tomcat与 Nginx + Tomcat 负载均衡群集.2·在各种网站服务器软件中,除了 Apache HTTP Server 外,还有一款轻量级的 HTTP 服务器软件--Nginx,它是由俄罗斯 Lgor Sysoev(伊戈尔·赛索耶夫)开发. Nginx 的优点: 1·稳定性2·系统资源消耗低3·高并发链接的处理能力 (30000~50000 个并发请求),核心优点!4·反向代理服务器5·安装

[转帖]利用nginx实现负载均衡 | 哈希算法,sticky模块实现session粘滞

利用nginx实现负载均衡 | 哈希算法,sticky模块实现session粘滞 2018年08月02日 10:06:03 Minza 阅读数 483 https://blog.csdn.net/ha_weii/article/details/81350087 学习一下如何使用sticky 版权声明:创作不易,转载请注明出处 https://blog.csdn.net/ha_weii/article/details/81350087 一,普通的负载均衡 1,启动nginx服务器 之前已经把/us

Nginx做负载均衡时session共享问题详解

用nginx做负载均衡时,同一个IP访问同一个页面会被分配到不同的服务器上,如果session不同步的话,就会出现很多问题,比如说最常见的登录状态. 再者Nginx连接Memcached集群时,Nignx的请求从memcached服务器中根据key获得了value则直接返回value,如果没有获得到value则去MySQL中查询再返回. location / { set $memcached_key "$request_uri"; #设置请求memcached服务器的key memca

Nginx的负载均衡方案详解

Nginx的负载均衡方案详解 作者:chszs,转载需注明.博客主页:http://blog.csdn.net/chszs Nginx的负载均衡方案有: 1.轮询 轮询即Round Robin,根据Nginx配置文件中的顺序,依次把客户端的Web请求分发到不同的后端服务器. 配置的例子如下: http{ upstream sampleapp { server <<dns entry or IP Address(optional with port)>>; server <&l

使用nginx+Apache负载均衡及动静分离

使用nginx+Apache负载均衡及动静分离 介绍    LB负载均衡集群分两类: LVS (四层)和 nginx或haproxy (七层)    客户端都是通过访问分发器的VIP来访问网站 在七层中的网站页面有: .php .html .png .jpeg .jsp 等, 有动态页面有静态页面. 需要在应用层基于不同的应用进行分发. 一:实验拓扑图:     二:实验目标 实战:使用Apache+nginx实现动静分离的负载均衡集群 三:实验环境 主机作用分类 主机名 IP地址 安装软件 N

基于nginx的负载均衡概述与实现

前言: 前面我们提到了lvs和keepalived结合起来的高可用负载均衡,lvs根据原目ip地址及端口将其调度转发至后端 的某个主机,是一种四层的实现,因为lvs是四层的,所以不会受限于套接字或打开的文件数量.不过,如果我们想实现一些更高阶的功能,lvs就显得力不从心了,比如基于uri,cookie,header头部信息的负载均衡,此时我们就可以选择一些7层的负载均衡实现,比如nginx或haproxy等.本次我们就先来讲讲nginx的负载均衡把~ 正文: 其实,如果对lvs的各种类型和调度有

【Nginx】配置Nginx的负载均衡

阅读目录 参考的优秀文章 在本机运行2个Tomcat Nginx的负载均衡配置 参考的优秀文章 tomcat配置文件server.xml详解 AJP协议总结与分析 Using nginx as HTTP load balancer 在本机运行2个Tomcat 现需要运行两个Tomcat,监听不同端口,让Nginx作负载均衡跳转过来.Tomcat版本:apache-tomcat-7.0.69-windows-x64.zip 要在一台机器运行两个Tomcat,要解决端口的冲突,我们只需要D:\gre

Linux下利用nginx实现负载均衡

linux下利用nginx实现负载均衡 前提条件: 1,安装好jdk 2,安装好tomcat和nginx(可以参考我前两篇文章) 满足前提条件后,要用nginx实现负载均衡,主要是靠配置nginx的配置文件. 我们要实现的架构图如下: 1.分别部署3个tomcat,端口分别为8080,8081,8082 drwxr-xr-x 9 root root 4096 Mar 11 13:41 tomcat8-8080drwxr-xr-x 9 root root 4096 Mar 11 17:27 tom

nginx实现负载均衡

一.测试环境 OS: rhel6 Lib1: 192.168.20.106   A服务器 Lib2: 192.168.20.107   B服务器 Lib3: 192.168.20.108   C服务器 部署: A服务器做为主服务器,域名直接解析到A服务器(192.168.20.106)上,由A服务器负载均衡到B服务器(192.168.20.107)与C服务器(192.168.20.108)上. upstream是Nginx的HTTP Upstream模块,这个模块通过一个简单方法来实现在轮询和客