LabHub
学习 学习路径 课程

Tomcat 与 nginx 运维

放两台服务器,和扛得住故障,不是一回事

在 LabHub 中继续学习

一句话总结

负载均衡的首要目的不是把请求平均分成两半,而是防止一台服务器的故障扩散成整个服务的故障。因此真正需要选择的不是算法,而是何时判定失败,以及何时重新加入。

概念图: 何时判定失败,以及何时重新加入。 · 不是主动健康检查。 · 没有流量就发现不了故障。 · 每个 worker 进程独立判断。

为什么需要它

启动两台后端并放入 nginx upstream,并不代表高可用已经完成。如果不决定何时排除故障服务器、是否复用连接、会话存放在哪里,用户就会在每次请求时交替看到成功与失败。

最常见的事故是这样开始的:一台后端在部署过程中持续 30 秒返回 500。nginx 并不知道这一点,仍把一半请求发过去。在用户看来,就是“刷新后有时成功、有时失败”。监控只看平均值,50% 的错误率还可能被稀释成 25%,告警因此延迟。

如何选择算法

方式 分配依据 适用场景 注意事项
round_robin(默认) 请求顺序 处理时间均匀的 API 混入慢请求后偏差会增大
least_conn 正在处理的连接数 响应时间波动较大的服务 长连接 SSE、WebSocket 会扭曲判断
ip_hash 客户端 IP 哈希 把会话留在服务器内存中的遗留系统 增减服务器会整体改变分配
hash $key consistent 任意键的一致性哈希 重视缓存命中率的场景 键设计不当会使流量偏向一侧

只有服务器性能不同时才使用 weight。相同规格却设置不同权重,会让容量计算失真,故障时其中一侧更早崩溃。

故障发现其实是被动的

max_failsfail_timeout 不是主动健康检查。 它们通过观察真实请求失败,暂时排除服务器,因此会产生三个后果。

哪些情况计为失败,由 proxy_next_upstream 决定。默认值是 error timeout,因此后端返回的 500 不算失败。 即使应用已经出错并持续返回 500,nginx 仍会向它发请求。必须显式加入 http_500 http_502 http_503 才会计数。但若没有同时谨慎处理 non_idempotent,POST 可能被执行两次,因此重试对象必须限定为幂等请求。

upstream Keep-Alive 必须同时满足三个条件

复用连接可以省去 TCP 握手与 TLS 协商,但必须同时完成三项配置。缺少任何一项,系统都会静默地为每次请求新建连接。

upstream app {
  server 127.0.0.1:8080;
  server 127.0.0.1:8082;
  keepalive 32;                  # 1) 워커당 유지할 유휴 연결 수
}

location / {
  proxy_pass http://app;
  proxy_http_version 1.1;        # 2) 기본값 1.0 은 keep-alive 를 못 쓴다
  proxy_set_header Connection "";# 3) 클라이언트가 보낸 Connection 헤더를 지운다
}

keepalive 不是“并发处理量”,而是“保留的空闲连接数”。它乘以 worker 数量后,不应超过后端最大并发连接设置。若 Tomcat 的 maxThreads 为 200,而 4 个 nginx worker 各保留 100 条连接,就会有 400 条连接堆积等待线程。

会话粘滞是最后手段

使用 ip_hash 固定会话时,只要增减服务器,哈希空间就会改变,大量用户会被重新分配到其他服务器,而新服务器内存中没有其会话,于是同时退出登录。 每次部署都可能发生。而公司或学校网络中的用户常位于同一 NAT 后方,IP 相同,流量还会集中到一台服务器。

优先级应如下。

  1. 把会话移出服务器——放到 Redis 或 DB,任何服务器都能处理
  2. 把状态放进令牌——使用签名 JWT 时,服务器无需记忆任何内容
  3. 实在不行再粘滞——此时基于 cookie 的 sticky 也优于 ip_hash

在实际项目中

故障只偶尔出现时,先在 access log 中记录 $upstream_addr$upstream_status,按后端拆分结果。

log_format lb '$remote_addr [$time_local] "$request" $status '
              '$upstream_addr $upstream_status '
              '$upstream_response_time $request_time';

关键是会读日志。如果 $upstream_addr 中出现由逗号分隔的两个地址,说明发生过重试(127.0.0.1:8080, 127.0.0.1:8082)。只有特定地址返回 5xx 时,应先检查部署或配置差异,而不是分配算法;所有地址同时变慢时,再调查公共 DB 或下游服务。

$upstream_response_time$request_time 的差异也用于诊断。两者相近,说明后端慢;只有 $request_time 较大,说明客户端网络或响应传输较慢。

下一实验要做什么

亲自启动两个后端,对比轮询、权重、会话粘滞、故障排除与连接复用。在日志中记录 $upstream_addr,统计请求发往哪一侧;再故意关闭一台,确认 max_fails 实际在多少次失败后生效。最后整理每种算法应何时使用、何时避免的运维标准。