问题引入:半夜被报警短信炸醒的滋味
上个月有个周三,凌晨两点,我被钉钉报警震醒了。
打开手机一看,全是 “tomcat 响应超时”、“接口 504 gateway timeout” 的告警。公司业务最近推广了一波,日活从几千蹭蹭涨到了几万,结果那台孤零零的 tomcat 服务器开始扛不住了。
白天用户访问慢还能忍,到了高峰期直接频繁 502/504,客服群里炸锅,老板在群里 @ 我:“怎么回事?修一下。”
我爬起来看监控,cpu 飙到 90%,内存快满了,gc 频率高得吓人。扩容机器?可以,但一台 8 核 16g 的服务器也不便宜啊。而且就算硬件升级了,单点故障的问题还是没解决——这台 tomcat 一挂,整个服务就全凉了。
说白了,一台服务器就像一家只有一个厨师的餐厅,饭点一到,客人全堵在柜台前,厨师累得手忙脚乱,后面的客人等不及就开始骂娘。
那怎么办?多请几个厨师,再找个机灵的服务员在门口统筹安排——客人来了,服务员负责引导到不同的厨师那儿,谁闲了给谁派活。这样既能分流压力,某个厨师请假了,其他厨师还能继续干活。
这个"机灵的服务员",就是咱们今天要聊的 nginx。
方案分析:为什么选 nginx?
我一开始也想过几个方案:
方案 a:直接升级 tomcat 服务器配置
- 优点:简单,改个云服务器配置就行
- 缺点:贵,而且单点故障没解决,tomcat 本身并发处理能力也有限
方案 b:用 tomcat 集群 + 硬件负载均衡
- 优点:稳定
- 缺点:硬件 f5 贵得离谱,小公司玩不起
方案 c:nginx 反向代理 + 多 tomcat 节点
- 优点:免费开源、性能彪悍、配置灵活、社区生态成熟
- 缺点:需要学一点配置语法(但其实不难)
对比下来,nginx 就是性价比之王。它不仅能做反向代理和负载均衡,还能处理静态资源、做缓存、压缩、https 终止、限流防刷……简直就是运维界的瑞士军刀。
但这里有个概念很多新手容易懵:反向代理和正向代理到底啥区别?
用餐厅服务员来类比
正向代理,就像你(客户端)想打电话给某个明星,但你不方便直接打,于是找了个中间人(代理)帮你打。明星不知道电话是你打的,只知道是中间人打的。正向代理代理的是客户端,隐藏的是"你"。
反向代理,就像你去一家高档餐厅吃饭,门口有个服务员接待你,把你领到具体的厨师那儿。你根本不知道厨师是谁、在哪,你只跟服务员打交道。反向代理代理的是服务端,隐藏的是"后厨"。
nginx 就是那个站在门口的服务员。用户访问的是 nginx 的 80 端口,nginx 再把请求转发给后端的 tomcat 1、tomcat 2、tomcat 3……用户完全感知不到后端有几台服务器。
实现过程:step by step 上手
好了,概念聊完了,咱们来点实在的。假设你现在有两台 tomcat,分别跑在 192.168.1.101:8080 和 192.168.1.102:8080,咱们用 nginx 把它们代理起来。
step 1:安装 nginx(略过编译安装的坑)
如果你用 centos,直接 yum 装:
# 添加 epel 源后安装 sudo yum install nginx -y # 启动并设置开机自启 sudo systemctl start nginx sudo systemctl enable nginx
ubuntu 的话用 apt install nginx。编译安装太折腾,新手建议先用包管理器,把核心概念搞明白再说。
step 2:配置反向代理
nginx 的核心配置文件一般在 /etc/nginx/nginx.conf,但咱们更常在 /etc/nginx/conf.d/ 下创建独立的 .conf 文件,方便管理。
先来看一个最简版的反向代理配置:
# /etc/nginx/conf.d/myapp.conf
server {
listen 80;
server_name api.example.com;
location / {
# 把所有请求转发到后端 tomcat
proxy_pass http://192.168.1.101:8080;
# 关键:把客户端真实 ip 传给后端,不然 tomcat 日志里全是 nginx 的 ip
proxy_set_header host $host;
proxy_set_header x-real-ip $remote_addr;
proxy_set_header x-forwarded-for $proxy_add_x_forwarded_for;
}
}这段要干嘛? 告诉 nginx:所有访问 api.example.com:80 的请求,都帮我甩给 192.168.1.101:8080。
关键点在哪?
proxy_pass是核心,指定后端地址proxy_set_header这三行非常重要,否则后端拿不到用户的真实 ip 和原始 host,有些业务逻辑会出问题
配置写完后,一定要执行 reload:
sudo nginx -s reload
很多新手改完配置发现没生效,就是因为忘了这步。后面我会专门讲这个坑。
step 3:负载均衡——一台不够,多台一起扛
现在咱们有两台 tomcat 了,总不能只代理一台吧?nginx 的 upstream 模块就是干这个的。
# 先定义一个后端服务器池,叫 tomcat_cluster
upstream tomcat_cluster {
server 192.168.1.101:8080;
server 192.168.1.102:8080;
}
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://tomcat_cluster; # 注意这里写的是 upstream 的名字
proxy_set_header host $host;
proxy_set_header x-real-ip $remote_addr;
proxy_set_header x-forwarded-for $proxy_add_x_forwarded_for;
}
}这段要干嘛? 把请求轮流分发给两台 tomcat,实现最基本的负载均衡。
默认策略是轮询(round-robin),就是请求 1 给 101,请求 2 给 102,请求 3 给 101,依次循环。
step 4:四种负载均衡策略,怎么选?
nginx 支持好几种负载均衡策略,不同的场景用不同的策略,别只会轮询。
1. 轮询(round-robin)——默认策略
upstream tomcat_cluster {
server 192.168.1.101:8080;
server 192.168.1.102:8080;
}适用场景:后端机器配置差不多,请求处理时间也差不多。简单公平。
2. 权重(weight)——能者多劳
upstream tomcat_cluster {
server 192.168.1.101:8080 weight=3;
server 192.168.1.102:8080 weight=1;
}适用场景:两台机器配置不一样,101 是 8 核 16g,102 是 4 核 8g。那就让 101 多扛点活,权重设为 3:1。
3. ip_hash——同一个用户始终落在同一台机器
upstream tomcat_cluster {
ip_hash;
server 192.168.1.101:8080;
server 192.168.1.102:8080;
}适用场景:你的应用用了本地 session 存储,用户登录状态存在 tomcat 内存里。如果请求被分配到不同机器,用户就会频繁掉线。
但要注意:ip_hash 不是万能的,如果某台机器挂了,原本落在这台的请求会重新 hash 到其他机器。而且如果用户在公司内网,出口 ip 相同,可能会导致某一台机器压力特别大。
4. least_conn——谁闲给谁
upstream tomcat_cluster {
least_conn;
server 192.168.1.101:8080;
server 192.168.1.102:8080;
}适用场景:接口处理时间差异很大,有的请求 10ms 搞定,有的要 10 秒。least_conn 会把新请求发给当前连接数最少的机器,更智能一些。
我的建议:如果做了分布式 session(比如 redis 存 session),优先用 轮询 或 least_conn;如果还是本地 session,临时用 ip_hash 过渡,但长远看还是要上分布式 session。
step 5:静态资源分离——别让 tomcat 干杂活
tomcat 处理动态请求还行,但让它去传图片、css、js,那就是大材小用,还拖累动态接口的响应速度。
nginx 传静态文件的能力是 tomcat 的几十倍,所以咱们要让 nginx 直接处理静态资源,动态请求才转发给 tomcat。
server {
listen 80;
server_name api.example.com;
root /usr/share/nginx/html;
# 静态资源直接由 nginx 返回
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2)$ {
expires 30d; # 缓存 30 天
add_header cache-control "public, immutable";
access_log off; # 关闭静态资源访问日志,减少磁盘 io
}
# 动态请求转发给 tomcat
location /api/ {
proxy_pass http://tomcat_cluster;
proxy_set_header host $host;
proxy_set_header x-real-ip $remote_addr;
proxy_set_header x-forwarded-for $proxy_add_x_forwarded_for;
}
}这段要干嘛? 图片、css、js 这些文件,nginx 自己从磁盘读出来返回给用户;只有 /api/ 开头的接口请求,才转发给 tomcat。
效果很明显:我上次加了这个配置后,tomcat 的 cpu 使用率直接降了 30%,页面加载速度也快了一截。
step 6:gzip 压缩——让传输更快
现在的前端资源动不动就几百 kb,开启 gzip 压缩能省不少带宽。
http {
# 在 nginx.conf 的 http 块里加
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
gzip_min_length 1k; # 小于 1k 的文件不压缩,省 cpu
gzip_comp_level 4; # 压缩级别 1-9,4 是性价比平衡点
}为什么要设 gzip_min_length 1k? 因为压缩本身也要消耗 cpu,如果文件本来就几字节,压缩后可能反而更大,得不偿失。
step 7:https 配置——现在没 https 都不好意思上线
申请一个免费 ssl 证书(let’s encrypt 或者阿里云免费证书),配置很简单:
server {
listen 443 ssl;
server_name api.example.com;
ssl_certificate /etc/nginx/ssl/api.example.com.crt;
ssl_certificate_key /etc/nginx/ssl/api.example.com.key;
ssl_protocols tlsv1.2 tlsv1.3;
location / {
proxy_pass http://tomcat_cluster;
proxy_set_header host $host;
proxy_set_header x-real-ip $remote_addr;
proxy_set_header x-forwarded-for $proxy_add_x_forwarded_for;
}
}
# http 自动跳 https
server {
listen 80;
server_name api.example.com;
return 301 https://$server_name$request_uri;
}关键提醒:https 配完后,一定要检查证书有效期,设置自动续期。我有一次就因为证书过期了,导致全站无法访问,被老板在群里点名批评……
step 8:限流防刷——给系统加个保险杠
接口被爬虫狂刷怎么办?nginx 可以简单限流。这里介绍两种常用的:
限制单 ip 并发连接数:
# 在 http 块定义一个连接限制区域
limit_conn_zone $binary_remote_addr zone=addr:10m;
server {
location /api/ {
limit_conn addr 10; # 单个 ip 最多 10 个并发连接
proxy_pass http://tomcat_cluster;
}
}限制请求速率(漏桶算法):
# 在 http 块定义
limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;
server {
location /api/ {
limit_req zone=one burst=20 nodelay; # 每秒 10 个请求,突发 20 个
proxy_pass http://tomcat_cluster;
}
}关键点:rate=10r/s 是平均每秒 10 个请求,burst=20 允许突发 20 个请求排队处理,nodelay 表示不延迟,直接处理。如果超过了,nginx 会返回 503。
限流这玩意儿不能设太死,不然正常用户也可能被误伤。建议先设宽松一点,观察日志再调整。
一份生产级精简配置参考
说了这么多,我把上面这些整合成一份生产可用的精简配置,你可以直接拿去改改 ip 就能用:
# /etc/nginx/nginx.conf
user nginx;
worker_processes auto; # 根据 cpu 核数自动调整
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 4096; # 单个 worker 的最大连接数
use epoll; # linux 高性能网络模型
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# === 日志格式 ===
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
# === 性能优化 ===
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
client_max_body_size 50m; # 允许上传的最大文件大小
# === gzip 压缩 ===
gzip on;
gzip_vary on;
gzip_min_length 1k;
gzip_comp_level 4;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
# === 负载均衡 upstream ===
upstream tomcat_cluster {
least_conn; # 谁闲给谁,比轮询更智能
server 192.168.1.101:8080 weight=2;
server 192.168.1.102:8080 weight=1;
# 健康检查:失败 3 次认为不可用,恢复 2 次认为可用
server 192.168.1.101:8080 max_fails=3 fail_timeout=30s;
server 192.168.1.102:8080 max_fails=3 fail_timeout=30s;
}
# === 虚拟主机配置 ===
server {
listen 80;
server_name api.example.com;
# http 跳转 https(如果不需要 https 可以注释掉)
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
server_name api.example.com;
ssl_certificate /etc/nginx/ssl/api.example.com.crt;
ssl_certificate_key /etc/nginx/ssl/api.example.com.key;
ssl_protocols tlsv1.2 tlsv1.3;
root /usr/share/nginx/html;
# 静态资源直接由 nginx 处理
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2)$ {
expires 30d;
add_header cache-control "public, immutable";
access_log off;
}
# 动态 api 转发给 tomcat 集群
location /api/ {
proxy_pass http://tomcat_cluster;
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_connect_timeout 5s;
proxy_send_timeout 10s;
proxy_read_timeout 30s;
}
}
}这份配置的核心思路:
- nginx 负责 ssl 终止、静态资源、gzip 压缩、负载均衡
- tomcat 只专注处理动态业务逻辑
- 通过
least_conn和权重实现智能分流 - 健康检查确保单点故障时自动剔除异常节点
踩坑记录:我踩过的两个大坑
光讲配置没意思,分享两个我真实踩过的坑,说不定你正在踩或者即将踩。
坑一:改完配置没生效?因为你没 reload!
这个坑我踩过不下三次。
nginx 的配置文件改完后,必须执行 nginx -s reload 才能热加载新配置。如果你只改了文件就完事了,nginx 还在用旧的配置在跑。
更坑的是,有时候你执行了 reload,但语法有错误,nginx 会拒绝加载新配置,然后默默地继续用旧配置运行。你以为是新配置生效了,实际上还是老样子。
正确做法:
# 先检查语法是否正确 sudo nginx -t # 语法 ok 后再 reload sudo nginx -s reload
养成 nginx -t 的习惯,能救命。
坑二:location 路径匹配优先级搞错
nginx 的 location 匹配规则有点反直觉,不是简单的"谁在前面先匹配谁"。
它的优先级是这样的:
=精确匹配(最高优先级)^~前缀匹配~和~*正则匹配(按配置文件中的顺序)- 普通前缀匹配
/通用匹配(最低优先级)
有一次我把静态资源的正则匹配写在了 /api/ 的后面,结果某些带 .js 后缀的 api 请求被 nginx 当成静态资源处理了,直接返回 404,查了半天才发现是 location 顺序的问题。
建议:正则匹配的 location 尽量按精确度从高到低排列,或者直接用 ^~ 做前缀匹配,避免意外。
验证效果:改造前后对比
咱们来验收一下成果。
改造前:
- 单台 tomcat 扛所有请求
- 高峰期 cpu 90%+,频繁 502/504
- 静态资源和动态请求混在一起,互相拖累
改造后:
- 两台 tomcat 分担动态请求压力
- nginx 直接处理静态资源,tomcat cpu 下降约 30%
- 开启 gzip 后,静态资源体积减少 60%-70%
- 单台 tomcat 挂掉时,nginx 自动把流量切到另一台,服务不中断
虽然架构还是很简单,但对于中小型项目来说,这套方案性价比极高,花半天时间配置,能换来很长一段时间的安稳 sleep。
总结
今天咱们聊了怎么用 nginx 解决"一台服务器扛不住"的问题:
- 反向代理就像是餐厅门口的服务员,用户只跟 nginx 打交道,后端 tomcat 被隐藏起来
- 负载均衡让多台 tomcat 一起干活,策略有轮询、权重、ip_hash、least_conn,按需选择
- 静态资源分离能显著减轻 tomcat 负担,让专业的人干专业的事
- gzip 压缩和 https 配置是现代 web 服务的基本操作
- 限流防刷能给系统加一道保险杠,防止被恶意流量冲垮
当然,这套方案也不是银弹。如果业务量继续增长,后面你可能还要引入 redis 做分布式 session、用 consul 做服务发现、上 kubernetes 做容器编排……但那是后话了。
千里之行,始于一个靠谱的 nginx 配置。
到此这篇关于nginx 反向代理与负载均衡一台服务器扛不住怎么办的文章就介绍到这了,更多相关nginx 反向代理与负载均衡内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论