Nginx upstream 模块
2025-01-22 08:19:30    1.8k 字   
This post is also available in English and alternative languages.

一般情况应用服务器的上游会部署一套nginx集群,用作反向代理、负载均衡,这个是很好的。


1. upstream容错重试

假设有五台应用服务器(Backend Server),其中两台宕机不可用;这时某nginx发送请求到其中一台(Backend Server),由于其不可用,Backend Server 会超时或返回错误状态码,然后直接返回给Client,这种场景的处理是很不友好的。

nginx中的upstream模块,不仅可以负载均衡,还能够对上面这种情况容错。

upstream可以通过判断节点失效状态、自动探测等方式,自动将问题server节点从访问列表中剔除,期间不会再将请求发送到该Server上(直到Server恢复),而将请求发送给下一台Server,如果下一台也有问题,继续往下重试直到所有Server被重试完。


1.1. 判断节点失效

nginx默认判断节点状态通过 connect refuse 和 time out 状态为准,不会以http状态码进行判断;它认为,只要能返回http状态码说明该节点还可以正常连接。

PS:如果是业务层面主动返回的500状态码,它就无法区分。


1.2. proxy_next_upstream指令

通过 proxy_next_upstream 指令,可以设置对相关http状态码的请求重试、容灾。

语法:

1
proxy_next_upstream error | timeout | invalid_header | http_500 | http_502 | http_503 | http_504 | http_403 | http_404 | http_429 | non_idempotent | off ...;

默认值:

1
proxy_next_upstream error timeout;

上下文环境:

1
http, server, location

参数含义:

参数含义
erroran error occurred while establishing a connection with the server, passing a request to it, or reading the response header;

在与服务器建立连接、向其传递请求或读取响应头时发生错误。
timeouta timeout has occurred while establishing a connection with the server, passing a request to it, or reading the response header;

在与服务器建立连接、向其传递请求或读取响应头时发生超时。
invalid_headera server returned an empty or invalid response;
服务器返回空的或无效的响应;
http_500a server returned a response with the code 500;
后端服务器返回的响应状态码为500
http_502a server returned a response with the code 502;
后端服务器返回的响应状态码为502
http_503a server returned a response with the code 503;
后端服务器返回的响应状态码为503
http_504a server returned a response with the code 504;
后端服务器返回的响应状态码为504
http_403a server returned a response with the code 403;
后端服务器返回的响应状态码为403
http_404a server returned a response with the code 404;
后端服务器返回的响应状态码为404
http_429a server returned a response with the code 429
后端服务器返回的响应状态码为429
non_idempotentnormally, requests with a non-idempotent method (POST, LOCK, PATCH) are not passed to the next server if a request has been sent to an upstream server (1.9.13); enabling this option explicitly allows retrying such requests;
默认POST, LOCK, PATCH 这些会对服务器造成幂等的http请求,不会进行重试。一定要对这些http请求重试,则添加该参数。
offdisables passing a request to the next server.
停止将请求发送给下一台后端服务器

1.3. 超时参数

参数含义
proxy_connect_timeout与后端服务器建立连接的超时
默认值60s
proxy_send_timeout向后端服务器传输请求的超时
默认值60s
proxy_read_timeout从后端服务器读取响应的超时
默认值60s

2. Nginx - upstream - 负载均衡策略

2.1. 普通轮询(round-robin)

默认策略,每个请求按照时间顺序逐一分配。

如果某个机器宕机(符合指定异常、状态码),跳过该机器向下一个机器发起重试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 设定负载均衡服务器列表
upstream backend {
server 127.0.0.8:80;
server 127.0.0.9:80;
}

server {
location / {
# 请求转向backend定义的服务器列表
proxy_pass http://backend;
# 获取到指定异常(http状态码),再进行重试
proxy_next_upstream error http_500 http_502 http_503 http_504;
}
}

2.2. 加权轮询

weight 指定轮询的权值,weight值越大,分配到的访问机率越高,该策略主要用于后端每个服务器性能不均的情况下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 设定负载均衡服务器列表
upstream backend {
# weigth参数表示权值,权值越高被分配到的几率越大
server 127.0.0.8:80 weight=1;
server 127.0.0.9:80 weight=2;
}

server {
location / {
# 请求转向backend定义的服务器列表
proxy_pass http://backend;
# 获取到指定异常(http状态码),再进行重试
proxy_next_upstream error http_500 http_502 http_503 http_504;
}
}

2.3. ip_hash

按照Client Ip的hash结果分配;

这样来自同一个IP的client固定访问一个Backend Server,可以解决session不能跨服务器的问题。

当然如果这个节点不可用了,会发到下个节点,而此时没有session同步的话就注销掉了。

存在局限性,client IP一旦变化,hash值重新计算,可能就换backend server了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 设定负载均衡服务器列表
upstream backend {
ip_hash;
server 127.0.0.8:80;
server 127.0.0.9:80;
server 127.0.0.10:80;
}

server {
location / {
# 请求转向backend定义的服务器列表
proxy_pass http://backend;
# 获取到指定异常(http状态码),再进行重试
proxy_next_upstream error http_500 http_502 http_503 http_504;
}
}

2.4. least_conn

最少连接均衡;

请求被发送到当前活跃连接最少的后端服务器。会考虑weight的值。如果有多个后端服务器的 conns 值同为最小的,那么对它们采用加权轮询算法(weight)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
upstream backend {
least_conn;
server 127.0.0.8:80;
server 127.0.0.9:80;
server 127.0.0.10:80;
}
server {
location / {
# 请求转向backend定义的服务器列表
proxy_pass http://backend;
# 获取到指定异常(http状态码),再进行重试
proxy_next_upstream error http_500 http_502 http_503 http_504;
}
}

2.5. sticky

Nginx的负载均衡-upstream模块,其负载均衡策略有普通轮询、加权轮询、ip_hash、least_conn等几种方式。

sticky也是负载均衡策略之一(需要引入相应模块),sticky是基于cookie实现负载的。

通过分发识别cookie,使得来自同一个客户端的请求落在同一台服务器上,默认cookie标识名为route。


2.5.1. sticky 原理

①: client 首次发起请求,nginx接收后请求头没有cookie,则以轮询的方式分发给Backend Server。

②: Backend Server业务处理后返回nginx。

③: 此时nginx生成cookie(route),返回client。route值与Backend Server相关联。

④: client接收请求并保存带route的cookie。

⑤: 当client下次发送请求时,cookie中就会带上route,nginx根据cookie中的route,将这个client的请求转发到固定的Backend Server。


2.5.2. 问题

sticky策略在某些场景大流量冲击下,会导致负载不均的情况。

当年大促,系统被流量冲垮后复盘,发现应用服务器流量不均衡;往上游排查时发现,系统负载的Nginx负载策略是 sticky,这个策略是中间件部门配置的默认参数,公司大部分系统,默认都是这个策略。

由于当时日志检索系统也存在问题、降级,导致没有采集到关键信息,比如nginx分配的cookie route。

最后整改方案,将sticky策略,修改成普通的轮询策略。


3. Reference

nginx - proxy_next_upstream

Using nginx as HTTP load balancer