Java面试之Nginx负载均衡策略详解

Nginx负载均衡需结合Java生产实践:策略选型(如ip_hash保会话、least_conn适配长连接)、session共享方案(redis或JWT)、proxy_next_upstream精准重试、主动健康检查防假死。

Java后端面试中问到 Nginx 负载均衡策略,不是让你背诵定义,而是考察你是否真在生产环境调过、改过、踩过坑。Nginx 本身不属 Java 技术栈,但 Java 服务部署在它后面时,它的调度逻辑直接影响服务的可用性、会话一致性、扩容弹性——这些才是面试官想听的实战判断。

upstream 块里写错策略名,Nginx 直接启动失败

Nginx 不会静默降级,upstream 中指定不存在的负载均衡算法(比如手误写成 least_connctionip_hashh),nginx -t 就会报错:

nginx: [emerg] invalid parameter "least_connction" in /etc/nginx/conf.d/app.conf:12

常见合法策略只有 5 种(Nginx 1.23+): round_robin(默认,不需显式写)、least_connip_hashhash $request_uri consistentrandom(需 random tworandom two least_conn 等组合)。注意:

  • ip_hash 只支持 IPv4 的前 3 段哈希,IPv6 默认不兼容,要加 hash $binary_remote_addr consistent; 才能真正按客户端 IP 稳定路由
  • hash $request_uri 对带 Query 参数的请求敏感,/api/user?id=1/api/user?id=2 会被散列到不同后端——如果业务依赖 URI 局部一致性(如文件分片上传),得改用 hash $uri(去掉 query)
  • least_conn 不是“最少请求数”,而是“当前活跃连接数最少”,对长连接(WebSocket、gRPC 流)更真实,但对短连接压测时可能不如预期均衡

Java 应用启用了 Session,却配了 round_robin,结果登录态频繁丢失

这是高频翻车点。Spring Boot 默认用内存存储 HttpSession,没做共享;Nginx 若用轮询,用户第二次请求落到另一台机器,session.getId() 已失效,request.getSession(false) 返回 null。

解决路径不是“换策略”,而是明确取舍:

  • 若必须保留内存 Session → 必须配 ip_hashhash $cookie_JSESSIONID(前提是前端带 Cookie)
  • 若要水平扩展 → 改用 spring-session-data-redis,此时 round_robin 反而是最稳妥选择,无状态才可靠
  • 若用 JWT 替代 Session → 同样回归 round_robin,且应关掉 Nginx 的 proxy_cookie_path 重写,避免干扰 Token 透传

proxy_next_upstream 配少了,超时或 502 不重试,Java 接口毛刺率飙升

默认情况下,Nginx 只在后端返回 error(连接拒绝)、timeout(连接超时)时才尝试下一个 upstream server。但 Java 应用常见问题如 503 Service Unavailable(Tomcat 线程池满)、502 Bad Gateway(响应体过大被截断)、甚至 408 Request Timeout(应用处理慢),默认都不触发重试。

建议显式补全:

proxy_next_upstream error timeout http_500 http_502 http_503 http_504;

但要注意副作用:

  • 开启 http_500 后,所有后端抛出未捕获异常的请求都会重试——如果 Java 侧有非幂等操作(如扣库存),重复提交会导致资损
  • proxy_next_upstream_tries 3 控制最大重试次数,别设太大,否则平均延迟翻倍
  • 配合 proxy_next_upstream_timeout 10s,防止重试链路过长

健康检查只靠 max_fails=1 fail_timeout=10s,机器卡死却还在转发

这个配置只能发现“连接失败”,但 Java 进程假死(CPU 100%、GC 长停顿、线程池耗尽)时,TCP 握手仍成功,Nginx 会持续把流量打过去,直到超时或下游返回错误。

必须叠加主动健康检查(Nginx Plus 支持,开源版需用 nginx_upstream_check_module 编译模块,或改用 lua-resty-upstream-healthcheck):

  • 检查路径设为 Spring Boot Actuator 的 /actuator/health,返回 {"status":"UP"} 才算健康
  • fall=3 表示连续 3 次失败才摘除,rise=2 表示连续 2 次成功才恢复,避免抖动
  • 检查间隔 interval=5 秒足够,太密增加后端压力,太疏发现慢

没有主动健康检查时,至少把 max_fails 设为 3,fail_timeout 设为 30 秒,给 JVM GC 或慢查询一点缓冲时间。