我们为什么离不开 Nginx
后端是 Spring Boot 起的 8080 服务,但直接把 8080 暴露在公网既不安全也难扩展。一来没有 TLS 卸载,二来单实例挂了没容灾,三来限流得在每个应用里写。Nginx 在我们架构里身兼数职:反向代理、负载均衡、限流、长连接优化。它配一次能管一片服务,是 Java 后端前面那道刚需的闸门。
反向代理与负载均衡
两个实例 upstream,默认轮询:
upstream order_backend {
server 10.0.1.11:8080 weight=2;
server 10.0.1.12:8080;
}
server {
location /api/ {
proxy_pass http://order_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
weight=2 让性能好的那台多扛一倍流量。我们最早没配 X-Real-IP,后端拿到的全是 Nginx 的 IP,日志里没法定位真实用户,排障吃了亏才补上。
策略选择
- 轮询:默认,适合实例同构;
- ip_hash:按客户端 IP 粘会话,但扩容时会重新分布,已登录用户可能被踢;
- least_conn:挑连接数最少的,对长请求更友好,我们订单域最终用它;
- hash $request_uri:按 URL 粘,适合有本地缓存的场景。
限流配置
用 limit_req 挡刷子流量,保护后端。令牌桶算法:
limit_req_zone $binary_remote_addr zone=api:10m rate=20r/s;
location /api/ {
limit_req zone=api burst=40 nodelay;
proxy_pass http://order_backend;
}
rate=20r/s 是稳态速率,burst=40 允许突发排队,nodelay 表示突发不延迟直接放。我们曾遇到爬虫把下单接口打满,加上这层后后端 CPU 从 95% 掉回 40%,正常用户不再被挤。limit_conn 还能限制单 IP 并发连接数,防慢连接耗尽 worker。
长连接调优
反向代理默认每次都新建到后端的连接,高并发下 TIME_WAIT 爆炸,端口耗尽会导致"偶发连接失败"。开启 keepalive:
upstream order_backend {
server 10.0.1.11:8080;
keepalive 32;
}
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_connect_timeout 3s;
proxy_read_timeout 30s;
调完后到后端的连接数从峰值 1500 降到稳定 60,后端 CPU 掉了一截,TIME_WAIT 从几千降到两位数。keepalive 32 意思是每个 worker 最多复用 32 个到后端的空闲连接。
静态资源与 gzip
前端构建产物我们直接放 Nginx,不走后端,再开 gzip 省带宽:
gzip on;
gzip_types text/css application/javascript application/json;
gzip_min_length 1024;
首页 JS 从 380KB 压到 110KB,首屏快了不少。这部分交给 Nginx 比交给 Spring Boot 省事,后端专注业务。
健康检查与优雅下线
K8s 下线 Pod 时如果直接砍,在途请求会 502。我们在 Nginx 侧配合:K8s 先摘流量(endpoint 从负载均衡移除),N8s 发 SIGTERM 给应用,应用处理完在途请求再退出。Nginx 这边用被动健康检查,发现后端连不上就摘掉,避免把请求转给已下线的实例:
proxy_next_upstream error timeout http_502;
upstream order_backend {
server 10.0.1.11:8080 max_fails=3 fail_timeout=30s;
}
max_fails=3 表示 30 秒内连挂 3 次就摘 30 秒,给应用重启留出时间。这层健康检查让我们滚动发布时用户几乎无感。
灰度发布怎么用 Nginx 做
新版本上线我们常先放 10% 流量灰度。Nginx 用 weight 做简单灰度:
upstream order_backend {
server 10.0.1.11:8080 weight=9; # 旧
server 10.0.1.12:8080 weight=1; # 新
}
但 weight 是按连接比例,不是按用户,同一用户可能新旧反复跳。要按用户粘灰度得用 hash(如按 userId 取模)或上更细的网关。Nginx 这层只适合"无状态、可随机"的粗灰度,精细灰度交给上层网关。我们把它定位成"兜底分流",不指望它做复杂路由。
常见配置错误
列几个我们踩过的 Nginx 配置错:一是 proxy_pass 后面带不带斜杠,带斜杠会把 location 匹配的路径段去掉再转发,path 错乱;二是忘了 proxy_set_header Host,后端拿到的是 upstream 名而非真实域名,生成绝对链接时出错;三是 keepalive 配了但没设 proxy_http_version 1.1 和 Connection "",keepalive 不生效,连接照样每次新建。这几个错单独看小,叠加起来就是"为什么加了 Nginx 反而慢"。我们把它写成 Nginx 配置评审清单,上线前逐项核对。
nginx 不是银弹
最后提醒:Nginx 解决的是接入层问题,业务限流、鉴权该在应用层做的还是得做,别全推给 Nginx。我们见过有人把复杂业务限流规则写进 Nginx if,又难维护又易错。Nginx 管"流量分发、TLS、基础限流、静态资源"四件它擅长的事,业务逻辑、细粒度权限留给 Spring Boot。职责边界清晰,两边都不累。
写在后面
现在回头看,《Nginx 在 Java 架构中的常见用法与调优》本身不算多难,难的是线上真出问题那十分钟里的判断。经验都是这么来的。