【NGINX--2】高性能负载均衡
1、HTTP 负载均衡
将负载分发到两台或多台 HTTP 服务器。
在 NGINX 的 HTTP 模块内使用 upstream 代码块对 HTTP 服务器实施负载均衡:
upstream backend {
server 10.10.12.45:80 weight=1;
server app.example.com:80 weight=2;
server spare.example.com:80 backup;
}
server {
location / {
proxy_pass http://backend;
}
}
该配置对端口 80 的两台 HTTP 服务器实施负载均衡,然后再将另一台服务器定义为backup,以便在两台主服务器不可用时发挥作用。weight 参数指示 NGINX 向第二台服务器传输两倍的请求,它的默认值为 1。
详解
HTTP 的 upstream 模块控制着 HTTP 负载均衡。该模块定义了一个目标池 —— 它可以是 Unix 套接字、IP 地址和 DNS(域名服务)记录的任意组合,也可以是它们的混合使用配置。upstream 模块还定义了如何将任一个请求分发给任何上游(upstream)服务器。
每个上游目标都通过 server 指令在上游池中进行定义。server 指令接收 Unix 套接字、IP 地址或 FQDN(全限定域名)以及一些可选的参数。可选参数能够增强对请求路由的控制。这包括均衡算法中服务器的 weight 参数(无论服务器处于待机模式、可用还是不可用),以及确定服务器是否不可用的参数。NGINX Plus 还提供了许多其他好用的参数,例如对服务器的连接限制、高级 DNS 解析控制以及在服务器启动后缓慢增加与服务器的连接等等。
2、TCP 负载均衡
将负载分发到两台或多台 TCP 服务器。
在 NGINX 的 stream 模块内使用 upstream 代码块对 TCP 服务器实施负载均衡:
stream {
upstream mysql_read {
server read1.example.com:3306 weight=5;
server read2.example.com:3306;
server 10.10.12.34:3306 backup;
}
server {
listen 3306;
proxy_pass mysql_read;
}
}
此示例中的 server 代码块指示 NGINX 侦听 TCP 端口 3306,并对两个 MySQL 数据库读取副本实施负载均衡,同时将另一台服务器定义为 backup,以便在主服务器崩溃时传输流量。
此配置不会被添加到 conf.d 文件夹中,因为该文件夹包含在 http 代码块中;您应该另行创建名为 stream.conf.d 的文件夹,打开 nginx.conf 文件中的 stream 代码块,添加新文件夹以支持 stream 配置。示例见下。
/etc/nginx/nginx.conf 配置文件中:
user nginx;
worker_processes auto;
pid /run/nginx.pid;
stream {
include /etc/nginx/stream.conf.d/*.conf;
}
名为 /etc/nginx/stream.conf.d/mysql_reads.conf 的文件可能包含以下配置:
upstream mysql_read {
server read1.example.com:3306 weight=5;
server read2.example.com:3306;
server 10.10.12.34:3306 backup;
}
server {
listen 3306;
proxy_pass mysql_read;
}
详解
http 和 stream 上下文之间的主要区别在于它们在 OSI 模型的不同层上运行。http 上下文在应用层(七层)运行,stream 在传输层(四层)运行。这并不意味着 stream 上下文不能通过一些巧妙的脚本获得应用感知能力,而是说 http 上下文是专门为了完全理解HTTP 协议而设计的,stream 上下文默认情况下只能对数据包进行路由和负载均衡。
TCP 负载均衡由 NGINX 的 stream 模块定义。stream 模块与 HTTP 模块一样,允许您定义上游(upstream)服务器池并配置侦听服务器。在配置服务器侦听给定端口时,您必须定义待侦听的端口或者地址加端口(可选)。然后您必须配置目标服务,无论这是连接另一个地址的直接反向代理还是上游资源池。
配置中有许多选项可以改变 TCP 连接反向代理的属性,包括 SSL/TLS 验证限制、超时和 keepalive 等。这些代理选项的一些值可以是(或者包含)变量,例如下载速率、验证 SSL/TLS 证书时使用的名称等。
TCP 与 HTTP 负载均衡中的 upstream 指令非常相似,它们均将上游资源定义为服务器,配置格式同样为 Unix 套接字、IP 或 FQDN,此外服务器 weight 参数、最大连接数、DNS 解析器、连接数缓增期以及判断服务器是激活状态、故障状态还是备用模式的参数也都相似。
3、UDP 负载均衡
将负载分发到两台或多台 UDP 服务器。
在 NGINX 的 stream 模块内使用 upstream 代码块(定义为 udp)对 UDP 服务器实施负载均衡:
stream {
upstream ntp {
server ntp1.example.com:123 weight=2;
server ntp2.example.com:123;
}
server {
listen 123 udp;
proxy_pass ntp;
}
}
这部分配置对使用 UDP 协议的两台上游(upstream)网络时间协议(NTP)服务器实施了负载均衡。UDP 负载均衡的指定非常简单,只需使用 listen 指令中的 udp 参数便可。
如果进行负载均衡的服务需要在客户端和服务器之间来回发送多个数据包,则可以指定 reuseport 参数。例如,OpenVPN、互联网语音协议(VoIP)、虚拟桌面解决方案和数据报传输层安全(DTLS)都是这种类型的服务。下面举例说明了如何使用NGINX 处理 OpenVPN 连接并将其代理到本地运行的 OpenVPN 服务:
stream {
server {
listen 1195 udp reuseport;
proxy_pass 127.0.0.1:1194;
}
}
详解
您可能会问:“我的 DNS A 或服务记录(SRV 记录)中已经有多个主机了,又何必再要负载均衡器呢?”原因是我们不仅有备选的负载均衡算法,我们还可以对 DNS 服务器本身实施负载均衡。UDP 服务构成了我们在网络系统中依赖的许多服务,例如 DNS、NTP、QUIC、HTTP/3 和 VoIP。UDP 负载均衡可能对某些企业来说不太常见,但在大型环境中十分有用。
与 TCP 类似,您可以在 stream 模块中找到 UDP 负载平衡,并以同样的方式完成大部分配置。两者的主要区别在于,UDP 负载均衡的 listen 指令指定打开的套接字用于处理数据报。此外,在处理数据报时,UDP 负载均衡还提供了 TCP 所没有的一些其他指令,例如 proxy_response 指令,它向 NGINX 指定了可以从上游服务器发送多少预期的响应。默认情况下,除非达到 proxy_timeout 限制,否则这一数量是无限的。proxy_timeout 指令设置了在连接关闭之前,客户端或代理服务器连接连续进行两次读取或写入操作之间的时间。
reuseport 参数指示 NGINX 为每个 worker 进程创建一个单独的侦听套接字。这允许内核在 worker 进程之间分发传入的连接,以处理在客户端和服务器之间发送的多个数据包。reuseport 功能仅适用于 Linux kernels 3.9 及更高版本、DragonFly BSD、FreeBSD 12 及更高版本。
4、负载均衡方式
轮询负载均衡不适合您的用例,因为您有异构工作负载或服务器池
使用 NGINX 的负载均衡方法之一,例如最少连接、最短时间、通用哈希、随机算法或IP 哈希。此示例将后端上游(upstream)池的负载均衡算法设为了最少连接:
upstream backend {
least_conn;
server backend.example.com;
server backend1.example.com;
}
除了通用哈希、随机算法和最短时间外,所有其他负载均衡算法都是独立的指令,如上例所示。下面的“详解”一节解释了这些指令的参数
详解
并非所有请求或数据包都有相同的权重。有鉴于此,轮询甚至上例使用的加权轮询都将无法满足所有应用或流量的需求。NGINX 提供了多种负载均衡算法,您可以根据特定的用例进行选择,也可以对您选择的算法进行配置。以下负载均衡方法可用于上游HTTP、TCP 和 UDP 池:
轮询
轮询是默认的负载均衡方法,它按照上游池中服务器列表的顺序分发请求。当上游服务器的容量变化时,您还可以考虑使用加权轮询。权重的整数值越高,服务器在轮询中的优势就越大。权重背后的算法只是加权平均值的统计概率。
最少连接
此方法通过将当前请求代理到打开连接数最少的上游服务器实现负载均衡。与轮询一样,在决定将连接发送到哪台服务器时,最少连接也会考虑权重。其指令名称是least_conn。
最短时间
该算法仅在 NGINX Plus 中提供,与最少连接算法类似,它将请求代理到当前连接数最少的上游服务器,但首选平均响应时间最短的服务器。此方法是最复杂的负载均衡算法之一,能够满足高性能 Web 应用的需求。最短时间在最少连接的基础上进行了优化,因为少量连接并不一定意味着最快的响应。使用此算法时,切记要考虑服务请求时间的统计差异。有些请求可能本身就需要更多的处理,请求时间也就更长,因而拉宽了统计的范围。请求时间长并不一定意味着服务器性能欠佳或超负荷工作。但是,需要进行更多处理的请求可以考虑使用异步工作流。用户必须为此指令指定 header 或 last_byte 参数。当指定 header 时,使用接收响应头的时间;当指定 last_byte 时,使用接收完整响应的时间。其指令名称是 least_time。
通用哈希
管理员使用请求或运行时给定的文本、变量或两者的组合定义哈希值。NGINX 能够为当前请求生成哈希值并将其放在上游服务器上,从而在这些服务器之间分发负载。当您希望更好地控制请求的发送位置或确定哪台上游服务器最有可能缓存数据时,此方法就会派上用场。请注意,当从池中添加或删除服务器时,将重新分发哈希请求。此算法有一个可选的参数:consistent,它能够将重新分发带来的影响最小化。其指令名称是 hash。
随机
该方法用于指示 NGINX 从组中随机选择一台服务器,同时考虑服务器的权重。可选的 two [method] 参数指示 NGINX 随机选择两台服务器,然后使用提供的负载均衡方法对两者均匀地分发请求。默认情况下,如果传输的参数只有 two,没有method,则使用 least_conn 方法。随机负载均衡的指令名称是 random。
IP 哈希
此方法仅适用于 HTTP。IP 哈希算法使用客户端 IP 地址作为哈希。IP 哈希与通用哈希存在细微的不同,前者使用 IPv4 地址的前三个八进制位或整个 IPv6 地址,而后者使用的是远程变量。当会话状态十分重要,但又无法通过应用的共享内存进行处理时,此方法可确保客户端始终被代理到同一上游服务器(只要服务器可用)。此方法在分发哈希值时也考虑了 weight 参数。其指令名称是 ip_hash。
5、NGINX Plus 之 Sticky Cookie
使用 NGINX Plus 将下游(downstream)客户端绑定到上游(upstream)服务器。
使用 sticky cookie 指令指示 NGINX Plus 创建和跟踪 cookie:
upstream backend {
server backend1.example.com;
server backend2.example.com;
sticky cookie
affinity
expires=1h
domain=.example.com
httponly
secure
path=/;
}
此配置创建并跟踪了一个 cookie,该 cookie 可以将下游客户端绑定到上游服务器。在此示例中,cookie 被命名为 affinity,为 example.com 设置,有效期为一个小时,无法在客户端使用,只能通过 HTTPS 发送,并且对所有路径有效。
详解
在 sticky 指令中使用 cookie 参数,为第一个请求创建一个包含上游服务器信息的cookie。NGINX Plus 将跟踪此 cookie,允许它继续将后续请求定向到同一台服务器。cookie 参数的第一个位置参数是待创建和跟踪的 cookie 的名称。其他参数提供了额外的控制,为浏览器提供了相应的使用信息 —— 如有效期、域、路径,以及 cookie 是否可以在客户端使用,或者 cookie 是否可以通过不安全的协议传递。
6、NGINX Plus 之 Sticky Learn
使用 NGINX Plus 的现有 cookie 将下游(downstream)客户端绑定到上游(upstream)服务器。
使用 sticky learn 指令发现和跟踪上游应用创建的 cookie:
upstream backend {
server backend1.example.com:8080;
server backend2.example.com:8081;
sticky learn
create=$upstream_cookie_cookiename
lookup=$cookie_cookiename
zone=client_sessions:2m;
}
此示例指示 NGINX 通过在响应头中查找名为 COOKIENAME 的 cookie 来查找和跟踪会话,并通过在请求头中查找相同的 cookie 来查找现有会话。此会话的 affinity 被存储在一个 2MB 的共享内存区中,后者可以跟踪大约 16,000 个会话。cookie 的名称始终是应用专有的。常用的 cookie 名称(如 jsessionid 或 phpsessionid)通常是应用或应用服务器配置中的默认设置。
详解
当应用创建自己的会话状态 cookie 时,NGINX Plus 可以在请求响应中发现它们并跟踪它们。当在 sticky 指令中使用 learn 参数时,就会执行这种类型的 cookie 跟踪。跟踪 cookie 的共享内存由 zone 参数指定,包括名称和大小。NGINX Plus 通过指定create 参数来从上游服务器的响应中查找 cookie,并使用 lookup 参数搜索之前注册的服务器 affinity。这些参数的值是 HTTP 模块暴露的变量。
7、NGINX Plus 之 Sticky Routing
使用 NGINX Plus 对持久会话路由到上游(upstream)服务器的方式进行细粒度控制。
通过带 route 参数的 sticky 指令使用关于请求的变量进行路由:
map $cookie_jsessionid $route_cookie {
~.+\.(?P<route>\w+)$ $route;
}
map $request_uri $route_uri {
~jsessionid=.+\.(?P<route>\w+)$ $route;
}
upstream backend {
server backend1.example.com route=a;
server backend2.example.com route=b;
sticky route $route_cookie $route_uri;
}
此示例尝试提取 Java 会话 ID,首先通过将 Java 会话 ID cookie 映射到第一个 map 代码块的变量来提取,然后查找请求 URI 中名为 jsessionid 的参数,将值映射到使用第二个 map 代码块的变量。带有 route 参数的 sticky 指令可以传输任意数量的变量。第一个非零或非空值用于路由。如果使用 jsessionId cookie,请求将被路由到 backend1;如果使用 URI 参数,请求将被路由到 backend2。尽管此示例使用的是 Java 通用会话 ID,但这也同样适用于其他会话技术(如 phpsessionid)或者您的应用为会话 ID 生成的任何有保证的唯一标识符。
详解
有时,您可能希望利用更细粒度的控制,将流量定向到特定的服务器。sticky 指令的route 参数就是为了实现这个目标而构建的。与通用哈希负载均衡算法相比,sticky route 能够为您提供更好的控制、实际跟踪能力和粘性。该算法会先根据指定的路线将客户端路由到上游服务器,随后的请求则会在 cookie 或 URI 中携带路由信息。Sticky route 采用了许多要计算的位置参数。第一个非空变量用于路由到服务器。map 代码块可以选择性地解析变量,并将它们保存为要在路由中使用的其他变量。本质上,sticky route 指令会在 NGINX Plus 共享内存区内创建一个会话,用于跟踪为上游服务器指定的任何客户端会话标识符,从而始终一致地将具有此会话标识符的请求传输到与原请求相同的上游服务器。
8、NGINX Plus 之连接清空
出于维护或其他原因,您需要使用 NGINX Plus 优雅地移除服务器,同时仍然提供会话。
借助 NGINX Plus API(详见第 5 章),使用 drain 参数指示 NGINX 停止发送未被跟踪的新连接:
$ curl -X POST -d '{"drain":true}' 'http://nginx.local/api/3/http/upstreams/backend/servers/0'
{
"id":0,
"server":"172.17.0.3:80",
"weight":1,
"max_conns":0,
"max_fails":1,
"fail_timeout":
"10s","slow_start":
"0s",
"route":"",
"backup":false,
"down":false,
"drain":true
}
详解
当会话状态被存储到本地服务器时,必须要先清空连接和持久会话,然后再从池中删除服务器。连接清空是指在从上游(upstream)池中删除服务器之前,先让服务器会话在本地失效的过程。您可以通过将 drain 参数添加到 server 指令来配置特定服务器的清空。如果设置了 drain 参数,NGINX Plus 会停止向该服务器发送新会话,但允许在当前会话长度内继续提供当前会话。您还可以通过将 drain 参数添加到上游服务器指令来切换此配置,然后重新加载 NGINX Plus 配置。
9、被动健康检查
被动检查上游(upstream)服务器的健康状况。
通过 NGINX 健康检查和负载均衡确保只使用健康的上游服务器:
upstream backend {
server backend1.example.com:1234 max_fails=3 fail_timeout=3s;
server backend2.example.com:1234 max_fails=3 fail_timeout=3s;
}
此配置能够监控定向到上游服务器的客户端请求的响应,从而被动监控上游服务器的健康状况。该示例将 max_fails 指令设置为 3,将 fail_timeout 设置为 3 秒。这些指令参数在 stream 和 HTTP 服务器中的工作原理相同。
详解
NGINX 开源版提供了被动健康检查功能,并且使用了相同的 server 参数来实施HTTP、TCP 和 UDP 负载均衡。当客户端发出请求时,被动监控功能可以监测通过NGINX 的失效或超时连接。默认情况下启用被动健康检查;此处提到的参数允许您调整它们的行为。max_fails 的默认值为 1,fail_timeout 的默认值为 10s。健康监控在所有类型的负载均衡中都很重要,这不仅是为了保障用户体验,也是为了实现业务连续性。NGINX 能够被动监控上游 HTTP、TCP 和 UDP 服务器,确保它们健康、高效地运行。
参考资料
HTTP 健康检查管理指南
TCP 健康检查管理指南
UDP 健康检查管理指南
10、NGINX Plus 之主动健康检查
使用 NGINX Plus 主动检查上游(upstream)服务器的健康状况。
对于 HTTP,请使用 location 代码块中的 health_check 指令:
http {
server {
# ...
location / {
proxy_pass http://backend;
health_check interval=2s
fails=2
passes=5
uri=/
match=welcome;
}
}
# 状态码是 200,内容类型是 "text/html",
# 正文包含 "Welcome to nginx!"
match welcome {
status 200;
header Content-Type = text/html;
body ~ "Welcome to nginx!";
}
}
此处的 HTTP 服务器健康检查配置通过每 2 秒向 URI“/”发送 HTTP GET 请求来检查上游服务器的健康状况。我们无法为健康检查定义 HTTP 方法,只能执行 GET 请求,因为其他方法可能会更改后端系统的状态。上游服务器只有连续通过五次健康检查才能被认为是健康的。如果它们连续两次未通过检查,则被认定为不健康。上游服务器的响应必须匹配定义的 match 代码块,后者将状态码定义为 200,将请求头的Content-Type 值定义为‘text/html’,同时定义了响应正文中的字符串“Welcome to nginx!”。HTTP match 代码块具有三个指令:status、header 和 body。这三个指令均带有比较标记。
TCP/UDP 服务的 stream 健康检查非常相似:
stream {
# ...
server {
listen 1234;
proxy_pass stream_backend;
health_check interval=10s
passes=2
fails=3;
health_check_timeout 5s;
}
# ...
}
在此示例中,TCP 服务器配置为侦听端口 1234,并代理到一组上游服务器,它将主动检查这些服务器的健康状况。除了 uri 之外,stream health_check 指令与 HTTP 中的其他参数都相同,并且 stream 版本有一个可以将检查协议切换到 udp 的参数。在此示例中,间隔时间设为 10 秒,规定通过两次被视为健康,失败三次被视为不健康。主动stream 健康检查也能验证来自上游服务器的响应。但是 stream 服务器的 match 代码块只有两个指令:send 和 expect。send 指令是要发送的原始数据,expect 是确切的响应或要匹配的正则表达式。
详解
NGINX Plus 允许使用被动或主动健康检查来监控源服务器。这些健康检查可以测量的不仅仅是响应代码。在 NGINX Plus 中,主动 HTTP 健康检查根据一系列有关上游服务器响应的接受标准实施监控。您可以对主动健康检查的监控进行配置,包括上游服务器的检查频率、服务器必须通过多少次检查才能被视为健康、失败多少次会被视为不健康以及预期的结果应该是什么。对于更复杂的逻辑,match 代码块的 require 指令允许使用值不能为空或零的变量。match 参数指向了定义响应接受标准的 match 代码块。当在 TCP/UDP 的 stream 上下文中使用时,match 代码块还定义了要发送到上游服务器的数据。这些特性使得 NGINX 能够确保上游服务器始终处于健康状态。
参考资料
HTTP 健康检查管理指南
TCP 健康检查管理指南
UDP 健康检查管理指南
11、NGINX Plus 之慢启动
您的应用需要缓慢增加流量,直至承担全部生产负载。
当上游(upstream)负载均衡池重新引入服务器时,使用 server 指令中的 slow_start 参数在指定的时间内逐渐增加连接数:
upstream {
zone backend 64k;
server server1.example.com slow_start=20s;
server server2.example.com slow_start=15s;
}
server 指令配置将在上游池重新引入服务器后缓慢增加流量。server1 和 server2 将分别在 20 秒和 15 秒内缓慢增加连接数量。
详解
慢启动是指在一段时间内缓慢增加代理到服务器的请求数量的概念。慢启动允许应用通过填充缓存和初始化数据库连接来“热身”,而不至于一启动就被“汹涌而来”的连接压垮。当健康检查失败的服务器开始再次通过检查并重新进入负载均衡池时,该功能就会派上用场,并且仅在 NGINX Plus 中提供。慢启动不能与 hash、ip_hash 或random 负载均衡方法一起使用。