Nginx负载均衡策略

负载均衡技术是将大量的客户端请求通过特定的策略分配到集群中的节点,实现快速响应的应用技术。在应对高并发的应用请求时,单节点的应用服务计算能力有限,无法满足客户端的响应需求,通过负载均衡技术,可以将请求分配到集群中的多个节点中,让多个节点分担高并发请求的运算,快速完成客户端的请求响应。

1、轮询

轮询(Round Robin)策略是 Nginx 配置中默认的负载均衡策略,该策略将客户端的请求依次分配给后端的服务器节点,对后端集群中的服务器实现轮流分配。轮询策略绝对均衡,且实现简单,但也会因后端服务器处理能力的不同而影响整个集群的处理性能。

1) 加权轮询

在 Nginx 的轮询策略中,为了避免因集群中服务器性能的差异对整个集群性能造成影响,在轮询策略的基础上增加了权重参数,让使用者可以手动根据集群中各服务器的性能将请求数量按照权重比例分配给不同的被代理服务器。

2) 平滑轮询

在加权轮询策略中,会按照权重的高低分配客户端请求,若按照高权重分配完再进行低权重分配的话,可能会出现的情况是高权重的服务器一直处于繁忙状态,压力相对集中。Nginx 通过平滑轮询算法,使得上游服务器组中的每台服务器在总权重比例分配不变的情况下,均能参与客户端请求的处理,有效避免了在一段时间内集中将请求都分配给高权重服务器的情况发生。

配置样例如下:
http {
    upstream backend {
        server a weight=5;
        server b weight=1;
        server c weight=1;
    }

    server {
        listen 80;

        location / {
            proxy_pass http://backend;
        }
    }
}
配置样例中 Nginx 平滑轮询策略计算过程如下。
  • 当前配置中 a,b,c 服务器的配置权重为 {5,1,1};
  • 配置样例中 Nginx 平滑轮询计算过程如下表所示。

轮询次数 当前权重 选择后权重 选择节点
0 {0, 0, 0} {0, 0, 0} --
1 {5, 1, 1} {-2, 1, 1} a
2 {3, 2, 2} {-4, 2, 2} a
3 {1, 3, 3} {1, -4, 3} b
4 {6, -3, 4} {-1, -3, 4} a
5 {4, -2, 5} {4, -2, -2} c
6 {9, -1, -1} {2, -1, -1} a
7 {7, 0, 0} {0, 0, 0} a

关于上表有以下几点需要说明:
  • 有效权重(effective_weight),初始值为配置文件中权重的值,会因节点的健康状态而变化;
  • 当前权重(current_weight),节点被选择前的权重值,由上一个选择后权重值及各节点与自己的有效权重值相加而得;
  • 选择后权重,所有节点中权重最高节点的当前权重值为其初始值与有效总权重相减的值,其他节点的权重值不变;
  • 有效总权重为所有节点中非备份、非失败状态的服务器的有效权重之和;
  • 根据上述平滑轮询算法,选择节点顺序为 {a,a,b,a,c,a,a}。

2、一致性哈希

Nginx 启用哈希的负载均衡策略,是用 hash 指令来设置的。哈希策略方法可以针对客户端访问的 URL 计算哈希值,对相同的 URL 请求,Nginx 可以因相同的哈希值而将其分配到同一后端服务器。当后端服务器为缓存服务器时,将极大提高命中率,提升访问速度。

一致性哈希的优点是,可以使不同客户端的相似请求发送给同一被代理服务器,当被代理服务器为缓存服务器场景应用时,可以极大提高缓存的命中率。

一致性哈希的缺点是,当上游服务器组中的节点数量发生变化时,将导致所有绑定被代理服务器的哈希值重新计算,影响整个集群的绑定关系,产生大量回源请求。

配置样例如下:
http {
    upstream backend {
        hash $request_uri;  # 以客户端请求URI为计算哈希值的key
        server a weight=5;
        server b weight=1;
        server c weight=1;
    }

    server {
        listen 80;

        location / {
            proxy_pass http://backend;
        }
    }
}
配置样例中 Nginx 哈希策略计算过程如下。
  • 首先会根据 $request_uri 计算哈希值;
  • 根据哈希值与配置文件中非备份状态服务器的总权重计算出哈希余数;
  • 按照轮询策略选出初始被代理服务器,如果哈希余数大于初始被代理服务器的权重,则遍历轮询策略中被代理服务器列表;
  • 当遍历轮询策略中被代理服务器列表时,要用哈希余数依次减去轮询策略中的上一个被代理服务器的权重,直到哈希余数小于某个被代理服务器的权重时,该被代理服务器被选出;
  • 若循环 20 次仍无法选出,则使用轮询策略进行选择。

针对哈希算法的缺点,Nginx 提供了 consistent 参数启用一致性哈希(Consistent Hash)负载均衡策略。Nginx 采用的是 Ketama 一致性哈希算法,使用一致性哈希策略后,当上游服务器组中的服务器数量变化时,只会影响少部分客户端的请求,不会产生大量回源。

Nginx 一致性哈希计算过程如下。

1) 根据配置文件中非备份状态服务器的总权重乘以 160 计算出总的虚拟节点数量,初始化虚拟节点数组。

2) 遍历轮询策略中的被代理服务器列表,根据每个服务器的权重数乘以160得出该服务器的虚拟节点数量,并根据服务器的 HOST 和 PORT 计算出该服务器的基本哈希(base_hash)。

3) 循环每个服务器虚拟节点总数次数,由基本哈希(base_hash)值与上一个虚拟节点的哈希值(PREV_HASH)依次计算出所有属于该服务器的虚拟节点哈希值,并把虚拟节点哈希值与服务器映射关系保存在虚拟节点哈希值数组中。

4) 对虚拟节点哈希值数组进行排序去重处理,得到新的有效虚拟节点哈希值数组。

配置样例如下:
http {
    upstream backend {
        hash $request_uri consistent;       # 以客户端请求URI为计算哈希值的key,使用一致性
                                                # 哈希算法
        server a weight=1;
        server b weight=1;
        server c weight=1;
        server c weight=1;
    }

    server {
        listen 80;

        location / {
            proxy_pass http://backend;
        }
    }
}
配置样例中 Nginx 一致性哈希策略计算过程如下。
  • 首先根据 $request_uri 计算哈希值;
  • 通过二分法,快速在虚拟节点列表中选出该哈希值所在范围的最大虚拟节点哈希值;
  • 通过虚拟节点哈希值与虚拟节点集合总数取余,获得对应的服务器作为备选服务器;
  • 遍历轮询策略中被代理服务器列表,判断备选服务器的有效性,选出服务器;
  • 若循环 20 次仍无法选出,则使用轮询策略进行选择。

3、IP 哈希

IP 哈希(IP Hash)负载均衡策略根据客户端IP计算出哈希值,然后把请求分配给该数值对应的被代理服务器。在哈希值不变且被代理服务器可用的前提下,同一客户端的请求始终会被分配到同一台被代理服务器上。IP 哈希负载均衡策略常被应用在会话(Session)保持的场景。

HTTP 客户端在与服务端交互时,因为 HTTP 协议是无状态的,所以任何需要上下文逻辑的情景都必须使用会话保持机制,会话保持机制是通过客户端存储由唯一的 Session ID 进行标识的会话信息,每次与服务器交互时都会将会话信息提交给服务端,服务端依照会话信息实现客户端请求上下文的逻辑关联。

会话信息通常存储在被代理服务器的内存中,如果负载均衡将客户端的会话请求分配给其他被代理服务器,则该会话逻辑将因为会话信息失效而中断。所以为确保会话不中断,需要负载均衡将同一客户端的会话请求始终都发送到同一台被代理服务器,通过会话保持实现会话信息的有效传递。

配置样例如下:
http {
    upstream backend {
        ip_hash;            # 启用IP哈希负载均衡策略
        server a weight=5;
        server b weight=1;
        server c weight=1;
    }

    server {
        listen 80;

        location / {
            proxy_pass http://backend;
        }
    }
}
配置样例中 Nginx 的 IP 哈希策略计算过程如下。
  • 在多层代理的场景下,请确保当前 Nginx 可获得真实的客户端源 IP;
  • 首先会根据客户端的 IPv4 地址的前三个八位字节或整个 IPv6 地址作为哈希键计算哈希值;
  • 根据哈希值与配置文件中非备份状态服务器的总权重计算出哈希余数;
  • 按照轮询策略选出初始被代理服务器,如果哈希余数大于初始被代理服务器的权重,则遍历轮询策略中被代理服务器列表,否则初始被代理服务器将被选出;
  • 当遍历轮询策略中被代理服务器列表时,要用哈希余数依次减去轮询策略中的上一个被代理服务器的权重,直到哈希余数小于某个被代理服务器的权重时该被代理服务器被选出;
  • 若循环 20 次仍无法选出,则使用轮询策略进行选择。

4、最少连接

默认配置下轮询算法是把客户端的请求平均分配给每个被代理服务器,每个被代理服务器的负载大致相同,该场景有个前提就是每个被代理服务器的请求处理能力是相当的。如果集群中某个服务器处理请求的时间比较长,那么该服务器的负载也相对增高。在最少连接(least_conn)负载均衡策略下,会在上游服务器组中各服务器权重的前提下将客户端请求分配给活跃连接最少的被代理服务器,进而有效提高处理性能高的被代理服务器的使用率。

配置样例如下:
upstream backend {
    least_conn;         # 启用最少连接负载均衡策略
    server a weight=4;
    server b weight=2;
    server c weight=1;
}

server {
    listen 80;
    location / {
        proxy_pass http://backend;
    }
}
配置样例中 Nginx 最少连接策略计算过程如下。
  • 遍历轮询策略中被代理服务器列表,比较各个后端的活跃连接数(conns)与其权重(weight)的比值,选取比值最小者分配客户端请求;
  • 如果上一次选择了 a 服务器,则当前请求将在 b 和 c 服务器中选择;
  • 设 b 的活跃连接数为 100,c 的活跃连接数为 60,则 b 的比值(conns/weight)为 50,c 的比值(conns/weight)为 60,因此当前请求将分配给 b。

5、随机负载算法

在 Nginx 集群环境下,每个 Nginx 均通过自身对上游服务器的了解情况进行负载均衡处理,这种场景下,很容易出现多台 Nginx 同时把请求都分配给同一台被代理服务器的场景,该场景被称为羊群行为(Herd Behavior)。

Nginx 基于两种选择的力量(Power of Two Choices)原理,设计了随机(Random)负载算法。该算法使 Nginx 不再基于片面的情况了解使用固有的负载均衡策略进行被代理服务器的选择,而是随机选择两个,在经过比较后进行最终的选择。随机负载算法提供了一个参数 two,当这个参数被指定时,Nginx 会在考虑权重的前提下,随机选择两台服务器,然后用以下几种方法选择一个服务器。
  • 最少连接数,配置指令为 least_conn,默认配置;
  • 响应头最短平均时间,配置指令为 least_time=header,仅对商业版本有效;
  • 完整请求最短平均时间,配置指令为 least_time=last_byte,仅对商业版本有效。

配置样例如下:
upstream backend {
    random two least_conn;
    server backend1.example.com;
    server backend2.example.com;
    server backend3.example.com;
    server backend4.example.com;
}
在只有单台 Nginx 服务器时,一般不建议使用随机负载算法。