服务器负载均衡配置教程

结论: 服务器负载均衡(Load Balancing)的配置分三步:选层级(四层 LVS 或七层 Nginx/HAProxy)、配 upstream 后端池与调度算法、配健康检查与高可用。日 PV 百万以内的网站用 Nginx 七层反向代理即可;需要超高性能或透传客户端 IP 选 LVS DR;需要精细流量治理选 HAProxy;最后用 Keepalived 给调度器本身做主备,消除单点。

为什么需要负载均衡?

一台服务器总有上限:4 核 8G 的云主机跑 PHP-FPM 大概能撑 300~800 QPS,跑 Java 应用到 1500 QPS 左右就接近瓶颈。当业务量超过单机能力,或者不能接受"这台机器挂了整个站就打不开",就必须引入负载均衡。

负载均衡器(Load Balancer)的作用有三点:分流——把请求按规则摊到多台后端;容错——后端某台故障自动摘除;扩展——加机器就能线性提升容量,对前端无感知。

本文是系统性教程,覆盖从层级选型、三种主流实现的完整配置、调度算法、健康检查、会话保持、SSL 终结到调度器自身高可用的全部环节。如果你只是想快速配一个 Nginx upstream,可以看同站的快速上手篇;本文侧重"搭一套能上生产的完整体系"。

一、四层负载均衡与七层负载均衡怎么选?

结论:七层(L7)看得懂 HTTP,能按域名、URL、Cookie 分流,功能强但性能略低;四层(L4)只看 IP 和端口,性能极高但不能做内容路由。 绝大多数业务先用七层。

对比项四层负载均衡(L4)七层负载均衡(L7)
工作层次传输层,基于 IP + 端口 + 协议(TCP/UDP)应用层,解析 HTTP/HTTPS 报文内容
典型实现LVS(IPVS)、HAProxy tcp mode、云厂商 CLB 四层Nginx、HAProxy http mode、Envoy、云厂商 ALB 七层
性能上限极高,LVS DR 模式可打满万兆网卡,百万并发连接较高,单台 Nginx 通常 5 万~10 万并发连接、数万 QPS
路由依据源/目标 IP、端口域名(Host)、URL 路径、Header、Cookie、请求方法
会话保持只能按源 IP 哈希(sh / ip_hash 思路)可基于 Cookie、Header、URL 参数做精细粘滞
附加能力基本没有SSL 终结、压缩、缓存、限流、改写 Header、鉴权
后端要求LVS DR 模式要求后端配置 VIP 并抑制 ARP无特殊要求,后端是普通 HTTP 服务即可
适用场景超高性能入口、数据库/Redis/游戏长连接代理Web 网站、API 网关、微服务入口、按路径分流

选择建议:Web 业务一律从七层 Nginx 起步;当日均请求超过单机 Nginx 承载(约 3 万 QPS)时,在前面叠一层 LVS DR 做四层入口,形成"LVS + Nginx"两级架构,这是国内互联网公司非常经典的组合。

二、主流实现选型:Nginx / HAProxy / LVS

结论:Nginx 综合最省心,HAProxy 健康检查与统计最专业,LVS 性能最高但配置最复杂。

实现层级性能健康检查配置难度推荐场景
Nginx七层(1.9+ 支持四层 stream)高开源版仅被动检查,主动检查需 Plus 或第三方模块低网站、API、反向代理、SSL 终结
HAProxy七层 + 四层很高内置主动健康检查,支持多种 check 方式中需要精细流量治理、TCP 代理、专业统计页面
LVS(IPVS)四层极高(内核态转发)需配合 keepalived/ldirectord 或脚本高百万级并发入口、数据库代理、游戏长连接

三、Nginx 负载均衡完整配置

结论:核心是 upstream 定义后端池 + proxy_pass 转发,配合 weight、max_fails、fail_timeout、backup 四个参数控制权重与容错。

3.1 基础 upstream 与反向代理

假设有三台后端应用服务器 10.0.0.11、10.0.0.12、10.0.0.13,前端调度器为 10.0.0.10。

nginx
# /etc/nginx/conf.d/lb.conf  —— 定义后端池
upstream app_pool {
    # weight 权重:3 表示这台机器分到的请求是默认机器的 3 倍,按性能配比
    server 10.0.0.11:8080 weight=3 max_fails=3 fail_timeout=30s;
    server 10.0.0.12:8080 weight=2 max_fails=3 fail_timeout=30s;
    server 10.0.0.13:8080 weight=1 max_fails=3 fail_timeout=30s;
    # backup 备份节点:所有主节点都挂掉后才会接流量
    server 10.0.0.14:8080 backup;

    # 长连接复用,显著降低 TIME_WAIT 与握手开销
    keepalive 64;
}

server {
    listen 80;
    server_name www.example.com;

    location / {
        proxy_pass http://app_pool;

        # 传递真实客户端信息给后端
        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;

        # 超时控制:connect 建连 5s,send 发送 60s,read 读取 60s
        proxy_connect_timeout 5s;
        proxy_send_timeout    60s;
        proxy_read_timeout    60s;

        # 失败重试策略:后端返回 500/502/503/504 或超时时切换下一台
        proxy_next_upstream     error timeout http_500 http_502 http_503 http_504;
        proxy_next_upstream_tries 3;
        proxy_next_upstream_timeout 10s;

        # 复用 upstream 中的 keepalive 长连接,必须配合 HTTP/1.1
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }

    # 调度器自身的健康检查出口,便于外部探活
    location = /nginx-status {
        stub_status on;
        allow 127.0.0.1;
        allow 10.0.0.0/24;
        deny all;
    }
}
bash
# 校验配置并热加载,注意用 reload 而不是 restart,避免断连
sudo nginx -t
sudo nginx -s reload

# 观察后端分发是否均匀(连续请求 20 次看命中)
for i in $(seq 1 20); do curl -s http://10.0.0.10/ -o /dev/null -w "%{http_code}\n"; done

3.2 健康检查:开源版 Nginx 的准确做法

这里是最容易踩坑的地方:开源版 Nginx 没有主动健康检查指令。health_check 是 NGINX Plus 的专有指令,直接写进开源版配置会报错 unknown directive "health_check"。

开源版 Nginx 的健康检查是被动式的,依靠上面配置中的 max_fails 与 fail_timeout:

  • 含义是:在 fail_timeout(30s)窗口内,若与该节点的失败请求数累计达到 max_fails(3 次),则判定该节点不可用并摘除 30 秒;30 秒后重新尝试放行一个请求探测。
  • 缺点:必须先有真实请求打过去才知道节点挂了,极端情况下会有少量请求失败(但配合 proxy_next_upstream,对客户端是透明的)。

如果需要主动健康检查(定期主动探测后端 /health 接口),有三条路:

  1. 使用 NGINX Plus 的 health_check 指令(商业版);
  2. 使用淘宝开源的 Tengine,它内置 check 指令;
  3. 编译第三方模块 nginx_upstream_check_module。

下面是 Tengine / 第三方模块的主动健康检查写法,供参考:

nginx
# 主动健康检查(Tengine 或 nginx_upstream_check_module 环境)
upstream app_pool {
    server 10.0.0.11:8080 weight=3;
    server 10.0.0.12:8080 weight=2;
    server 10.0.0.13:8080 weight=1;

    # 每 3 秒探测一次 /health,连续 2 次成功视为 up,连续 3 次失败视为 down
    check interval=3000 rise=2 fall=3 timeout=2000 type=http;
    check_http_send "GET /health HTTP/1.0\r\nHost: app_pool\r\n\r\n";
    check_http_expect_alive http_2xx http_3xx;
}

server {
    listen 80;
    location /status {
        check_status;   # Tengine 的后端节点状态页面
        access_log off;
        allow 10.0.0.0/24;
        deny all;
    }
}

3.3 七种调度算法对照表

结论:默认用加权轮询,需要会话保持用 ip_hash 或一致性哈希,后端性能差异大用最少连接。

调度算法Nginx 指令原理优点缺点适用场景
轮询(Round Robin)默认按顺序逐个分发简单均匀不考虑后端性能差异后端配置完全一致
加权轮询(Weighted RR)weight=n按权重比例分发可按机器性能分配权重需人工维护后端配置不一致(最常见)
最少连接(Least Conn)least_conn优先发给当前连接数最少的节点能应对长连接、慢请求不看实际负载请求耗时差异大的业务
源地址哈希(ip_hash)ip_hash按客户端 IP 前 24 位哈希固定到某台天然实现会话保持节点变动会大规模重映射;NAT 出口用户会扎堆需要会话保持且无 Cookie 方案
URL 哈希(url_hash)需第三方模块按请求 URI 哈希提升后端缓存命中率热点 URL 会压单台后端有本地缓存/CDN 回源
fair(响应时间)需第三方模块 nginx-upstream-fair按后端响应快慢动态分配自适应能力强第三方模块,维护成本高后端响应时间波动大
一致性哈希hash $key consistent哈希环映射,节点变动只影响 1/N 流量节点上下线影响最小配置略复杂缓存类服务、大规模集群
nginx
# 一致性哈希示例:按客户端 IP 做 key,节点增减只影响少量请求
upstream cache_pool {
    hash $remote_addr consistent;
    server 10.0.0.21:11211;
    server 10.0.0.22:11211;
    server 10.0.0.23:11211;
}

3.4 会话保持的三种方案

结论:优先用"共享 Session",其次 Cookie 插入,最后才考虑 ip_hash。 依赖 ip_hash 会让扩容变得痛苦。

  • 方案一:共享 Session(推荐)——把 Session 存到 Redis,后端节点无状态,随意扩缩容。
ini
# PHP 场景:php.ini 中把 session 存到 Redis
session.save_handler = redis
session.save_path    = "tcp://10.0.0.30:6379?auth=RedisPass2026&timeout=2"
  • 方案二:Cookie 插入(sticky cookie)——调度器在响应里种一个 Cookie,后续请求按 Cookie 固定到某台。Nginx 开源版需 nginx-sticky-module,HAProxy 原生支持。
haproxy
# HAProxy 原生 cookie 粘滞(见下一节完整配置)
backend app_pool
    balance roundrobin
    cookie SERVERID insert indirect nocache maxidle 30m
    server web1 10.0.0.11:8080 check cookie s1
    server web2 10.0.0.12:8080 check cookie s2
  • 方案三:ip_hash——最简单但最不灵活,节点数量变化会导致大部分用户的会话失效,且在移动端(基站 IP 频繁变化)表现很差。

3.5 SSL 终结与后端走 HTTP

结论:把 HTTPS 终结在负载均衡器上,后端用 HTTP 通信,既省后端 CPU 又能统一证书管理。 内网可信时这是标准做法;如果内网不可信,需做 SSL 透传或后端也配 HTTPS。

nginx
# SSL 终结:客户端 HTTPS → 调度器解密 → 后端 HTTP
upstream app_pool {
    server 10.0.0.11:8080 weight=3 max_fails=3 fail_timeout=30s;
    server 10.0.0.12:8080 weight=2 max_fails=3 fail_timeout=30s;
}

server {
    listen 80;
    server_name www.example.com;
    return 301 https://$host$request_uri;   # HTTP 全量跳转 HTTPS
}

server {
    listen 443 ssl;
    http2 on;
    server_name www.example.com;

    ssl_certificate     /etc/nginx/ssl/example.com/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/example.com/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_session_cache   shared:SSL:50m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    # 后端拿到 X-Forwarded-Proto: https,应用层据此生成 https 链接
    location / {
        proxy_pass http://app_pool;
        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 https;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}

四、HAProxy 配置示例

结论:HAProxy 用 frontend 定义入口、backend 定义后端池,内置主动健康检查,比 Nginx 更适合做专业的四七层流量治理。

haproxy
# /etc/haproxy/haproxy.cfg
global
    log /dev/log local0 info
    maxconn 100000
    user haproxy
    group haproxy
    daemon
    stats socket /run/haproxy/admin.sock mode 660 level admin

defaults
    log     global
    mode    http
    option  httplog
    option  dontlognull
    option  redispatch            # 连接失败时重分发到其它节点
    retries 3
    timeout connect 5s
    timeout client  60s
    timeout server  60s
    timeout http-request 15s
    default-server inter 3s fall 3 rise 2   # 全局健康检查:3s 间隔,3 次失败摘除,2 次成功恢复

# ---------- 七层 HTTP 入口 ----------
frontend http_in
    bind *:80
    # 按域名分流
    acl is_api   hdr_beg(Host) -i api.
    acl is_static path_beg /static/ /assets/
    use_backend api_pool   if is_api
    use_backend api_pool   if is_static
    default_backend web_pool

frontend https_in
    bind *:443 ssl crt /etc/haproxy/certs/example.com.pem alpn h2,http/1.1
    default_backend web_pool

# ---------- Web 后端池 ----------
backend web_pool
    balance roundrobin
    # Cookie 粘滞:插入 SERVERID,用户固定到首次命中的节点
    cookie SERVERID insert indirect nocache maxidle 30m
    # 主动健康检查:GET /health 必须返回 2xx/3xx
    option httpchk GET /health HTTP/1.1\r\nHost:\ www.example.com
    http-check expect status 200
    server web1 10.0.0.11:8080 check weight 3 cookie s1
    server web2 10.0.0.12:8080 check weight 2 cookie s2
    server web3 10.0.0.13:8080 check weight 1 cookie s3 backup

# ---------- API 后端池(最少连接) ----------
backend api_pool
    balance leastconn
    option httpchk GET /healthz
    http-check expect status 200
    server api1 10.0.0.21:9000 check
    server api2 10.0.0.22:9000 check

# ---------- 四层 TCP 代理(如 MySQL 只读、Redis) ----------
frontend mysql_in
    bind *:3307
    mode tcp
    option tcplog
    default_backend mysql_ro

backend mysql_ro
    mode tcp
    balance leastconn
    server db1 10.0.0.31:3306 check port 3306 inter 5s
    server db2 10.0.0.32:3306 check port 3306 inter 5s

# ---------- 统计页面 ----------
listen stats
    bind *:8404
    mode http
    stats enable
    stats uri /stats
    stats refresh 10s
    stats admin if TRUE
    stats auth admin:StrongStatsPass2026
bash
# 校验 + 热重载(HAProxy 支持 -sf 平滑接管旧进程 socket,不中断连接)
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
sudo systemctl reload haproxy
# 查看统计页面
curl -u admin:StrongStatsPass2026 http://127.0.0.1:8404/stats | head -40

五、LVS DR 模式简述

结论:LVS DR(Direct Routing,直接路由)模式下,调度器只改写目标 MAC 地址把包转给后端,响应报文由后端直接回给客户端,不经过调度器,因此性能极高。 代价是后端必须配置 VIP 并抑制 ARP。

bash
# ===== 调度器(Director)10.0.0.10 =====
# 在 lo 接口上绑定 VIP(注意掩码 /32)
sudo ip addr add 10.0.0.100/32 dev lo
sudo ip addr add 10.0.0.100/32 dev eth0 label eth0:0

# 添加虚拟服务(-A)与真实服务器(-a),-g 表示 DR 模式,-w 为权重
sudo ipvsadm -A -t 10.0.0.100:80 -s wlc              # wlc = 加权最少连接
sudo ipvsadm -a -t 10.0.0.100:80 -r 10.0.0.11:80 -g -w 3
sudo ipvsadm -a -t 10.0.0.100:80 -r 10.0.0.12:80 -g -w 2
sudo ipvsadm -a -t 10.0.0.100:80 -r 10.0.0.13:80 -g -w 1

# 查看连接分发情况
sudo ipvsadm -Ln --stats

# ===== 后端真实服务器(RS)上必须做的 ARP 抑制 =====
cat <<'EOF' | sudo tee /etc/sysctl.d/90-lvs-dr.conf
net.ipv4.conf.all.arp_ignore = 1
net.ipv4.conf.lo.arp_ignore = 1
net.ipv4.conf.all.arp_announce = 2
net.ipv4.conf.lo.arp_announce = 2
EOF
sudo sysctl --system

# 后端同样要把 VIP 绑在 lo 上(掩码 /32),否则无法接收目标为 VIP 的包
sudo ip addr add 10.0.0.100/32 dev lo

DR 模式关键点:调度器与后端必须在同一个二层网络(同一 VLAN/交换机),不能跨三层;后端响应直接回客户端,所以客户端 IP 天然透传,无需 X-Forwarded-For。

六、Keepalived 给调度器做高可用

结论:调度器本身是单点,必须用 Keepalived 做主备,通过 VRRP 协议让 VIP 在主备之间漂移。 两台调度器共享 VIP 10.0.0.100,主节点故障时 VIP 秒级漂到备节点。

conf
# 主节点 /etc/keepalived/keepalived.conf(备节点改 state 为 BACKUP、priority 改 90)
global_defs {
    router_id LB_01
    script_user root
    enable_script_security
}

# 检测本机 Nginx 是否存活的脚本
vrrp_script chk_nginx {
    script "/usr/bin/killall -0 nginx"   # 只检测进程存在,不做重启
    interval 2
    timeout 2
    weight -30          # 检测失败时优先级降 30,触发主备切换
    fall 2
    rise 2
}

vrrp_instance VI_1 {
    state MASTER
    interface eth0
    virtual_router_id 51      # 同一组主备必须一致,取值 0~255
    priority 100              # 主 100,备 90,高者持有 VIP
    advert_int 1              # VRRP 通告间隔 1 秒

    authentication {
        auth_type PASS
        auth_pass VrrpPass2026
    }

    virtual_ipaddress {
        10.0.0.100/24 dev eth0 label eth0:0
    }

    track_script {
        chk_nginx
    }

    notify_master "/usr/bin/systemctl start nginx"
    notify_backup "/usr/bin/systemctl start nginx"
    notify_fault  "/usr/bin/systemctl stop nginx"
}
bash
# 两台调度器都启动 Keepalived
sudo systemctl enable --now keepalived

# 在主节点上确认 VIP 已生效
ip addr show eth0 | grep 10.0.0.100
# 手动模拟切换:主节点停 keepalived,观察 VIP 漂移到备节点
sudo systemctl stop keepalived
# 备节点上查看日志确认接管
sudo journalctl -u keepalived -f