一、概述
1.1 背景介绍
搞运维的应该都用过 Nginx 做负载均衡。但说实话,很多人只会最基本的 upstream 配置,搞个轮询就完事了。等到生产环境出问题——比如后端挂了一台但流量还往那边打、某台机器负载高但流量还在增加、WebSocket 连接动不动就断——才发现坑还真不少。
我们团队之前就踩过一个大坑:用 Nginx 做 TCP 代理转发 MySQL 流量,结果发现连接经常莫名其妙断开。排查了一圈才发现是 4 层代理的超时配置没设好。后来又遇到 HTTP 长连接场景,keepalive 配置不对导致性能很差。
这篇文章就把 4 层和 7 层负载均衡的区别、选型、配置和调优都讲清楚,算是我们踩坑后的经验总结。如果你正在为选层发愁,或者生产环境中总出现莫名其妙的问题,不妨从头梳理一遍。
1.2 技术特点
什么是4层和7层负载均衡?
这个"层"指的是 OSI 七层模型:
- 4层负载均衡(传输层):基于 IP 和端口转发,不理解应用层协议
- Nginx 的 stream 模块
- LVS
- 云厂商的 NLB/CLB
- 7层负载均衡(应用层):理解 HTTP/HTTPS 协议,可以根据 URL、Header 等做路由
- Nginx 的 http 模块
- HAProxy
- 云厂商的 ALB
怎么选?
| 场景 |
推荐 |
原因 |
| HTTP/HTTPS网站 |
7层 |
可以做URL路由、Header改写、SSL卸载 |
| MySQL/Redis等TCP服务 |
4层 |
不需要理解协议,直接转发 |
| WebSocket |
都可以 |
7层可以做升级处理,4层更简单 |
| 极致性能要求 |
4层 |
不解析协议,性能更高 |
| 需要请求级别控制 |
7层 |
可以根据URL、Cookie做分流 |
1.3 适用场景
- 场景一:Web 应用负载均衡,多台后端服务器分担流量
- 场景二:微服务网关,根据路径转发到不同服务
- 场景三:数据库中间层,TCP 代理转发
- 场景四:混合场景,同时需要 4 层和 7 层
1.4 环境要求
| 组件 |
版本要求 |
说明 |
| Nginx |
1.9.0+ |
4层stream模块需要1.9.0以上 |
| 操作系统 |
CentOS 7+ / Ubuntu 18.04+ |
|
| 编译选项 |
--with-stream |
使用4层需要编译时加上 |
检查是否支持 stream 模块:
nginx -V 2>&1 | grep stream
# 如果有 --with-stream 说明支持
二、详细步骤
2.1 7层负载均衡配置
这是最常用的场景,HTTP/HTTPS 反向代理。
◆ 2.1.1 基本配置
# /etc/nginx/conf.d/lb.conf
upstream backend {
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}
server {
listen 80;
server_name www.example.com;
location / {
proxy_pass http://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;
}
}
◆ 2.1.2 负载均衡算法
Nginx 支持多种负载均衡算法:
1. 轮询(默认)
upstream backend {
server 192.168.1.10:8080;
server 192.168.1.11:8080;
}
# 请求依次分发到每台服务器
2. 加权轮询
upstream backend {
server 192.168.1.10:8080 weight=5; # 权重5
server 192.168.1.11:8080 weight=3; # 权重3
server 192.168.1.12:8080 weight=2; # 权重2
}
# 按权重比例分配,10个请求:5个给第一台,3个给第二台,2个给第三台
3. IP Hash
upstream backend {
ip_hash;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
}
# 同一IP的请求总是转发到同一台后端,实现会话保持
4. 最少连接(least_conn)
upstream backend {
least_conn;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
}
# 把请求发给当前连接数最少的服务器,适合长连接场景
5. 一致性Hash(需要第三方模块或Nginx Plus)
upstream backend {
hash $request_uri consistent;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
}
# 根据请求URI做一致性hash,适合缓存场景
怎么选算法?
| 场景 |
推荐算法 |
| 无状态服务 |
轮询或加权轮询 |
| 需要会话保持 |
ip_hash |
| 长连接服务(如WebSocket) |
least_conn |
| 缓存服务 |
一致性hash |
| 服务器配置不同 |
加权轮询 |
◆ 2.1.3 健康检查
Nginx 开源版的健康检查是被动的,请求失败才会标记后端不可用。
被动健康检查:
upstream backend {
server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
}
# max_fails:失败多少次标记为不可用
# fail_timeout:不可用后多久重试
问题是:必须有请求失败才能发现问题,可能会有一部分用户受影响。
主动健康检查(需要第三方模块 nginx_upstream_check_module):
# 编译安装(需要重新编译Nginx)
git clone https://github.com/yaoweibin/nginx_upstream_check_module.git
cd nginx-1.24.0
patch -p1 < ../nginx_upstream_check_module/check_1.20.1+.patch
./configure --add-module=../nginx_upstream_check_module
make && make install
配置主动健康检查:
upstream backend {
server 192.168.1.10:8080;
server 192.168.1.11:8080;
# 每3秒检查一次,连续2次失败标记为down,连续5次成功标记为up
check interval=3000 rise=5 fall=2 timeout=1000 type=http;
check_http_send "GET /health HTTP/1.0\r\n\r\n";
check_http_expect_alive http_2xx http_3xx;
}
# 健康检查状态页
location /upstream_status {
check_status;
allow 127.0.0.1;
deny all;
}
2.2 4层负载均衡配置
4 层用于非 HTTP 协议,比如 MySQL、Redis、自定义 TCP 协议。
◆ 2.2.1 基本配置
4 层配置写在 stream 块里,和 http 块平级:
# /etc/nginx/nginx.conf
stream {
upstream mysql_backend {
server 192.168.1.20:3306;
server 192.168.1.21:3306;
}
server {
listen 3306;
proxy_pass mysql_backend;
}
}
http {
# HTTP配置...
}
或者单独文件:
# /etc/nginx/stream.conf
stream {
include /etc/nginx/stream.d/*.conf;
}
# /etc/nginx/stream.d/mysql.conf
upstream mysql_backend {
server 192.168.1.20:3306 weight=5;
server 192.168.1.21:3306 weight=3;
}
server {
listen 3306;
proxy_pass mysql_backend;
# 超时配置(很重要!)
proxy_connect_timeout 10s;
proxy_timeout 300s; # 空闲连接超时
# 开启TCP优化
proxy_socket_keepalive on;
}
在主配置中引入:
# /etc/nginx/nginx.conf 最外层添加
include /etc/nginx/stream.conf;
◆ 2.2.2 4层超时配置(踩坑重点)
4 层代理的超时配置和 7 层不一样,配错了会导致连接莫名断开。
stream {
upstream mysql_backend {
server 192.168.1.20:3306;
}
server {
listen 3306;
proxy_pass mysql_backend;
# 连接后端超时
proxy_connect_timeout 10s;
# 空闲连接超时(关键!)
# 如果设太短,长时间没数据的连接会被断开
proxy_timeout 3600s; # MySQL长连接建议设长一点
# TCP keepalive
proxy_socket_keepalive on;
}
}
踩坑案例:我们之前 proxy_timeout 用默认值 10 分钟,结果 DBA 反馈连接池的空闲连接老是被断开,因为连接空闲超过 10 分钟就被 Nginx 掐了。改成 3600s 后解决。
◆ 2.2.3 UDP负载均衡
4 层也支持 UDP:
stream {
upstream dns_backend {
server 8.8.8.8:53;
server 8.8.4.4:53;
}
server {
listen 53 udp;
proxy_pass dns_backend;
proxy_timeout 1s;
proxy_responses 1; # 期望收到几个响应包
}
}
2.3 高级配置
◆ 2.3.1 Keepalive连接池
如果不开启 keepalive,每个请求都要和后端建立新连接,性能很差。
upstream backend {
server 192.168.1.10:8080;
server 192.168.1.11:8080;
# 保持连接池,每个worker保持16个连接
keepalive 32;
# 每个连接最多处理1000个请求后关闭
keepalive_requests 1000;
# 空闲连接超时时间
keepalive_timeout 60s;
}
server {
location / {
proxy_pass http://backend;
# 使用HTTP/1.1,支持keepalive
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
注意:必须同时配置 proxy_http_version 1.1 和 proxy_set_header Connection "",否则 keepalive 不生效。
◆ 2.3.2 备份服务器
upstream backend {
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080 backup; # 备份节点,只有前两台都挂了才启用
}
◆ 2.3.3 慢启动(Nginx Plus或OpenResty)
后端服务刚启动时,不要一下子打满流量:
# Nginx Plus或使用第三方模块
upstream backend {
server 192.168.1.10:8080 slow_start=30s;
server 192.168.1.11:8080 slow_start=30s;
}
# 30秒内逐步增加流量
◆ 2.3.4 根据URL路由到不同upstream
upstream api_backend {
server 192.168.1.10:8080;
server 192.168.1.11:8080;
}
upstream static_backend {
server 192.168.1.20:80;
}
server {
listen 80;
location /api/ {
proxy_pass http://api_backend;
}
location /static/ {
proxy_pass http://static_backend;
}
location / {
proxy_pass http://api_backend;
}
}
upstream backend_v1 {
server 192.168.1.10:8080;
}
upstream backend_v2 {
server 192.168.1.20:8080;
}
# 根据Header选择后端
map $http_x_version $backend_name {
default backend_v1;
"v2" backend_v2;
}
server {
location / {
proxy_pass http://$backend_name;
}
}
2.4 SSL/TLS配置
◆ 2.4.1 SSL卸载(7层)
客户端用 HTTPS,Nginx 到后端用 HTTP:
server {
listen 443 ssl;
server_name www.example.com;
ssl_certificate /etc/nginx/ssl/example.com.crt;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
# SSL优化
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
location / {
proxy_pass http://backend; # 后端用HTTP
proxy_set_header X-Forwarded-Proto $scheme;
}
}
◆ 2.4.2 SSL透传(4层)
不解密,直接转发加密流量:
stream {
upstream https_backend {
server 192.168.1.10:443;
server 192.168.1.11:443;
}
server {
listen 443;
proxy_pass https_backend;
# SSL透传,不解密
ssl_preread on;
}
}
◆ 2.4.3 SNI路由
根据域名转发到不同后端(不解密):
stream {
map $ssl_preread_server_name $backend_name {
www.example.com backend1;
api.example.com backend2;
default backend1;
}
upstream backend1 {
server 192.168.1.10:443;
}
upstream backend2 {
server 192.168.1.20:443;
}
server {
listen 443;
proxy_pass $backend_name;
ssl_preread on;
}
}
三、示例代码和配置
3.1 完整的7层负载均衡配置
# /etc/nginx/conf.d/webapp.conf
# 后端服务器池
upstream webapp_backend {
# 最少连接算法
least_conn;
# 后端服务器配置
server 192.168.1.10:8080 weight=5 max_fails=3 fail_timeout=30s;
server 192.168.1.11:8080 weight=5 max_fails=3 fail_timeout=30s;
server 192.168.1.12:8080 weight=3 max_fails=3 fail_timeout=30s;
server 192.168.1.13:8080 backup; # 备份节点
# keepalive连接池
keepalive 64;
keepalive_requests 1000;
keepalive_timeout 60s;
}
# HTTPS server
server {
listen 443 ssl http2;
server_name www.example.com;
# SSL配置
ssl_certificate /etc/nginx/ssl/example.com.crt;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
# 日志
access_log /var/log/nginx/webapp_access.log main;
error_log /var/log/nginx/webapp_error.log;
# 根路径
location / {
proxy_pass http://webapp_backend;
# HTTP/1.1 for keepalive
proxy_http_version 1.1;
proxy_set_header Connection "";
# 传递真实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;
proxy_set_header X-Forwarded-Proto $scheme;
# 超时配置
proxy_connect_timeout 10s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# 缓冲配置
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 32k;
proxy_busy_buffers_size 64k;
# 失败重试
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 3;
proxy_next_upstream_timeout 10s;
}
# 静态资源
location /static/ {
alias /var/www/static/;
expires 7d;
add_header Cache-Control "public, no-transform";
}
# 健康检查接口
location /health {
access_log off;
return 200 "OK\n";
add_header Content-Type text/plain;
}
}
# HTTP重定向HTTPS
server {
listen 80;
server_name www.example.com;
return 301 https://$host$request_uri;
}
3.2 完整的4层负载均衡配置
# /etc/nginx/stream.d/mysql.conf
upstream mysql_master {
server 192.168.1.20:3306;
}
upstream mysql_slave {
least_conn;
server 192.168.1.21:3306 weight=5;
server 192.168.1.22:3306 weight=3;
}
# 主库(写)
server {
listen 3307;
proxy_pass mysql_master;
proxy_connect_timeout 10s;
proxy_timeout 7200s; # 2小时,长事务场景
proxy_socket_keepalive on;
}
# 从库(读)
server {
listen 3308;
proxy_pass mysql_slave;
proxy_connect_timeout 10s;
proxy_timeout 3600s;
proxy_socket_keepalive on;
}
# Redis
upstream redis_cluster {
server 192.168.1.30:6379;
server 192.168.1.31:6379;
server 192.168.1.32:6379;
}
server {
listen 6379;
proxy_pass redis_cluster;
proxy_connect_timeout 5s;
proxy_timeout 600s;
proxy_socket_keepalive on;
}
3.3 WebSocket负载均衡
WebSocket 需要特殊处理 Upgrade 头:
upstream websocket_backend {
# 使用ip_hash保持会话
ip_hash;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
keepalive 32;
}
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
location /ws/ {
proxy_pass http://websocket_backend;
# WebSocket必需
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# WebSocket长连接,超时设长一点
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
3.4 灰度发布配置
按比例分流:
upstream backend_stable {
server 192.168.1.10:8080;
server 192.168.1.11:8080;
}
upstream backend_canary {
server 192.168.1.20:8080;
}
# 按权重分流
split_clients "${remote_addr}${uri}" $backend_version {
5% backend_canary; # 5%流量到金丝雀
* backend_stable; # 其余到稳定版
}
server {
location / {
proxy_pass http://$backend_version;
}
}
按 Cookie/Header 分流:
map $cookie_version $backend_version {
default backend_stable;
"canary" backend_canary;
}
server {
location / {
proxy_pass http://$backend_version;
}
}
四、最佳实践和注意事项
4.1 最佳实践
◆ 4.1.1 性能调优
1. worker进程数
# 等于CPU核数
worker_processes auto;
# 每个worker的最大连接数
events {
worker_connections 65535;
use epoll;
multi_accept on;
}
2. 系统参数优化
# /etc/sysctl.conf
# 增加本地端口范围(作为客户端连接后端)
net.ipv4.ip_local_port_range = 1024 65535
# TIME_WAIT优化
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30
# 连接队列
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
# 文件描述符
fs.file-max = 655350
3. Nginx优化参数
http {
# 开启sendfile
sendfile on;
tcp_nopush on;
tcp_nodelay on;
# 客户端keepalive
keepalive_timeout 65;
keepalive_requests 1000;
# 缓冲区
client_body_buffer_size 10k;
client_header_buffer_size 1k;
large_client_header_buffers 4 8k;
# Gzip
gzip on;
gzip_min_length 1k;
gzip_comp_level 4;
gzip_types text/plain text/css application/json application/javascript;
}
◆ 4.1.2 高可用配置
单点 Nginx 是风险,建议用 Keepalived 做主备:
# 安装keepalived
yum install -y keepalived
# /etc/keepalived/keepalived.conf (主)
vrrp_script check_nginx {
script "/etc/keepalived/check_nginx.sh"
interval 2
weight -20
}
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
192.168.1.100
}
track_script {
check_nginx
}
}
# /etc/keepalived/check_nginx.sh
#!/bin/bash
if ! pidof nginx > /dev/null; then
systemctl restart nginx
sleep 2
if ! pidof nginx > /dev/null; then
exit 1
fi
fi
exit 0
◆ 4.1.3 监控配置
# 开启状态页
location /nginx_status {
stub_status on;
allow 127.0.0.1;
allow 10.0.0.0/8;
deny all;
}
用 Prometheus 监控:
# prometheus配置
scrape_configs:
- job_name: 'nginx'
static_configs:
- targets: ['nginx:9113'] # nginx-prometheus-exporter
4.2 注意事项
◆ 4.2.1 配置注意事项
- reload前要测试:
nginx -t 必须通过再 reload
- upstream名字不能重复:多个配置文件的 upstream 名字冲突会出问题
- 注意proxy_pass的末尾斜杠:
proxy_pass http://backend; - 保留 location 路径
proxy_pass http://backend/; - 替换 location 路径
# 例子:请求 /api/user/list
location /api/ {
proxy_pass http://backend;
# 后端收到:/api/user/list
}
location /api/ {
proxy_pass http://backend/;
# 后端收到:/user/list (/api/被替换成/)
}
◆ 4.2.2 常见错误
| 错误现象 |
原因分析 |
解决方案 |
| keepalive不生效 |
没设HTTP/1.1和Connection头 |
添加正确配置 |
| 4层连接莫名断开 |
proxy_timeout太短 |
根据业务调大 |
| 502大量出现 |
max_fails设太小或后端太慢 |
调整参数或优化后端 |
| WebSocket连接断开 |
超时配置太短 |
proxy_read_timeout设长 |
| 后端无法获取真实IP |
没传X-Forwarded-For |
添加对应Header |
◆ 4.2.3 兼容性问题
- HTTP/2和upstream:HTTP/2 只在客户端到 Nginx 段生效,Nginx 到 upstream 还是 HTTP/1.1
- gRPC负载均衡:需要开启
grpc_pass,不是普通的 proxy_pass
- Unix Socket和TCP:upstream 可以混用,但注意路径和权限
五、故障排查和监控
5.1 日志分析
配置详细日志格式:
log_format upstream_log '$remote_addr - [$time_local] '
'"$request" $status $body_bytes_sent '
'upstream: $upstream_addr '
'upstream_status: $upstream_status '
'upstream_response_time: $upstream_response_time '
'request_time: $request_time';
access_log /var/log/nginx/upstream.log upstream_log;
分析命令:
# 统计各后端的请求数
awk '{print $8}' /var/log/nginx/upstream.log | sort | uniq -c | sort -rn
# 找出响应慢的请求
awk -F 'upstream_response_time: ' '$2 > 1 {print $0}' /var/log/nginx/upstream.log
# 统计各状态码
awk '{print $4}' /var/log/nginx/upstream.log | sort | uniq -c | sort -rn
# 找出502请求
grep "502" /var/log/nginx/error.log | tail -20
5.2 性能监控
# 实时连接数
watch -n 1 'ss -s'
# Nginx状态
curl http://127.0.0.1/nginx_status
# Active connections: 291
# server accepts handled requests
# 16630948 16630948 31070465
# Reading: 6 Writing: 179 Waiting: 106
# 各upstream的连接数
ss -ant | grep ":8080" | awk '{print $6}' | sort | uniq -c
5.3 告警规则
# Prometheus告警规则
groups:
- name: nginx
rules:
- alert: NginxHighConnections
expr: nginx_connections_active > 10000
for: 5m
labels:
severity: warning
annotations:
summary: "Nginx连接数过高"
- alert: NginxUpstreamDown
expr: nginx_upstream_server_state != 1
for: 1m
labels:
severity: critical
annotations:
summary: "后端服务器不可用"
- alert: NginxHighLatency
expr: histogram_quantile(0.95, rate(nginx_http_request_duration_seconds_bucket[5m])) > 2
for: 5m
labels:
severity: warning
annotations:
summary: "请求延迟过高"
六、总结
6.1 技术要点回顾
- 7层负载均衡:适合 HTTP/HTTPS,可以根据 URL/Header 路由,支持 SSL 卸载
- 4层负载均衡:适合 TCP/UDP,不理解协议,性能更高
- 算法选择:无状态用轮询,会话保持用 ip_hash,长连接用 least_conn
- Keepalive很重要:不开连接复用性能差很多
- 超时配置要合理:特别是 4 层的 proxy_timeout,设太短会断连接
如果你正在做 Nginx 负载均衡的选型或调优,以上这些配置和经验应该能帮你少踩一些坑。云栈社区里也有不少关于 Nginx 和数据库中间件的实战分享,可以结合起来看。
6.2 进阶学习方向
- OpenResty:Nginx + Lua,可以写复杂的路由逻辑
- Envoy:云原生代理,支持 xDS 动态配置
- APISIX:基于 OpenResty 的 API 网关
- 服务网格:Istio 用 Envoy 做 Sidecar 代理
6.3 参考资料
- Nginx 官方文档
- Nginx 负载均衡详解
- OpenResty 最佳实践
附录
A. 命令速查表
# 测试配置
nginx -t
# 重载配置
nginx -s reload
# 查看Nginx进程
ps aux | grep nginx
# 查看连接数
ss -s
# 查看某端口连接
ss -ant | grep :80 | wc -l
# 实时日志
tail -f /var/log/nginx/access.log
# 分析慢请求
awk -F 'rt=' '$2 > 1' /var/log/nginx/access.log
B. 负载均衡算法对比
| 算法 |
适用场景 |
优点 |
缺点 |
| 轮询 |
无状态服务 |
简单均匀 |
不考虑负载 |
| 加权轮询 |
服务器配置不同 |
可调权重 |
手动配置 |
| ip_hash |
会话保持 |
稳定分配 |
分布可能不均 |
| least_conn |
长连接 |
动态均衡 |
略复杂 |
| 一致性hash |
缓存场景 |
节点变化影响小 |
需额外模块 |
C. 术语表
| 术语 |
英文 |
解释 |
| 负载均衡 |
Load Balancing |
将流量分发到多台服务器 |
| Upstream |
Upstream |
Nginx中定义的后端服务器组 |
| 反向代理 |
Reverse Proxy |
代理后端服务,对客户端透明 |
| 健康检查 |
Health Check |
定期检测后端服务状态 |
| Keepalive |
Keepalive |
连接保持,复用TCP连接 |
| SSL卸载 |
SSL Offloading |
在代理层处理SSL加解密 |