05 高可用负载均衡:用 Nginx 手搓四节点 LB 集群

📅 2026/7/27 19:35:01
05 高可用负载均衡:用 Nginx 手搓四节点 LB 集群
05 高可用负载均衡用 Nginx 手搓四节点 LB 集群本篇是「基于华为云 FlexusX 四节点集群的云计算全栈实操」系列第 5 篇。上篇我们测得内网 6.3 Gbps、0.13ms证明节点间走内网可行且高效。本篇就在内网之上用一台 Nginx 把流量调度到后台三节点并实测轮询与故障转移。所有命中结果均来自真实日志results/05_lb_roundrobin.txt、06_lb_failover.txt。1. 引子负载均衡Load Balancer是集群的总入口。云厂商都提供 ELB/CLB弹性负载均衡点几下就能用。但作为工程师我更想搞清楚它底层到底怎么工作——于是没买 ELB而是用 Nginx 在 node1 上自己搭了一个。为什么值得手搓三个理由① 成本——ELB 按实例/LCU 收费Nginx 白嫖现有机器② 可控——所有转发逻辑、健康检查参数都在自己手里③ 理解——亲手配过upstream和proxy_next_upstream再看云厂商的 LB 就不是黑盒了。本篇目标在 node1 跑 Nginx把请求按轮询打到 node2/node3/node4 的后端并在停掉一台后自动转移流量。2. 背景与理论对照《深入浅出云计算》课程里讲负载均衡核心是三件事调度算法轮询round-robin、加权轮询weight、最少连接least_conn、IP 哈希ip_hash。Nginx 默认轮询。健康检查要能探测后端是否存活死了就摘掉活了再加回来。云 LB 默认有自建得自己配参数。故障转移failover某次请求打到已挂的后端时能自动重试下一个健康节点对客户端透明。Nginx 的upstream模块天然支持这些。关键指令指令作用server ip:port weightN加权权重越高分到的请求越多max_failsN fail_timeout30s30s 内失败 N 次则熔断该节点 30sproxy_next_upstream error timeout http_502什么情况下把请求转给下一个 upstreamkeepalive N与后端保持的长连接数提升性能注意max_fails/fail_timeout是被动健康检查靠真实请求出错来判定不是主动探活。Nginx 商业版Plus或nginx-upsync-module才支持主动 health check。本篇用被动检查已经够证明故障转移。3. 环境与准备节点弹性公网 IP私有 IP角色node1113.47.6.41192.168.0.252Nginx LB入口node2124.70.93.52192.168.0.64后端 upstreamecs-71b9-0002node31.94.220.182192.168.0.241后端 upstreamecs-71b9-0003node4124.70.102.139192.168.0.150后端 upstreamecs-71b9-0004规格均为 8vCPU/16GiB、Ubuntu 24.04.4 LTS同 VPC 同子网内网互通见上篇实测。后端准备在三台后端各跑一个返回自身主机名的 HTTP 服务用于验证命中谁。本实验后端监听 8080响应体即主机名ecs-71b9-000x。# node2/node3/node4 上安装 nginx 作为后端或直接用 python 起服务# 方式一用 python 快速起一个返回主机名的服务cat/tmp/backend.pyPY import os, http.server, socketserver name os.popen(hostname).read().strip() class H(http.server.BaseHTTPRequestHandler): def do_GET(self): self.send_response(200); self.send_header(Content-Length,%d%len(name)) self.end_headers(); self.wfile.write(name.encode()) def log_message(self,*a): pass socketserver.TCPServer((0.0.0.0,8080),H).serve_forever() PYnohuppython3 /tmp/backend.py/dev/null214. 实操步骤与完整配置4.1 安装 Nginxnode1sudoaptupdatesudoaptinstall-ynginxsudosystemctlenablenginx4.2 完整 lb.conf放在/etc/nginx/conf.d/lb.conf注意先删掉默认的sites-enabled/default避免它抢 80 端口# /etc/nginx/conf.d/lb.conf upstream backend_pool { # 三台后端默认轮询weight 可调权重 server 192.168.0.64:8080 weight1 max_fails3 fail_timeout30s; server 192.168.0.241:8080 weight1 max_fails3 fail_timeout30s; server 192.168.0.150:8080 weight1 max_fails3 fail_timeout30s; # 与后端保持长连接减少握手开销 keepalive 32; } server { listen 80; server_name _; location / { proxy_pass http://backend_pool; proxy_http_version 1.1; # 关键开启长连接必须清掉默认的 Connection 头 proxy_set_header Connection ; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 故障转移这些情况下自动重试下一个 upstream proxy_next_upstream error timeout http_502 http_503 http_504; proxy_next_upstream_tries 3; proxy_connect_timeout 3s; proxy_read_timeout 10s; } }要点说明max_fails3 fail_timeout30s表示 30 秒内某后端失败 3 次连接/读超时Nginx 就把它从可用池摘掉 30 秒proxy_next_upstream让单次请求命中故障节点时自动换下一个客户端无感知。4.3 校验并重载sudonginx-t# 先检查语法sudosystemctl restart nginx4.4 验证命中在任意能访问 node1 公网的机器上foriin$(seq115);docurl-shttp://113.47.6.41/;echo;done5. 真实输出实测数据未篡改5.1 轮询测试15 次请求ecs-71b9-0002 (192.168.0.64) ecs-71b9-0002 (192.168.0.64) ecs-71b9-0002 (192.168.0.64) ecs-71b9-0002 (192.168.0.64) ecs-71b9-0002 (192.168.0.64) ecs-71b9-0002 (192.168.0.64) ecs-71b9-0002 (192.168.0.64) ecs-71b9-0002 (192.168.0.64) ecs-71b9-0003 (192.168.0.241) ecs-71b9-0004 (192.168.0.150) ecs-71b9-0002 (192.168.0.64) ecs-71b9-0003 (192.168.0.241) ecs-71b9-0004 (192.168.0.150) ecs-71b9-0002 (192.168.0.64) ecs-71b9-0003 (192.168.0.241)5.2 故障转移测试停掉 node2 后 12 次请求 n2宕机后 12次请求 ecs-71b9-0004 (192.168.0.150) ecs-71b9-0003 (192.168.0.241) ecs-71b9-0004 (192.168.0.150) ecs-71b9-0003 (192.168.0.241) ecs-71b9-0004 (192.168.0.150) ecs-71b9-0003 (192.168.0.241) ecs-71b9-0004 (192.168.0.150) ecs-71b9-0003 (192.168.0.241) ecs-71b9-0004 (192.168.0.150) ecs-71b9-0003 (192.168.0.241) ecs-71b9-0004 (192.168.0.150) ecs-71b9-0003 (192.168.0.241)6. 深度解读重点6.1 轮询确实生效但前 8 次都打 0002是为什么这是本篇最有价值的观察。严格轮询应该 0002/0003/0004 交替但实测前 8 次全是 0002。原因有两个HTTP 长连接keepalive我们配了keepalive 32proxy_http_version 1.1。客户端curl 循环和 Nginx 之间、Nginx 和后端之间都复用同一条 TCP 连接。一条已建立的连接会持续命中同一个 upstream 节点直到连接关闭。前 8 次可能都跑在同一条到 0002 的长连接上。客户端连接复用for循环里 curl 默认不保持连接但 Nginx 侧的 upstream 长连接会让请求粘在某个后端。工程结论轮询是按连接还是按请求取决于 keepalive。如果你的业务要求严格按请求打散比如做灰度染色要么关掉 keepalive要么用hash $request_uri/hash $remote_addr等更明确的策略。绝大多数 web 场景按连接轮询完全没问题还能省握手。6.2 故障转移被实打实验证了停掉 node2模拟宕机后12 次请求全部落在 0003 / 0004且稳定交替 0004↔00030002 一次都没出现。这证明Nginx 在fail_timeout内把 0002 判定为不可用并移出池proxy_next_upstream在首次命中故障节点时把请求转给下一个客户端拿到的响应正常没有 502剩余两节点接管了 100% 流量集群少一个节点仍可用。这正是高可用的核心单点故障不导致服务整体不可用。6.3 weight 与容量规划本篇三后端weight都是 1均等。真实生产里若某节点配置更强应调高weight。Nginx 的加权轮询是平滑的smooth weighted round-robinSWRR不会出现连续打满一台再换下一台的抖动。7. 踩坑与排障reload 不生效restart 才救场真实踩坑改完lb.conf我习惯性地nginx -s reload结果 curl 还是命中 Nginx 默认欢迎页新 upstream 根本没生效。折腾半天才解决——改用systemctl restart nginx立刻生效。复盘根因大概率是这两个之一默认站点没删/etc/nginx/sites-enabled/default仍然存在listen 80 default_server抢在conf.d/lb.conf之前生效reload后匹配的还是默认 server。删掉它再reload即可。reload 与配置包含顺序Nginxreload是平滑重载旧 worker 处理完存量连接再退出如果旧 worker 还握着长连接短期内你看到的仍是旧行为。经验法则怀疑配置没生效时先nginx -t确认语法再systemctl restart nginx强制重读别在 reload 上纠结并且上线前务必rm -f /etc/nginx/sites-enabled/default让 LB 配置成为唯一的 80 端口 server。另外检查include顺序conf.d/*.conf要在sites-enabled/*之前或确保只留一个。8. 自建 Nginx LB vs 云厂商 ELB/CLB维度自建 Nginx LB本篇云厂商 ELB/CLB成本免费复用 node1按实例 LCU/流量收费高可用单点node1 挂则全挂需自己再做 LB 主备多 AZ 冗余原生高可用健康检查被动max_fails需自己加主动探活主动探活丰富弹性手动扩受单机性能限制自动扩支撑百万级并发运维自己管配置/证书/升级托管少操心可控性完全可控可玩转复杂转发受限但够用我的取舍建议学习 / 测试 / 中小流量 单机 Nginx 上限约数万 QPS自建 Nginx省钱且练手。生产核心业务、要求 SLA、跨 AZ、需 WAF/证书托管直接上 ELB。自建 LB 本身又成了单点你得再为 LB 做主备Keepalived 双机复杂度反超收益。折中用 Nginx 做七层精细路由按路径/Header 转发、灰度前面挂一个 ELB 做四层流量接入和高可用。9. 小结本篇在 node1 手搓了 Nginx 负载均衡upstream轮询 weight加权 max_fails/fail_timeout被动健康检查的完整配置已给出实测轮询命中 0002/0003/0004且因keepalive 按连接复用出现前段粘同一节点的现象——这是正常且高效的停掉 node2 后 12 次请求 100% 落到 0003/0004故障转移真实有效踩坑reload不生效、restart才生效根因多半是默认站点未删 / 长连接残留自建 LB 适合学习与小流量生产核心业务建议 ELB Nginx 七层路由的组合。下一篇我们给这四个节点装上监控node-exporter Prometheus Grafana把看不见的系统变成仪表盘。脚本参考../scripts/下 LB 验证脚本轮询 / 故障转移批量 curl 并统计命中分布。