nginx负载均衡中常见的算法及原理

1. Nginx负载均衡中常见的算法

1.1 轮询(round robin)

轮询,依次将请求分配到各个后台服务器,默认方式。

upstream backserver { 
server 192.168.31.7; 
server 192.168.31.8; 
} 

1.2 加权轮询(weight round robin)

根据权重加权依次轮询,默认为1,实现类似于LVS中的WRR,WLC等.默认时和rr效果一样.

upstream backserver { 
server 192.168.31.7 weight=10; 
server 192.168.31.8 weight=20; 
} 

1.3 源地址hash(ip_hash)

源地址hash调度方法,基于的客户端的remote_addr(源地址IPv4的前24位或整个IPv6地址)做hash计算,以实现会话保持.

upstream backserver { 
ip_hash;
server 192.168.31.7; 
server 192.168.31.8; 
} 

1.4 目的Url hash(url_hash)

hash $request_uri consistent;
根据请求的url的hash值分配服务器,当后台服务器为缓存时,效率较高.

upstream backserver { 
consistent_hash $remote_addr;
server 192.168.31.7; 
server 192.168.31.8; 
} 

1.5 最少连接数(least_conn)

least_conn;
最少连接调度算法,优先将客户端请求调度到当前连接最少的后端服务器,相当于LVS中的WLC

upstream backserver { 
least_conn;
server 192.168.31.7; 
server 192.168.31.8; 
} 

1.6 最快响应时间(fair)

根据服务器响应时间来分发,响应时间短,分发越多。

upstream backserver { 
fair;
server 192.168.31.7; 
server 192.168.31.8; 
} 

2. 源码剖析

nginx的负载均衡策略可以划分为两大类:

  • 内置策略
    在默认情况下这两种策略会编译进nginx内核,只需在nginx配置中指明参数即可。
  • 扩展策略
    默认不编译进nginx内核。

内置策略:

  • 轮询(round robin)
  • 加权轮询(weight round robin)
  • 源地址hash(ip_hash)

扩展策略:

2.1 轮询(round robin)

2.1.1 RR 请求流程图

在这里插入图片描述

2.1.2 RR 请求源码

以nginx-1.20.1版本为例
源码中的重要声明:
nginx-1.20.1/src/http/ngx_http_upstream_round_robin.h
在这里插入图片描述
current_weight: 权重排序的值,随着处理请求会动态的变化.
weight: 配置值,用来恢复初始状态。
轮询的创建过程。代码如下图:
nginx-1.20.1/src/http/ngx_http_upstream_round_robin.c
在这里插入图片描述
变量tried: tried中记录了服务器当前是否被尝试连接过。他是一个位图。如果服务器数量小于32,则只需在一个int中即可记录下所有服务器状态。如果服务器数量大于32,则需在内存池中申请内存来存储。

对该位图数组的使用可参考如下代码:
在这里插入图片描述
在这里插入图片描述

2.2 源地址hash(ip_hash)

ip_hash算法的原理很简单,根据请求所属的客户端IP计算得到一个数值,然后把请求发往该数值对应的后端。
所以同一个客户端的请求,都会发往同一台后端,除非该后端不可用了。ip_hash能够达到保持会话的效果。
ip_hash是基于round robin的,判断后端是否可用的方法是一样的。

  • 第一步,根据客户端IP计算得到一个数值。
hash1 = (hash0 * 113 + addr[0]) % 6271;
hash2 = (hash1 * 113 + addr[1]) % 6271;
hash3 = (hash2 * 113 + addr[2]) % 6271;

hash3就是计算所得的数值,它只和初始数值hash0以及客户端的IP有关。

  • 第二步,根据计算所得数值,找到对应的后端。
w = hash3 % total_weight;
while (w >= peer->weight) {
    w -= peer->weight;
    peer = peer->next;
    p++;
}

total_weight为所有后端权重之和。遍历后端链表时,依次减去每个后端的权重,直到w小于某个后端的权重。
选定的后端在链表中的序号为p。因为total_weight和每个后端的weight都是固定的,所以如果hash3值相同,则找到的后端相同。
nginx-1.20.1/src/http/modules/ngx_http_upstream_ip_hash_module.c
在这里插入图片描述

2.3 加权轮询(weight round robin)

每个后端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不用减小。
加权轮询算法可描述为:

  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=3;
    server b weight=2;
    server c weight=1;
}

2.4 目的Url hash(url_hash)

按照这个配置,每6个客户端请求中,a会被选中3次、b会被选中2次、c会被选中1次,且分布平滑。

url hash是用于提高squid命中率的一种架构算法,一般现行的架构通常是使用dns轮询或lvs等将访问量负载均衡到数台squid,这样做可以使 squid的访问量做到了均衡,但是忽略了一个重要方面–数据量。在这种架构下,每台squid的数据量虽然是一致的,但通常都是满载,并且存在数据重 复缓存的情况。如果后端服务器数据容量或者用户的访问热点数远远超过缓存机器的内存容量,甚至配置的disk cache容量,那么squid将会大量使用磁盘或者不停与后端服务器索取内容。

在新的架构下,使用nginx架载于squid之前,如果squid机器有4台,那么在这4台机器上装上nginx,nginx使用80端口,而 squid改为3128端口或其他端口。nginx的效率非常高,消耗内存也非常少,所以并不需考虑加装nginx所带来的性能损耗。然后在nginx上 配置url hash,使访问量根据url均衡分布到各台squid,根据url分流之后,每一个url就会只存在于一台squid中,每台squid的数据都会完全 不同。我们有4台机器,每台2G内存的话,原先极有可能因为数据大量重复,内存使用率仍然为2G,而现在我们经过数据均衡分布,8G内存可以达到充分利 用。

是否会存在访问不均的情况呢?是有可能的,但是根据大数原理,访问量基本可以保持一致,只要不存在单一的特别夸张的热点。

假如squid是利用squidclient来刷新数据的话,新的架构提供了更高效的方法:在后端服务器中模拟url hash的算法来找到内容所在的squid,然后对此服务器刷新内容即可。在旧的架构中,需要遍历所有的服务器,比较低效。

2.5 最少连接数(least_conn)

nginx-1.20.1/src/http/modules/ngx_http_upstream_least_conn_module.c

least_conn算法,首选遍历后端集群,比较每个后端的conns/weight,选取该值最小的后端。如果有多个后端的conns/weight值同为最小的,那么对它们采用加权轮询算法。
在一个upstream配置块中,如果有least_conn指令,表示使用least connected负载均衡算法。least_conn指令的解析函数为ngx_http_upstream_least_conn
主要作用:

  • 指定初始化此upstream块的函数uscf->peer.init_upstream
  • 指定此upstream块中server指令支持的属性
    在这里插入图片描述
名称 作用
NGX_HTTP_UPSTREAM_CREATE 检查是否重复创建,以及必要的参数是否填写
NGX_HTTP_UPSTREAM_WEIGHT server指令支持weight属性
NGX_HTTP_UPSTREAM_MAX_FAILS server指令支持max_fails属性
NGX_HTTP_UPSTREAM_FAIL_TIMEOUT server指令支持fail_timeout属性
NGX_HTTP_UPSTREAM_DOWN server指令支持down属性
NGX_HTTP_UPSTREAM_BACKUP server指令支持backup属性

初始化upstream块
执行完指令的解析函数后,紧接着会调用所有HTTP模块的init main conf函数。
在执行ngx_http_upstream_module的init main conf函数时,会调用所有upstream块的初始化函数。对于使用least_conn的upstream块,其初始化函数(peer.init_upstream)就是上一步中指定ngx_http_upstream_init_least_conn
主要作用:

  • 调用round robin的upstream块初始化函数来创建和初始化后端集群,保存该upstream块的数据
  • 指定per request的负载均衡初始化函数peer.init
    在这里插入图片描述

初始化请求的负载均衡数据
收到一个请求后,一般使用的反向代理模块(upstream模块)为ngx_http_proxy_module,其NGX_HTTP_CONTENT_PHASE阶段的处理函数为ngx_http_proxy_handler,在初始化upstream机制的ngx_http_upstream_init_request函数中,调用在第二步中指定的peer.init,主要用于初始化请求的负载均衡数据。对于least_conn,peer.init实例为ngx_http_upstream_init_least_conn_peer
主要作用:

  • 调用round robin的peer.init来初始化请求的负载均衡数据
  • 重新指定peer.get,用于从集群中选取一台后端服务器
    least_conn的per request负载均衡数据和round robin的完全一样,都是一个ngx_http_upstream_rr_peer_data_t实例。
    在这里插入图片描述
    选取一台后端服务器

一般upstream块中会有多台后端,那么对于本次请求,要选定哪一台后端呢?
这时候第三步中r->upstream->peer.get指向的函数就派上用场了:
采用least connected算法,从集群中选出一台后端来处理本次请求。 选定后端的地址保存在pc->sockaddr,pc为主动连接。
函数的返回值:

名称 作用
NGX_DONE 选定一个后端,和该后端的连接已经建立。之后会直接发送请求。
NGX_OK 选定一个后端,和该后端的连接尚未建立。之后会和后端建立连接。
NGX_BUSY 所有的后端(包括备份集群)都不可用。之后会给客户端发送502(Bad Gateway)。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.6 最快响应时间(fair)

fair采用的不是内建负载均衡使用的轮换的均衡算法,而是可以根据页面大小、加载时间长短智能的进行负载均衡。
由于不是默认策略.需要单独安装

猜你喜欢

转载自blog.csdn.net/qq_29974229/article/details/121280826