当前位置:首页>排行榜>Nginx 负载均衡怎么选:轮询、权重与连接数策略对比

Nginx 负载均衡怎么选:轮询、权重与连接数策略对比

  • 更新时间 2026-10-02 23:06:09
Nginx 负载均衡怎么选:轮询、权重与连接数策略对比

Nginx 负载均衡怎么选:轮询、权重与连接数策略对比

负载均衡的目标不是让每台后端收到完全一样的请求数量,而是让用户请求在可用资源之间合理分配,同时控制错误与延迟。两台机器配置相同,不代表每个请求耗时相同;一台机器核心更多,也不代表它在数据库依赖上更快。选策略之前,需要明确请求类型、连接持续时间、后端处理能力与会话要求,否则一个看起来公平的请求分布,仍可能把慢节点压到排队。

本文使用虚构教学环境:Nginx 作为 HTTP 反向代理,后端 A 与 B 分别监听 127.0.0.1:18081 和 127.0.0.1:18082,测试入口监听 127.0.0.1:18080。主线比较默认加权轮询、不同权重与 least_conn。配置和命令未在读者生产环境执行,示例分布不是实测结果。涉及新版本或特定构建的能力,会提示现场核查,避免把文档中的最新指令当成所有发行版都具备。

本文的配置片段大多放在 http 上下文或对应的 upstream、server、location 中,同一主题的片段通常是替代配置,不能把所有同名组连续拼进一个文件。实验使用本地端口,负载脚本只面向自己的测试服务。生产变更应先确认配置、访问路径和回退文件,再进行小范围验证;算法效果需要用代表性请求测量,不能仅靠几次刷新网页作判断。

先搞清楚 Nginx 在分配什么

查看版本和构建参数能够确认模块能力、配置路径和发行版特征。版本号相同也可能有不同编译模块,因此不能只读包名。下面的输出通常写到标准错误,保存时应一起保留。本文基础算法广泛可用,但动态解析、状态接口与健康检查能力会随版本和产品变化;每个额外指令都应经过现场配置验证,而非凭其他服务器经验。

bash
nginx -V 2>&1

默认 upstream 使用加权轮询,未指定权重时各节点权重相同。下面定义两个后端,不声明一个固定的“每次必定交替”顺序。实际请求分配会受 worker 状态、故障、连接复用和算法实现影响。对于相近耗时的短请求,轮询通常是合理起点;它按请求选择后端,不会自动感知 CPU、业务队列或数据库等待。

nginx
upstream app_pool {
server127.0.0.1:18081;
server127.0.0.1:18082;
}

入口 server 把请求交给命名后端组,代理配置需要位于正确上下文。下面监听回环地址,避免教学服务暴露到所有接口。Host 传递规则影响后端虚拟主机与应用路由,现场应按实际业务设置。最小配置用于验证分配链路,不涵盖生产 TLS、认证与访问控制,不能直接作为完整公网入口部署。

nginx
server {
listen127.0.0.1:18080;
server_name localhost;
location / {
proxy_pass http://app_pool;
proxy_set_header Host $host;
    }
}

权重表达的是相对分配比例,不是容量上限。下面将 A 与 B 配成三比一,适用于已经确认 A 在代表性负载下具备更多可用能力的情况。权重不会自动随 CPU 使用率变化,也不保证每四个请求严格出现三次 A 一次 B。统计时应观察足够大的无故障窗口,并同时看延迟与错误,不能只验证比例接近目标就宣布容量规划正确。

nginx
upstream app_pool {
server127.0.0.1:18081 weight=3;
server127.0.0.1:18082 weight=1;
}

最少连接策略优先考虑后端当前活跃连接,并结合权重选择,适合连接占用时间差异较大的场景。下面把策略放在 server 列表之前,便于配置审阅。活跃连接并不是应用内部所有任务数,也不直接代表 CPU 负载。某些请求在连接结束后仍有后台工作,算法看不到;某些连接长期空闲,也不能直接用总连接数替代策略使用的活动状态。

nginx
upstream app_pool {
    least_conn;
server127.0.0.1:18081;
server127.0.0.1:18082;
}

连接数策略仍可配权重,表达不同节点的相对承载能力。下面是备选组合,而不是另一个需要同时加入的同名组。权重与活动连接共同影响选择,但仍不能感知请求成本。若 A 的单个请求消耗更大资源,给它高权重可能加重问题。权重应由性能实验与容量余量支持,更新后还需要观察长尾与错误,避免静态估计长期失效。

nginx
upstream app_pool {
    least_conn;
server127.0.0.1:18081 weight=3;
server127.0.0.1:18082 weight=1;
}

多个 worker 的状态是否共享,会影响连接计数和失败状态的观察。使用共享 zone 可以让组配置与相关运行状态跨 worker 共享,大小应按组规模核查。下面是最少连接的教学配置,不声称固定大小适合所有后端数量。即使共享状态,算法仍只获得自身管理的信息,无法替代应用队列监控;共享内存也不是保存所有业务请求历史。

nginx
upstream app_pool {
zone app_pool 64k;
    least_conn;
server127.0.0.1:18081;
server127.0.0.1:18082;
}

选择策略的假设应写入实验记录。下面明确短请求、长请求和异构节点三种条件,以及要观察的结果。连接持续时间、请求成本与业务会话要求可能同时存在,不能只凭“接口是 HTTP”就选轮询。一个策略适合当前负载不代表永远最佳;应用版本和请求结构变化后,应重新验证假设,而不是多年沿用最初权重。

yaml
selection_hypotheses:
short_uniform_requests:round_robin_baseline
variable_duration_requests:evaluate_least_conn
heterogeneous_capacity:evaluate_weights
acceptance_metrics: [success_rate, p95_latency, throughput, backend_distribution]

建立可辨识的实验后端,让每次分配都有证据

后端响应里应包含节点标识,这样测试端能统计实际去向。下面是完整教学服务,使用线程处理请求,支持延迟参数并发送正确长度,监听回环地址。它只适合模拟连接占用,不代表真实应用性能;Python 线程与本地网络都存在特殊条件。参数仅供自己的实验调用,不应把这种任意延迟入口部署到生产。

python
# 保存为 backend.py
import os,time
from http.server import BaseHTTPRequestHandler,ThreadingHTTPServer
from urllib.parse import urlparse,parse_qs
name=os.environ.get('BACKEND_NAME','A')
port=int(os.environ.get('BACKEND_PORT','18081'))
base=float(os.environ.get('BASE_DELAY','0'))
classHandler(BaseHTTPRequestHandler):
    protocol_version='HTTP/1.1'
defdo_GET(self):
        q=parse_qs(urlparse(self.path).query)
        delay=min(5,max(0,float(q.get('delay',[base])[0])))
        time.sleep(delay)
        body=(name+'\n').encode()
self.send_response(200)
self.send_header('Content-Type','text/plain')
self.send_header('Content-Length',str(len(body)))
self.send_header('X-Backend',name)
self.end_headers();self.wfile.write(body)
deflog_message(self,*args): pass
ThreadingHTTPServer(('127.0.0.1',port),Handler).serve_forever()

启动两个后端时保存进程编号,便于后续只停止本次实验资源。下面在工作目录启动,不操作已有应用进程。环境变量定义节点标识与端口,日志分开写出。后台进程成功创建不保证服务已经开始监听,下一步需要直接检查。如果端口被占用,应先查清对象,而不是结束所有同名 Python 进程以腾出端口。

bash
BACKEND_NAME=A BACKEND_PORT=18081 python3 backend.py > backend-a.log 2>&1 &
printf'%s\n'"$!" > backend-a.pid
BACKEND_NAME=B BACKEND_PORT=18082 python3 backend.py > backend-b.log 2>&1 &
printf'%s\n'"$!" > backend-b.pid

直接访问后端可以排除代理之前的应用问题。下面检查两个本地端点,失败时应先解决监听或程序错误,不进入策略比较。响应内容分别标识 A 和 B,说明识别链成立。实验服务只返回很小正文,适合统计后端分配,不代表上传、流式响应或数据库操作的成本;后续负载应逐步增加代表性特征。

bash
curl --fail --max-time 2 http://127.0.0.1:18081/
curl --fail --max-time 2 http://127.0.0.1:18082/

为了让实验配置不影响系统服务,可以创建独立目录和非特权端口配置。下面准备工作空间,不启动 Nginx。实际 Nginx 二进制可能使用系统默认用户与权限要求,独立配置中需要明确日志与 PID 路径。隔离配置不仅便于清理,也能避免算法实验意外覆盖生产 upstream;生产上线应走正式配置管理路径。

bash
mkdir -p "$PWD/nginx-lab/conf""$PWD/nginx-lab/logs"

独立实验的主配置需要包含 events 与 http,并在 http 中加载策略文件。下面限制一个 worker,便于第一轮观察,然后再验证多 worker 行为。这个配置是教学基线,日志位置相对实验前缀。不要把实验的 worker 数作为生产推荐值,真实容量还取决于 CPU、连接数量、文件描述符与代理负载。

nginx
worker_processes1;
pid logs/nginx.pid;
error_log logs/error.log notice;
events { worker_connections1024; }
http {
include app.conf;
}

运行前用显式前缀和配置路径检查,保证读到的是实验文件而非系统默认配置。下面的工作目录应与之前创建的位置一致,配置文件已保存才执行。语法成功不能证明后端可达或策略效果正确,但能尽早发现上下文、模块与重复定义错误。任何新指令都应先通过这种检查,再考虑生产变更。

bash
nginx -p "$PWD/nginx-lab/" -c conf/nginx.conf -t

启动实验入口后,应检查监听与访问,而不是只看进程存在。下面读取指定端口并请求入口,端口明确限定为本次实验。若响应来自系统已有服务,说明隔离没有按预期成立,应停止统计并核对 PID 与配置。实验要保证请求确实经过所比较的 upstream,否则得到再漂亮的比例也只是测了错误对象。

bash
nginx -p "$PWD/nginx-lab/" -c conf/nginx.conf
ss -lnt 'sport = :18080'
curl --fail --max-time 2 http://127.0.0.1:18080/

先做少量串行请求,可以验证节点标识与路由,但不能代表并发负载。下面记录二十次正文,统计去向只是基础检查。串行时每次请求完成后才有下一次,最少连接可能经常面对全部后端空闲,优势不会体现。策略比较因此必须包含并发与不均匀耗时,不能拿这轮结果判断所有算法在真实业务中等价。

bash
for i in $(seq 1 20); do curl --fail --silent --show-error --max-time 3 http://127.0.0.1:18080/; done > serial-backends.txt
sort serial-backends.txt | uniq -c

连接复用、超时与日志,决定实验是否可解释

上游 keepalive 缓存的是每个 worker 保留的空闲连接,参数不是后端最大总连接数。下面为组配置空闲连接缓存,同时保留最少连接策略。缓存能减少重复建立连接的开销,但会增加保留连接资源。它与活动连接选择是相关但不同的机制,比较算法时应保持复用配置一致,否则差异可能来自连接建立成本。

nginx
upstream app_pool {
zone app_pool 64k;
    least_conn;
server127.0.0.1:18081;
server127.0.0.1:18082;
keepalive32;
}

HTTP 上游复用需要配合正确协议与 Connection 头设置。下面明确使用 HTTP/1.1,避免依赖不同版本默认值。清空 hop-by-hop Connection 头让代理按自己的连接机制处理,不应把客户端 Connection 值照搬到后端。应用支持持久连接也需要正确响应长度或分块语义,教学后端因此显式发送 Content-Length,防止读取边界含糊。

nginx
location / {
proxy_pass http://app_pool;
proxy_http_version1.1;
proxy_set_header Connection "";
}

连接建立、向后端发送与从后端读取采用不同超时语义。下面数值仅供本地实验,生产应按服务目标设置。读取超时通常关注相邻读取操作之间的间隔,不是简单的整个请求总时长。流式接口可能持续有数据而长时间不结束,普通短请求则应由应用或网关另行定义端到端截止时间,不能混用一个超时控制全部行为。

nginx
proxy_connect_timeout2s;
proxy_send_timeout10s;
proxy_read_timeout10s;

日志需要看到请求、最终状态与上游尝试信息。下面以 JSON 记录多个时间字段,并将上游字段保留为字符串以容纳多次尝试。日志格式定义在 http 上下文,access_log 可按需要放在对应层次。变量并非都在每次请求中有值,解析时应保留缺失状态,不把横线当零;否则连接失败可能被统计成极快响应。

nginx
log_format lb_json escape=json '{"time":"$time_iso8601","rid":"$request_id","status":$status,"rt":"$request_time","upstream":"$upstream_addr","us":"$upstream_status","uct":"$upstream_connect_time","uht":"$upstream_header_time","urt":"$upstream_response_time"}';
access_log logs/access.jsonl lb_json;

调试响应头可以让测试快速知道后端去向,但可能暴露内部拓扑,生产是否保留需要评估。下面只在实验 server 或 location 使用。发生重试时上游地址变量可能包含多个节点,不是单个最终节点;测试程序应解析或优先用后端自身标识。缓存命中时也可能没有后端地址,所以分配实验应先排除缓存影响。

nginx
add_header X-Lab-Upstream $upstream_addr always;
add_header X-Lab-Request-ID $request_id always;

变更策略应先验证新配置,再 reload 到同一个实验实例。下面只针对实验前缀,不操作系统 Nginx。reload 会启动新 worker 并让旧 worker 处理剩余连接,短时间内新旧行为可能并存。比较窗口应等待旧请求排空并记录切换时间,不能把过渡期间混合分布当作新算法最终效果。

bash
nginx -p "$PWD/nginx-lab/" -c conf/nginx.conf -t
nginx -p "$PWD/nginx-lab/" -c conf/nginx.conf -s reload

日志中的上游地址可以用于统计尝试去向,下面先把多次尝试拆开,再聚合。它得到的是上游尝试数,不是用户请求数,重试会让总量大于入口记录数。空值和未访问上游情况被排除,但排除数量也应在正式报告里说明。策略比例比较最好先用无错误无重试的窗口,再单独分析失败场景。

bash
jq -r '.upstream // "" | split(",")[] | gsub("^ +| +$";"") | select(length>0 and .!="-")' nginx-lab/logs/access.jsonl | sort | uniq -c

错误日志能解释没有可用上游、连接拒绝和超时,但错误文字只是位置线索。下面查看最近片段,事故窗口应与访问日志时间对应。多个 worker 并发记录可能交错,不能按相邻行直接关联请求。需要请求标识、端点和时间联合定位;对于应用返回的业务失败,代理错误日志也可能没有相应内容,所以两类日志都要看。

bash
tail -n 100 nginx-lab/logs/error.log

轮询与权重,适合哪些请求和哪些异构能力

同等权重的期望比例可以先计算,作为实验假设而非实测结论。下面把权重转为百分比,适合展示规划。实际比例还会受到失败、节点不可用和测试样本量影响,尤其是短窗口不能要求完全相等。比例靠近目标只是分配机制表现,服务质量还要看请求成功率与延迟;均匀分配到两台都很慢的后端并不是成功。

python
weights={'A':1,'B':1}
total=sum(weights.values())
print({k:100*v/total for k,v in weights.items()})

异构节点的权重应从代表性容量出发。下面用教学容量每秒三百与一百请求计算三比一,但未宣称这两台机器实测如此。容量应在相同请求结构和可接受延迟下测量,不能只用最大压测吞吐。某节点依赖共享数据库,增加 CPU 后容量可能不成比例,权重需要围绕端到端能力更新。

python
capacity_rps={'A':300,'B':100}
base=min(capacity_rps.values())
print({k:v/base for k,v in capacity_rps.items()})

更细的权重比不一定带来更好的实际控制。下面使用五比二表达教学容量差异,仍是备选 upstream。权重不设硬上限,高峰时每台后端都可能过载,尤其当总到达率超过整体处理能力。生产还需要入口限流、后端并发限制与快速失败策略,不能期待算法在资源不足时凭分配自动消除排队。

nginx
upstream app_pool {
server127.0.0.1:18081 weight=5;
server127.0.0.1:18082 weight=2;
}

串行统计权重可以使用更多样本,但仍只验证分配,不验证承载能力。下面记录四百次教学请求,命令只应指向本地实验入口。curl 失败会影响输出,正式统计需要单独保留成功与失败数,不能只对返回 A/B 的行计数。若接口启用缓存,也会绕过后端,所以权重实验应明确没有缓存命中。

bash
for i in $(seq 1 400); do curl --fail --silent --show-error --max-time 3 http://127.0.0.1:18080/; done > weighted-backends.txt
sort weighted-backends.txt | uniq -c

比例计算应以实际识别到的后端响应为分母,并单独报告未知或失败。下面读取样本响应文件,只接受 A 与 B。它不统计 curl 失败尝试,因为失败信息通常在标准错误,所以报告还必须附总发起量。把分布百分比与成功率分别列出,能够防止“所有成功请求都分得很准”掩盖大量未成功尝试。

bash
awk '$0=="A"||$0=="B" {n[$0]++;total++;next} {unknown++} END{for(k in n)printf "%s %d %.2f%%\n",k,n[k],100*n[k]/total;print "identified",total+0,"unknown",unknown+0}' weighted-backends.txt

小比例灰度可以用权重表达流量倾向,但不保证精确用户比例或稳定会话。下面把新节点配为一比九十九,适用于可随请求切换的教学场景。重试、粘性、缓存与少量样本都会改变最终用户观察。涉及业务数据兼容时,灰度还需要版本与数据库变更方案,不能只改权重就认为一百分之一用户被稳定隔离。

nginx
upstream app_pool {
server127.0.0.1:18081 weight=99;
server127.0.0.1:18082 weight=1;
}

使用 down 可以从选择中排除指定节点,适合维护或实验分支。下面显式保留 B 配置但不向其分配新请求;已经建立的请求不会因此自动安全迁移。下线需要先确认应用是否允许排空与长连接结束,尤其是流式业务。reload 后旧 worker 的存量连接仍需观察,不能看到配置里写了 down 就立即终止后端。

nginx
upstream app_pool {
server127.0.0.1:18081;
server127.0.0.1:18082 down;
}

选择轮询的验证标准应与业务目标对应。下面记录配置一致、请求成本近似和延迟稳定这几个条件,状态仍待填写。轮询简单且容易解释,通常可作为对照基线。若后续连接持续时间差异显著、某个节点长期积压,再用最少连接与容量权重做实验;不要因为算法名字更复杂就默认更适合。

yaml
round_robin_review:
equal_capacity_verified:pending
request_duration_similarity:pending
retry_window_excluded:pending
success_rate:null
p95_seconds:null
backend_distribution:null

最少连接的价值,在并发与不均匀耗时中观察

并发请求比串行刷新更能体现活动连接差异。下面启动二十路本地请求,返回节点标识用于统计。教学命令不记录每次耗时和错误细节,只用于基础并发检查。生产压测必须定义总量、并发、请求模型和停止条件,不能把命令指向公网业务反复执行;并发上升还可能先压满测试客户端,而非被测服务。

bash
seq 1 200 | xargs -P 20 -I '{}' curl --fail --silent --show-error --max-time 5 'http://127.0.0.1:18080/?delay=0.2' > concurrent-backends.txt

模拟慢节点需要只改本次实验后端,不干扰其他进程。下面重新启动 B 并增加基础延迟,PID 文件必须确认属于该实例。慢节点模拟增加的是连接占用时间,不等价于真实 CPU 饱和或数据库锁。轮询与最少连接在这种负载下可能表现不同,但结论应限制到模拟模型,不能扩大为所有生产接口的普遍性能提升。

bash
# 仅停止本次实验 B,执行前核对 PID 对应进程
kill -- "$(cat backend-b.pid)"
BACKEND_NAME=B BACKEND_PORT=18082 BASE_DELAY=0.5 python3 backend.py > backend-b.log 2>&1 &
printf'%s\n'"$!" > backend-b.pid

带多次请求耗时的测试程序可以同时记录节点与成功情况。下面使用线程池产生固定并发,错误也进入 CSV,不只保留成功。它采用封闭并发模型,完成一个任务才继续处理队列,不能直接代表开放到达流量。真正容量评估还应控制到达率与客户端资源,并避免协调遗漏造成长尾被低估。

python
import csv,time,urllib.request
from concurrent.futures import ThreadPoolExecutor
url='http://127.0.0.1:18080/'
defrun(i):
    start=time.perf_counter()
try:
with urllib.request.urlopen(url,timeout=5) as r:
return i,r.status,r.read().decode().strip(),time.perf_counter()-start
except Exception as e:
return i,0,type(e).__name__,time.perf_counter()-start
with ThreadPoolExecutor(max_workers=20) as ex,open('lb-results.csv','w') as f:
    w=csv.writer(f);w.writerow(['attempt','status','backend','seconds'])
    w.writerows(ex.map(run,range(200)))

统计测试结果时,失败不能被排除后仍报告整体延迟。下面分别计算全部尝试成功率与成功请求的最近秩百分位,方法明确。失败耗时另外保留,避免超时被静默丢掉。样本较小时高分位不稳定,报告同时输出数量;比较策略还需要重复代表性窗口并保证配置一致,不能用两次不同负载的结果作公平对照。

python
import csv,math
rows=list(csv.DictReader(open('lb-results.csv')))
ok=[r for r in rows if r['status']=='200']
lat=sorted(float(r['seconds']) for r in ok)
print('attempts',len(rows),'successes',len(ok),'success_rate',len(ok)/len(rows) if rows elseNone)
for q in (.5,.95,.99):
print(q,lat[max(0,math.ceil(q*len(lat))-1)] if lat elseNone)

连接数只是资源占用的代理指标。下面查询后端端口的建立连接,结果会同时包含活动与空闲持久连接,不能直接等同于算法内部活动计数。需要在相同时间窗口观察,并结合应用在途任务数。如果空闲缓存很多,总连接可能高但实际请求很少,单用 ss 数量解释最少连接选择会产生误判。

bash
ss -Hnt state established '( sport = :18081 or sport = :18082 )'

max_conns 限制后端同时活动连接,但其作用范围与是否使用共享 zone 有关。下面为各节点设置教学上限,仍需考虑空闲 keepalive 与多 worker 导致总连接数可能超过这个数。达到上限时不意味着开源基础配置自动拥有无界排队,可能出现没有可选后端。入口限流与应用背压应独立设计,避免把连接上限当成完整流量控制。

nginx
upstream app_pool {
zone app_pool 64k;
    least_conn;
server127.0.0.1:18081 max_conns=50;
server127.0.0.1:18082 max_conns=20;
keepalive16;
}

长时间流式响应会长期占用活动连接,最少连接可能因此倾向选择较空节点。下面是教学流式 location,关闭响应缓冲,并使用适合持续数据的读取间隔超时。连接持续时间仍不等于请求计算成本,一条低频流与高吞吐流占用不同资源。业务需要同时监控活动流、发送速率、CPU 和队列,策略实验应使用代表性流式负载。

nginx
location /stream/ {
proxy_pass http://app_pool;
proxy_http_version1.1;
proxy_set_header Connection "";
proxy_bufferingoff;
proxy_read_timeout60s;
}

WebSocket 升级后是一条长连接,入口 HTTP 负载均衡并不会按每条消息重新选择节点。下面在 http 定义映射,再在专用 location 传递升级头。它与普通 HTTP 的 Connection 清空配置用途不同,不能对所有请求一律发送升级。会话状态、断线恢复和节点排空都应由应用设计,算法不能自动迁移已经升级的连接。

nginx
map$http_upgrade$connection_upgrade {
default upgrade;
    '' close;
}
server {
listen127.0.0.1:18083;
location /ws/ {
proxy_pass http://app_pool;
proxy_http_version1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
    }
}

最少连接的验收记录应同时包含活动时长差异和应用资源表现。下面保留指标,不填虚构的提升百分比。若它让慢节点少接请求却整体吞吐下降,需要检查慢节点是否只是连接长而实际仍有闲置资源。策略没有绝对优劣,结果应在当前请求模型下解释,再决定是否上线或为不同接口分配不同后端组。

yaml
least_conn_review:
workload:variable_duration
shared_zone:true
keepalive_settings_equal:true
success_rate:null
backend_inflight:null
backend_cpu:null
p95_seconds:null

失败、重试与恢复,可能改变你看到的分布

被动失败判断依据实际请求尝试,不等于持续主动探测。下面以失败次数和时间窗口配置节点,具体哪些失败计数由代理重试规则决定。fail_timeout 同时参与观察窗口和暂时不可用时间,不能只理解成请求超时。空闲期间节点坏了,首次业务请求仍可能撞上它;因此可用性方案还需结合部署探测和服务发现。

nginx
upstream app_pool {
zone app_pool 64k;
server127.0.0.1:18081 max_fails=2 fail_timeout=10s;
server127.0.0.1:18082 max_fails=2 fail_timeout=10s;
}

重试策略需要限制范围、次数与总窗口。下面只对连接错误和超时进行教学配置,具体能否重试还受到请求发送与响应阶段限制。不是所有失败都能透明切换,向客户端发送部分响应后无法简单替换完整结果。增加重试可能放大后端压力,尤其故障期间所有调用方同时重试,因此次数不能只追求用户最终成功。

nginx
proxy_next_upstreamerror timeout;
proxy_next_upstream_tries2;
proxy_next_upstream_timeout3s;

明确把某些上游状态纳入重试,可以改变失败计数与尝试分布。下面是只针对幂等读取接口的备选策略,不建议无差别应用到全部写接口。五百状态可能来自业务逻辑,重试未必改善,甚至进一步压垮依赖。配置前要知道哪些状态代表可恢复暂态,哪些应直接返回,并与应用重试共同计算最大尝试数。

nginx
location /read-api/ {
proxy_pass http://app_pool;
proxy_next_upstreamerror timeout http_502 http_503 http_504;
proxy_next_upstream_tries2;
}

写接口应谨慎处理重试,固定关闭代理层下一上游是一种可审阅的教学策略。下面不意味着所有写操作绝不能重试,而是把幂等键、事务和重试责任交给明确设计。客户端、网关与应用各重试两次,可能形成多倍尝试,故障放大明显。最小风险方案应统一重试预算,并能从日志识别一次业务意图对应多少后端操作。

nginx
location /write-api/ {
proxy_pass http://app_pool;
proxy_next_upstreamoff;
}

备用节点只在主节点不可用时参与,并非低权重常态节点。下面使用新的本地教学端口作为备用,需要独立启动对应服务。备用的容量和数据一致性必须验证,否则故障时切过去仍会失败。某些负载算法与 backup 不兼容,不能把该参数随意放进 hash 或其他组;现场应根据模块文档与配置测试确认。

nginx
upstream app_pool {
server127.0.0.1:18081;
server127.0.0.1:18082;
server127.0.0.1:18084 backup;
}

故障实验只停止本次创建的后端,然后观察代理结果。下面先核对 PID 对应参数,停止 A 后记录一次请求状态。不要用生产节点直接做随机故障测试,也不要按进程名结束所有实例。实验应同时统计首次失败、切换耗时与恢复行为,不能只看后面成功就忽略故障期间真正出现的用户错误。

bash
ps -p "$(cat backend-a.pid)" -o pid,args
kill -- "$(cat backend-a.pid)"
curl --max-time 5 -sS -o /dev/null -w 'status=%{http_code} total=%{time_total}\n' http://127.0.0.1:18080/

重试日志需要把地址、状态和耗时列表一起查看。下面选择包含多个上游地址的记录,输出相关字段。逗号与分组格式需按实际日志语义解析,不应只统计最后一个值就丢弃前面的失败。入口最终二百也可能经历一次失败尝试,这会影响后端压力与用户延迟;分配算法实验必须把这种记录单独分析。

bash
jq -r 'select(.upstream | contains(",")) | [.rid,.status,.upstream,.us,.uct,.urt] | @tsv' nginx-lab/logs/access.jsonl

恢复节点后不能假定流量立即按原比例回归,被动失败窗口、旧 worker 和连接复用都可能影响。下面只恢复实验 A,然后开始一个新观察窗口。某些环境有专门的慢启动或健康检查能力,应按实际版本验证,不能在基础配置中假定自动渐进恢复。生产节点刚恢复时还要考虑缓存冷启动和依赖初始化,避免一次涌入全部流量。

bash
BACKEND_NAME=A BACKEND_PORT=18081 python3 backend.py > backend-a.log 2>&1 &
printf'%s\n'"$!" > backend-a.pid
curl --fail --max-time 2 http://127.0.0.1:18081/

会话、缓存和服务发现,算法无法独自处理

应用把会话保存在单机内存时,轮询可能暴露状态缺失。IP hash 是一种关联方式,但共享出口、IPv6、代理地址与节点变化会影响效果。下面只展示备选组,不推荐用它掩盖状态设计。更稳定的方案通常是把会话放在可共享的存储,或使用明确的业务会话路由机制,并设计节点故障后如何恢复。

nginx
upstream session_pool {
    ip_hash;
server127.0.0.1:18081;
server127.0.0.1:18082;
}

通用 hash 可以按业务键选择节点,一致性 hash 可以降低成员变化时部分键的迁移范围。下面使用请求 URI 作为教学缓存键,不适用于用户会话授权。查询参数、大小写与编码都会改变键,因此要定义规范化。某个热点键仍可能集中压向一个节点,hash 的稳定性不等于负载一定均匀,热点处理需要额外设计。

nginx
upstream cache_pool {
hash$request_uri consistent;
server127.0.0.1:18081;
server127.0.0.1:18082;
}

真实客户端地址应只从可信代理恢复,不能无条件信任外部请求头。下面的地址段是文档示例,需替换为受控代理范围,并确认模块可用。恢复地址会影响日志、限流与 IP hash,错误信任范围可能让调用方伪造身份。地址还原属于入口边界设计,必须与网络拓扑一致,不能为了分配均匀随意修改来源字段。

nginx
set_real_ip_from192.0.2.0/24;
real_ip_header X-Forwarded-For;
real_ip_recursiveon;

静态域名后端与动态解析后端的行为不同。开源版本从 1.27.3 起提供这里使用的动态解析能力,resolve 需要共享区与 resolver,较旧构建应先核查支持情况。下面采用内部 DNS 文档地址,只用于配置说明。解析器应可信且可达,TTL 与失败缓存会影响更新速度。动态发现并不证明新节点健康,服务注册仍需保证只发布已经可以接流量的实例。

nginx
upstream discovered_pool {
zone discovered_pool 64k;
resolver192.0.2.53 valid=30s;
server backend.example.com:8080 resolve;
}

检查 DNS 返回,可以判断域名当前是否对应预期节点,但 Nginx 是否已更新还要根据其配置行为验证。下面分别查询解析器与系统解析结果,二者可能不同。容器内解析器、搜索域和缓存也可能改变结果。故障排查应明确代理实际使用哪个 resolver,不要拿笔记本解析到的新地址证明服务器已切换。

bash
dig @192.0.2.53 backend.example.com A
getent ahostsv4 backend.example.com

缓存命中会减少上游请求,导致后端比例与入口流量不同。下面仅展示缓存状态响应头,配置是否开启缓存需要查看实际生效文件。比较策略时应禁用实验缓存或分别统计命中与未命中。缓存让吞吐提高不代表后端处理能力提高,也可能掩盖热点冷启动;容量方案应包含缓存失效时的回源压力。

nginx
add_header X-Lab-Cache $upstream_cache_status always;

不同成本接口可以使用独立 upstream,让策略与容量边界更清楚。下面将普通请求与流式请求分开,组定义在同一片段中给出,采用相同节点以说明逻辑边界。这样做不自动分离物理资源,如果两个组仍指向同一进程,压力依旧共享。真正隔离需要后端实例、队列或并发预算支持,配置命名只是表达意图,应通过资源观测验证效果。

nginx
upstream short_pool {
server127.0.0.1:18081;
server127.0.0.1:18082;
}
upstream stream_pool {
    least_conn;
server127.0.0.1:18081;
server127.0.0.1:18082;
}
location /api/ {
proxy_pass http://short_pool;
}
location /stream/ {
proxy_pass http://stream_pool;
proxy_bufferingoff;
}

上线验证要同时看入口和后端,不只看分配比例

展开最终配置可以检查包含文件、继承关系和实际生效的策略。下面输出可能含内部地址与机密参数,应保存到受控位置。一个片段写了最少连接,不代表生产入口使用了该组;location 优先级和多个 server 也可能改变请求路径。上线前应从请求入口沿配置找到实际 proxy_pass,再核对组定义和超时,避免改了未被使用的文件。

bash
nginx -T > nginx-effective-config.txt 2>&1

基础状态接口提供活动客户端连接与请求计数,但不是每个 upstream 的完整活动状态。下面仅监听回环教学端口并限制访问,需要构建包含对应模块。不能把其 Active connections 直接叫作后端活动连接数,它描述的是该状态模块定义的入口连接状态。监控中应按指标来源命名,避免与应用在途任务数混合比较。

nginx
server {
listen127.0.0.1:18085;
location /status {
        stub_status;
allow127.0.0.1;
deny all;
    }
}

状态读取适合作为运行窗口的补充信息,单次结果不能判断容量上限。下面记录本地状态接口,正式监控应周期采样并计算计数增量。Active、Reading、Writing 与 Waiting 的定义不同,等待连接可能是空闲 keepalive。活动数量上升需要结合请求速率和延迟,不能看到连接多就立即调整 worker 或切换算法。

bash
curl --fail --max-time 2 http://127.0.0.1:18085/status

后端 CPU 与内存是算法看不到的实际资源。下面读取短时进程资源,进程筛选仅适用于本地教学服务。单个核心饱和与整机平均低利用率可能同时存在,应用线程模型也会影响表现。若最少连接把流量集中到某节点而 CPU 持续高,应检查请求成本与节点能力,必要时调整权重或分离工作类型,而不是认为算法一定会自动保护 CPU。

bash
pidstat -u -r -C python3 1 5

入口五百类比例应同时报告总请求数量,并保留非代理生成错误的区别。下面对教学 JSON 日志聚合,不把状态字符串和未知值隐式当零。jq 读取失败必须处理,格式损坏会影响完整性。业务错误以二百返回时还需要应用指标;Nginx 成功率是入口协议层观察,不能独自代表业务事务成功率。

bash
jq -r '.status' nginx-lab/logs/access.jsonl | awk ' /^[0-9][0-9][0-9]$/ {n++;e+=($1>=500&&$1<600)} END{if(n)printf "n=%d errors=%d rate=%.4f\n",n,e,e/n;else print "no_data"}'

请求延迟需要明确采样范围与失败处理。下面只导出合法请求耗时,排序后可按前面的最近秩脚本计算。它包含日志窗口中的所有状态,若要只算成功必须显式过滤。多策略比较应保持同样定义;一个策略失败更快会降低全量延迟,却不改善用户结果,因此成功率、成功延迟和失败耗时应一起看。

bash
jq -r '.rt' nginx-lab/logs/access.jsonl | awk '/^[0-9]+([.][0-9]+)?$/' | LC_ALL=C sort -n > nginx-request-seconds.txt

回退文件应在修改之前保存,且包含关联配置,不只备份一个算法行。下面只备份实验片段并保留时间标识。生产应由配置管理保存完整版本,回退还要考虑服务发现和应用状态变化。reload 可恢复配置选择,但不能撤销已经处理的请求,也不能把已建立长连接自动迁移回旧节点,因此回退方案要说明存量连接处理。

bash
cp -- nginx-lab/conf/app.conf "nginx-lab/conf/app.conf.before.$(date +%Y%m%d%H%M%S)"

实验结束用对应前缀向本次实例发送退出信号,防止误停系统代理。下面采用优雅退出,会等待部分现有处理完成,长连接可能延长收尾。后端进程清理也要核对 PID 仍对应本次程序,防止编号复用。操作完成应检查端口和文件归属,留下结果与配置记录,再删除不需要的临时流量日志。

bash
nginx -p "$PWD/nginx-lab/" -c conf/nginx.conf -s quit

入口背压和资源预算,让负载策略有可承受的边界

每个来源的速率限制可以减少某个调用方突然放大压力,但来源身份必须可信。下面在 http 上下文创建教学限流区,以地址二进制值作为键。共享出口下多个用户会共用配额,所以不能机械按 IP 实施业务套餐限制。限流键、阈值与峰值预算应由调用方模型决定,算法选择只分配已进入的请求,不会自动减少到达率。

nginx
limit_req_zone$binary_remote_addr zone=per_client:10m rate=20r/s;

把限流应用到具体接口时,要明确允许的短时突发与拒绝状态。下面即时处理允许突发范围内的请求,超出后返回四百二十九;数值是教学值。nodelay 并不让后端免于承受突发,它只是影响限流等待行为。上线前应观察真实调用方峰值并提供重试建议,避免正常批量任务被过度拒绝,同时防止客户端立即重试再次放大压力。

nginx
location /limited-api/ {
limit_req zone=per_client burst=40 nodelay;
limit_req_status429;
proxy_pass http://app_pool;
}

限制连接需要先定义共享统计键,接口中的并发请求与 TCP 连接也不总是一一对应。下面按服务名称建立教学区域,适合说明全入口预算。对于 HTTP/2 与 HTTP/3,模块会按其规则处理并发请求,不能简单认为一个复用 TCP 连接只占一份业务并发。现场应阅读模块语义,监控里也应将连接与请求区分。

nginx
limit_conn_zone$server_name zone=per_server:10m;

入口并发限制与 upstream 的 max_conns 控制不同范围。下面给教学 location 设置并发上限与拒绝状态,避免在所有后端都已繁忙时继续无限进入。合理阈值应依据后端整体能力、请求时长和服务目标计算。限流拒绝不是后端算法故障,日志需要单独记录,否则四百二十九上升可能被误解释成应用错误突然增加。

nginx
location /bounded-api/ {
limit_conn per_server 100;
limit_conn_status429;
proxy_pass http://app_pool;
}

每个 worker 的连接预算包含代理两端以及其他连接,并非全部都能分给客户端。下面展示教学资源设置,不推荐直接用于任意机器。文件描述符还被日志、文件与其他对象占用,服务启动管理器的限制也可能成为上限。调整连接预算之前应计算并发与内存成本,单纯增大数值可能让系统允许更多排队,却使延迟和恢复时间更差。

nginx
worker_processes auto;
worker_rlimit_nofile8192;
events {
worker_connections4096;
}

系统服务文件描述符限制需要查看实际管理配置。下面读取 Nginx 的限制值,实验独立进程则应检查其自身启动环境。配置中的提升意图可能受权限与管理器限制影响,因此运行结果应再次验证。后端连接耗尽时还要检查上游程序自己的限制,入口容量足够并不意味着应用进程也能接受相同数量的套接字。

bash
systemctl show nginx.service -p LimitNOFILE -p LimitNOFILESoft

可以用简化模型估算代理连接预算,但必须写明假设。下面假设四个 worker、每个四千零九十六连接,预留两千其他连接,每个代理请求粗略占两条连接。结果只是数量上界估计,不包含 HTTP 多路复用、空闲缓存和资源瓶颈。它适合发现明显预算缺口,不能作为承诺吞吐的依据,真实上限仍需代表性负载验证。

python
workers=4
connections_per_worker=4096
other_connections=2000
rough_proxy_slots=max(0,(workers*connections_per_worker-other_connections)//2)
print('illustrative_connection_slots',rough_proxy_slots)

上传请求缓冲决定入口先读完请求再转发,还是较早让后端参与接收。下面对教学上传接口关闭请求缓冲,可能让后端连接更长时间被慢上传占用,也可能限制发送后重试。选择最少连接时需要考虑这个变化:连接数升高可能是客户端上传慢,而非应用处理慢。上传大小、超时与磁盘临时空间应同时规划,不能只改一个开关追求低等待。

nginx
location /upload/ {
proxy_pass http://app_pool;
proxy_request_bufferingoff;
client_max_body_size20m;
}

响应缓冲影响客户端慢读取时后端连接的占用。下面启用有限缓冲,参数为教学值,需符合模块大小约束并通过配置检查。大响应可能落到临时文件,磁盘负载与空间因此进入容量预算。关闭缓冲适合某些流式场景,开启缓冲则可能更快释放上游,但不能由一个开关推断所有接口最佳行为,要根据响应大小与实时性需求选择。

nginx
proxy_bufferingon;
proxy_buffer_size8k;
proxy_buffers816k;
proxy_busy_buffers_size32k;

协议边界和分阶段发布,把策略实验变成可上线方案

上游使用 TLS 时,应验证证书与服务器名称,不能为了连接成功长期关闭验证。下面通过命名 upstream 访问 HTTPS,并显式设置可信 CA 与证书名称;文件路径和名称都是待替换的教学值。SNI、证书校验名称与 HTTP Host 可能不同,必须按后端实际服务核对。负载算法不会修正 TLS 身份错误,每个节点都应满足同样的连接安全条件。

nginx
upstream secure_pool {
server192.0.2.20:443;
server192.0.2.21:443;
}
location /secure-api/ {
proxy_pass https://secure_pool;
proxy_ssl_server_nameon;
proxy_ssl_name backend.example.com;
proxy_ssl_verifyon;
proxy_ssl_trusted_certificate /etc/nginx/ca/backend-ca.pem;
}

直接验证后端 TLS 有助于区分算法选择与证书故障。下面连接文档地址,显式提供名称、主机名验证和可信 CA;现场替换后只对自己的端点测试。握手成功还要检查验证结果与服务协议,不是出现一段证书输出就代表身份检查通过。若某个节点证书不一致,只有流量分到它时失败,表现很像随机负载问题,但根因在节点配置差异。

bash
openssl s_client -connect 192.0.2.20:443 -servername backend.example.com -verify_hostname backend.example.com -verify_return_error -CAfile backend-ca.pem </dev/null

请求标识向后端传递可以把入口一次请求与应用日志关联,生成和信任规则应明确。下面使用入口自己生成的标识,不直接接受任意外部值作为唯一关联键。发生上游重试时,同一个入口标识可能对应多个后端尝试,应用日志需要保留节点与尝试信息。链路关联帮助解释分配和失败过程,但不会自动把多个尝试合并为一次业务事务。

nginx
proxy_set_header X-Request-ID $request_id;
proxy_set_header X-Forwarded-Proto $scheme;

带 URI 的 proxy_pass 会涉及路径替换,末尾斜杠影响后端实际收到的路径。下面在前缀 location 下将 /api/ 替换为根路径,这会让 /api/orders 转为 /orders。这是路由语义,和算法无关,但错配时常表现为部分后端四百四。实验应同时验证路径与节点标识,避免只看回应来自哪台机器却忽略它收到的是错误接口。

nginx
location /api/ {
proxy_pass http://app_pool/;
}

不带 URI 的 proxy_pass 在这个普通前缀示例中保留原请求路径,和前一片段是替代选择。下面让后端收到 /api/orders。重写、变量和正则 location 会引入额外规则,不能把简化例子扩展为所有配置。统一后端路由契约非常重要:同一个组里如果 A 与 B 期待不同路径,负载均衡会把配置不一致暴露为间歇性错误。

nginx
location /api/ {
proxy_pass http://app_pool;
}

客户端读请求与写响应也有自己的超时边界,不能只配置上游读取超时。下面数值用于教学,间隔语义与整体业务截止时间不同。客户端慢上传可能长时间占用入口,客户端慢读取也可能影响缓冲与连接预算。策略比较时保持这些配置一致,并记录客户端超时,否则某算法看起来更快可能只是测试端更早放弃请求。

nginx
client_header_timeout10s;
client_body_timeout30s;
send_timeout30s;

一个组只有一个后端时,不能期待靠被动失败参数自动切到并不存在的替代节点。下面单节点示例用于提醒:仅有一个 server 时,max_fails、fail_timeout 与 slow_start 会被忽略,该节点不会按这些参数被判为不可用。高可用需要真实的可替代容量和一致性,写几个阈值不能凭空获得冗余。维护单节点服务时,入口错误处理和业务降级应作为独立方案,而不是算法选择问题。

nginx
upstream singleton_pool {
server127.0.0.1:18081 max_fails=3 fail_timeout=10s;
}

分阶段发布记录应该包含配置版本、流量范围、停止条件和回退责任。下面保持所有指标待填写,不把教学期望写成生产结果。小流量阶段要有足够代表性的长短请求与失败样本,不能只验证健康接口。达到停止条件就应暂停扩大范围,先解释错误与延迟变化,再决定继续或回退;发布过程的可控性比一次性切换到某个算法更重要。

yaml
rollout:
config_version:replace_with_reviewed_version
traffic_scope:limited_canary
workload_coverage: [short_request, long_request, upload, retry]
success_rate_floor:set_from_actual_SLO
p95_regression_limit:set_from_actual_SLO
stop_owner:replace_with_responsible_person
rollback_version:replace_with_verified_previous_version

把实验结果用于选型,而不是把策略名字当成结论

以一组虚构场景说明。两个后端配置相同,所有请求都在几十毫秒内完成,数据库依赖相同,接口没有单机状态。在这种情况下,轮询是很好的基线。它容易解释,也容易统计。切换最少连接未必带来显著收益,因为每次选择时活动连接数量可能很接近。选型应比较实际尾延迟和错误,不必为了使用更复杂策略增加维护难度。

第二个场景中,部分请求是短查询,部分请求是持续数十秒的流式响应。轮询按次数分配,可能让某节点同时积累较多长请求。最少连接可以观察未结束连接,从而在一定程度上把新请求发往较空节点。但一条流可能几乎不耗 CPU,另一条流却持续生成大量数据,所以连接数仍只是代理指标。需要查看每节点活动流、发送速率、CPU 与应用队列,确认收益来自合理资源分配,而不是把更多工作集中到暂时连接少的节点。

第三个场景里,A 的代表性容量是 B 的三倍。给 A 三倍权重可能合理,但容量不能只由核数决定。A 与 B 共享一个数据库,数据库饱和后,两台应用节点的可用吞吐可能同时下降;A 更快只是减少应用层等待,未必增加系统总处理能力。权重应建立在相同业务负载和延迟目标下的测量,且在依赖变化时重新评估。静态权重表达的是已知能力差异,不是实时自动调度系统。

还要考虑故障窗口。假设 B 一度连接超时,入口对读取请求重试到 A,最终状态仍是二百。用户成功率可能保持较高,但延迟增加,上游尝试数也大于入口请求数。此时看到 A 占比增大,并不说明轮询失效,而是失败状态与重试改变了可选集合。报告应分别列正常分配、首次失败、重试去向和恢复阶段,这样才能解释算法在不同状态下的行为。

最终的选择可以按负载类型分层:相近成本短请求先以轮询建立基线,已验证的异构能力用权重表达,连接占用时间差异明显时评估最少连接,稳定会话和缓存键则采用明确状态或 hash 设计。所有策略都需要限流、超时、重试预算、日志与容量监控配合。算法只负责选择,服务可用性来自整个调用链对失败、资源与恢复的设计。

参考资料

  • Nginx HTTP 负载均衡说明
  • HTTP upstream 模块
  • HTTP proxy 模块
  • HTTP 日志模块
  • stub_status 模块
文末福利

今天给大家分享一份超级牛掰的Linux学习笔记,足足有1456页!是一位Linux运维大佬整理分享的,分享是获得大佬同意的,大家有需要的尽管收藏起来!

笔记介绍

这份笔记非常全面且详细,从Linux基础到shell脚本,再到防火墙、数据库、日志服务管理、Nginx、高可用集群、Redis、虚拟化、Docker等等,与其说Linux学习笔记,不如说是涵盖了运维各个核心知识。

并且图文并茂,代码清晰,每一章下面都有更具体详细的内容,十分适合Linux运维学习参考!

笔记展示

笔记下载

扫描下方二维码,回复暗号“1456页Linux笔记“,即可100%免费领取成功

随机文章