Featured image of post 从 Nginx 到 LVS:流量调度在异地多活中的角色与实操

从 Nginx 到 LVS:流量调度在异地多活中的角色与实操

Java Web 微服务系列 · 第 2 篇 · 流量调度 阅读时长:约 38 分钟 本文写于 2026 年 6 月 前置阅读:《异地多活:Java Web 微服务的高可用终极形态》(系列第 1 篇)

引子:流量调度是异地多活的"前哨班"

系列第 1 篇《异地多活:Java Web 微服务的高可用终极形态》我们讲清楚了"业务单元化 + 存储双向同步 + 流量层分片"是异地多活的三大支柱,并画了完整的架构图。但那张图里最顶上的"用户请求"到"机房入口"之间,其实藏着一层很多人会忽略的细节——流量调度层

本文约定:本篇是系列第 2 篇,专攻"流量调度"这一层。读完你会知道:阿里、美团、字节这些大厂的异地多活架构,流量入口不是单一组件,而是"公网 LB → LVS → Nginx → 应用网关“四级串联的"前哨班”。

我们用一个真实场景开场:

2021 年某电商双 11 零点,入口流量是平时的 30 倍。第一道前哨——DNS 解析——把用户路由到最近的 CDN 边缘节点,扛掉了 80% 的静态请求。剩下 20% 的动态请求进入机房后,第二道前哨——LVS——在内核态做四层负载,把 50 万 QPS 均匀分给后方的 8 台 Nginx。第三道前哨——Nginx——做七层路由(按 Host 头、URL 路径分流到不同微服务)。三层前哨环环相扣,任意一层挂掉,下一层都能自动承接

这就是流量调度的全部奥秘——冗余 + 分流 + 自动故障切换。本篇按"单机 → 双机 → 集群 → 跨城"的演进顺序,把这条前哨班完整搭一遍。实操部分会给你完整可复制粘贴的命令清单,2 台虚拟机就能跑通整套 LVS + Keepalived + Nginx 链路。


一、流量调度的本质:4 层 / 7 层 / 高可用

异地多活架构里,“流量调度"听起来抽象,本质就是回答三个问题:

  1. 用户的请求先到谁?(入口选择)
  2. 到达入口后怎么分给后端?(负载均衡)
  3. 后端某台机器挂了怎么办?(高可用)

1.1 三层位置:流量调度在异地多活中的定位

先把流量调度放到异地多活的整体架构里看:

这张图里,流量调度对应"公网 LB → LVS → Nginx"这三层。本篇只讲这三层(DNS / CDN 属于另一条线,Spring Cloud Gateway 是微服务层的网关,留给系列第 3 篇)。

1.2 4 层 vs 7 层:性能与灵活性的权衡

维度4 层(L4)7 层(L7)
工作层级传输层(TCP/UDP)应用层(HTTP/HTTPS)
看的信息源 IP、目标 IP、端口全部 HTTP 头、URL、Cookie、Body
典型代表LVS、Haproxy(四层模式)、AWS NLBNginx、Tengine、Envoy、Traefik
性能几十万 QPS(内核态)1-5 万 QPS(用户态)
加密终结不终结 TLS可终结 TLS(看 https 证书)
路由能力仅 IP/端口级Host / URL / Header / Cookie 级
典型用途入口抗量、跨机房流量分发域名路由、灰度、A/B 测试

💡 原理:4 层为什么快?

4 层负载工作在 Linux 内核协议栈的 IPVS(IP Virtual Server)层,数据包在内核态就被转发了,不再上送用户态。7 层负载必须把整个 TCP 流接完、解析 HTTP 头,才能决定转发给谁,至少多两次上下文切换 + 一次内存拷贝

这就是 LVS 能扛 50 万 QPS 而 Nginx 只能扛 1-5 万 QPS 的根本原因。不是 LVS 算法多牛,是它根本不离开内核

1.3 流量调度的核心目标

不论用 Nginx 还是 LVS,流量调度的目标都只有 3 个:

  • 均匀:把请求均匀分给后端机器,避免热点
  • 健康:自动剔除故障机器,恢复后自动加回
  • 切换:主节点故障时,备用节点秒级接管(Keepalived 负责)

这 3 个目标对应负载均衡算法健康检查故障转移三个具体机制。后文逐个讲。

1.4 4 层 vs 7 层:一场持续 20 年的争论

从 2002 年 Nginx 诞生起,“4 层够用还是必须 7 层"就是架构圈反复争论的话题。本质上是性能 vs 灵活性的取舍:

4 层派的论据:

  • 内核态转发,单包转发延迟 < 10 微秒
  • 看不到应用层数据 = 更安全(攻击面小)
  • 协议无关,MySQL / Redis / MQTT / 游戏 TCP 都能代理

7 层派的论据:

  • 能看 HTTP 头,按 URL 路由 / 按 Cookie 灰度 是刚需
  • TLS 终结在反代层,后端明文 HTTP 性能高 3 倍
  • 业务错误码(如 5xx)能被反代识别,故障定位更准

实战结论(业界 20 年沉淀的共识):

  • 入口第一跳用 4 层(LVS)抗量
  • 入口第二跳用 7 层(Nginx)做业务路由
  • 不要"用 7 层做 4 层的事”——比如用 Nginx 做 MySQL 代理,性能差 10 倍
  • 不要"用 4 层做 7 层的事”——比如想用 LVS 按 URL 路由,做不到

💡 原理:4 层 + 7 层是"串联"不是"二选一"

真正的生产架构是 4 层在前、7 层在后,串联成两层。4 层负责"抗量 + 入口安全",7 层负责"业务路由 + 灰度"。每一层做自己擅长的事,不要试图用一层解决所有问题。

1.5 为什么"切换"比"均衡"更重要?

很多初学者以为负载均衡的核心是"算法选哪个"(轮询、最少连接、一致性哈希……)。。真正决定系统可用性的是故障切换速度

  • 算法选错:后端 3 台机器变成 2 台在干活,QPS 降 33%,用户可能没感知
  • 切换太慢:后端 1 台机器挂了,10 秒内没人接管,所有打到这台机器的请求都失败,用户感知强烈

所以本文会用大量篇幅讲 Keepalived 和 LVS 的故障切换——这才是流量调度的"命门"。


二、单机 Nginx:1 万 QPS 的边界

2.1 Nginx 凭什么成为"事实标准"

Nginx 2002 年由俄罗斯工程师 Igor Sysoev 写出来,最初是为了解决 C10K 问题——单机如何同时处理 1 万个并发连接。采用事件驱动的异步非阻塞模型,用 1 个 worker 进程就能扛几千个连接。

核心设计:

  • Master-Worker 模型:1 个 master 进程负责管理,N 个 worker 进程负责处理请求(worker_processes auto 默认等于 CPU 核数)
  • 事件循环:每个 worker 用 epoll(Linux)/ kqueue(BSD)系统调用,单线程处理所有连接
  • 内存占用低:每个连接约 2-4 KB,而 Apache 一个进程一个连接要 1-2 MB

2.2 反向代理的最简配置

Nginx 做反向代理的最核心 3 段配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# /etc/nginx/nginx.conf

http {
    # 1. 定义后端集群
    upstream backend {
        server 192.168.1.10:8080 weight=1 max_fails=3 fail_timeout=30s;
        server 192.168.1.11:8080 weight=1 max_fails=3 fail_timeout=30s;
        server 192.168.1.12:8080 weight=1 max_fails=3 fail_timeout=30s;
    }

    # 2. 终结 TLS(可选)
    server {
        listen 443 ssl;
        ssl_certificate     /etc/nginx/cert/server.crt;
        ssl_certificate_key /etc/nginx/cert/server.key;

        # 3. 路由到后端
        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;
        }
    }
}

3 段配置的职责

  • upstream:声明后端服务器列表 + 负载均衡参数
  • server { listen 443 ssl }:终结 HTTPS
  • proxy_pass http://backend:把请求转给 upstream,这是反向代理的"反"字所在——客户端只看到 Nginx,看不到后端

2.3 性能调优三件套:worker / connection / keepalive

Nginx 默认配置偏保守,生产环境必须调 3 个参数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 主配置
worker_processes auto;                    # worker 数 = CPU 核数
worker_rlimit_nofile 65535;               # 每个 worker 能打开的最大文件数

events {
    worker_connections 8192;              # 每个 worker 能同时处理的连接数
    multi_accept on;                       # 一次 accept 多个连接
    use epoll;                            # Linux 强制用 epoll
}

http {
    sendfile on;                          # 零拷贝发送文件
    tcp_nopush on;                        # 合并多个 TCP 包
    tcp_nodelay on;                       # 禁用 Nagle 算法

    # 关键:keepalive 调优(与后端连接)
    upstream backend {
        server 192.168.1.10:8080;
        keepalive 32;                     # 与每台后端保持 32 个长连接
        keepalive_timeout 60s;            # 长连接超时 60 秒
        keepalive_requests 1000;          # 单个长连接最多处理 1000 个请求后关闭
    }
}

3 个核心参数的内在逻辑

参数含义调优依据
worker_processesworker 进程数等于 CPU 核数;过多导致上下文切换开销
worker_connections每 worker 最大连接worker_rlimit_nofile 限制;理论最大并发 = workers × connections
keepalive与后端保持的长连接数关键! 默认 0(每次新建 TCP 连接)会导致 TIME_WAIT 飙升

🎯 避坑点:keepalive 默认 0 的隐形坑

Nginx 默认与后端不保持长连接upstream 里没有 keepalive 指令时)。每个 HTTP 请求都要 3 次握手 + 4 次挥手,并发一大就出现:

  • TIME_WAIT 状态连接数飙升(端口耗尽)
  • 后端 CPU 飙升(accept 新连接 + 关闭旧连接的开销)

生产配置必加 keepalive 32(与每台后端保持 32 个长连接池)。这是 Nginx 性能调优的"第一性原则"。

2.4 单机瓶颈:1 万 QPS 的天花板

调优到极致后,单机 Nginx 的上限大约:

  • 静态文件:5-10 万 QPS(取决于磁盘 IO / CDN 协同)
  • 动态反代:1-3 万 QPS(受后端应用限制)
  • TLS 终结:5-8 千 QPS(CPU 密集,受证书算法影响)

单机撑不住怎么办? 两条路:

路径适用场景代价
垂直扩展(换更牛机器)业务量增长 50% 以内边际成本急剧上升,10 万 QPS 后失效
水平扩展(多加几台 Nginx)长期增长需要在前置加一层负载均衡——这就引出了 Keepalived 和 LVS

📌 实践:什么时候单机 Nginx 就够了?

经验值:

  • 日活 < 10 万:单机 Nginx 足够
  • 日活 10-100 万:Nginx + Keepalived 双机
  • 日活 > 100 万 或 强 SLA 4 个 9:LVS + Keepalived + Nginx 三层塔

强可用性要求的系统(金融、交易、支付),即使日活不高也要 LVS 三层塔——因为单机 Nginx 挂了就全挂,没有容错

2.5 epoll 详解:Nginx 高并发的"内功心法"

Nginx 能扛高并发,核心在于它使用了 epoll(Linux 2.6+ 内核提供的 I/O 多路复用接口)。要理解 epoll 是什么,先看传统模型的瓶颈:

传统 BIO(Blocking I/O)模型

1
2
3
4
5
// 伪代码:每连接一线程
while (true) {
    int fd = accept(server_fd, ...);  // 阻塞等待新连接
    pthread_create(worker, handle_client, fd);  // 每连接开 1 个线程
}

1 万并发 = 1 万个线程。每个线程默认栈 8 MB,光内存就要 80 GB。上下文切换开销也爆炸。

NIO(Non-blocking I/O)+ epoll 模型

1
2
3
4
5
6
7
8
9
// 伪代码:单线程事件循环
struct epoll_event events[MAX_EVENTS];
int epfd = epoll_create(1);
while (true) {
    int n = epoll_wait(epfd, events, MAX_EVENTS, -1);  // 阻塞等待事件
    for (int i = 0; i < n; i++) {
        handle_event(events[i]);  // 处理就绪的连接
    }
}

epoll 的 3 个关键优势

优势含义
事件驱动只处理"就绪"的连接,未就绪的连接不消耗 CPU
O(1) 就绪查询不管 1 万还是 100 万连接,就绪查询都是 O(1)
内核-用户态共享内存通过 mmap 避免每次事件都从内核复制到用户态

Nginx 的 worker 进程 = 1 个 epoll 循环。1 个 worker 处理 1 万连接只需几十 MB 内存。这就是 Nginx “小马拉大车"的秘密。

🎯 避坑点:worker 数不是越多越好

常见错误:worker_processes 32(机器才 8 核)。32 个 worker 抢 8 个 CPU,频繁上下文切换,性能反而下降

正确配置:worker_processes auto(让 Nginx 自动检测 CPU 核数)。worker 数 = CPU 核数是经验最优值。


三、Nginx + Keepalived:同城双活的"经典双子星”

3.1 为什么需要 Keepalived

单机 Nginx 的"高可用"问题是:Nginx 机器本身挂了怎么办?

用户访问的 IP 是 Nginx 机器的 IP,Nginx 挂了,IP 跟着没了。必须有一个"备用 Nginx"能在主 Nginx 挂掉时接管 IP——这就是 Keepalived 干的事。

Virtual IP(VIP) 是关键:用户访问的 192.168.1.100 不是任何机器的物理 IP,而是一个虚拟 IP,由 Keepalived 在主备机器之间漂移。

3.2 VRRP 协议:VIP 漂移的原理

Keepalived 的核心是 VRRP 协议(Virtual Router Redundancy Protocol,虚拟路由冗余协议):

  1. 多台机器组成一个"VRRP 路由器组",共享一个 VIP
  2. 选出一台 Master,其他是 Backup
  3. Master 周期性(默认 1 秒)发送 VRRP 通告(advertisement)
  4. Backup 收不到通告(连续 3 次 = 3 秒)就认为 Master 挂了,自己升级为 Master,接管 VIP
  5. Master 恢复后默认会"抢占"回 VIP(可通过 nopreempt 关闭

💡 原理:为什么是 3 秒而不是立刻切换?

默认 3 秒切换是权衡:

  • 太快(< 1 秒):网络抖动会被误判为故障,导致"双 Master"脑裂
  • 太慢(> 10 秒):业务已经超时,用户感知到中断

3 秒是保守且工程化的折中值。生产环境可通过 vrrp_script 自定义健康检查,把切换时间压到 1-2 秒。

3.3 Keepalived 配置实战(主备模式)

3.3.1 准备 2 台机器

角色IP说明
Master192.168.1.10主 Nginx,持有 VIP
Backup192.168.1.11备 Nginx,平时空闲
VIP192.168.1.100对外服务的虚拟 IP

两台机器都装好 Nginx(监听 VIP)+ Keepalived。

3.3.2 Master 配置 /etc/keepalived/keepalived.conf

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
! Configuration File for keepalived

global_defs {
    router_id NGINX_MASTER      # 唯一标识,集群内不能重复
    script_user root
    enable_script_security
}

# 健康检查:Nginx 进程是否存活
vrrp_script check_nginx {
    script "/usr/local/bin/check_nginx.sh"
    interval 2                  # 每 2 秒检查一次
    weight -20                  # 失败时优先级降 20
    fall 3                      # 连续失败 3 次才认为故障
    rise 2                      # 连续成功 2 次才算恢复
    timeout 2                   # 单次检查超时 2 秒
}

# VRRP 实例定义
vrrp_instance VI_1 {
    state MASTER                # 初始状态 MASTER
    interface eth0              # 绑定的网卡
    virtual_router_id 51        # 虚拟路由 ID(主备必须一致)
    priority 100                # 优先级(MASTER > BACKUP)
    advert_int 1                # VRRP 通告间隔 1 秒
    authentication {
        auth_type PASS
        auth_pass 1111         # 简单密码,生产建议复杂
    }
    virtual_ipaddress {
        192.168.1.100/24 dev eth0 label eth0:1   # VIP 配置
    }
    track_script {
        check_nginx             # 引用上面的健康检查
    }
    nopreempt                   # 故障恢复后不抢占(避免抖动)
    notify_master "/usr/local/bin/notify.sh master"
    notify_backup "/usr/local/bin/notify.sh backup"
    notify_fault "/usr/local/bin/notify.sh fault"
}

3.3.3 Backup 配置(只改 3 处)

1
2
3
4
5
vrrp_instance VI_1 {
    state BACKUP                # ← 改成 BACKUP
    priority 90                 # ← 优先级低于 MASTER
    # 其他完全一致
}

3.3.4 健康检查脚本 /usr/local/bin/check_nginx.sh

1
2
3
4
5
6
7
#!/bin/bash
# 检查 Nginx 是否存活
count=$(ps -ef | grep "nginx: master process" | grep -v grep | wc -l)
if [ $count -eq 0 ]; then
    exit 1                      # 失败返回非 0
fi
exit 0
1
chmod +x /usr/local/bin/check_nginx.sh

3.3.5 启动与验证

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 两台机器分别启动
systemctl start keepalived
systemctl enable keepalived

# Master 上验证 VIP 是否绑定
ip addr show eth0
# 应该能看到 eth0:1 192.168.1.100

# 模拟 Master 故障
systemctl stop keepalived
# 在 Backup 上看 VIP 是否漂移过来
ip addr show eth0
# eth0:1 192.168.1.100 漂过来了 ✓

3.4 Nginx 端的 5 个真实 keepalive 参数

Nginx 跟后端保持长连接这 5 个参数必须配对出现,少一个就掉坑里:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
upstream backend {
    server 192.168.1.20:8080;
    keepalive 32;                          # ① 启用长连接池,每 worker 与后端保持 32 个连接
}

server {
    listen 80;
    location / {
        proxy_pass http://backend;
        proxy_http_version 1.1;            # ② HTTP/1.1(keepalive 必须的协议)
        proxy_set_header Connection "";    # ③ 清空 Connection 头(关键!避免后端 close)
        # ④ ⑤ 这两个参数在 upstream 块
    }
}
参数作用不配的后果
keepalive 32启用连接池每次新建 TCP 连接,性能差 5 倍
proxy_http_version 1.1HTTP/1.1 协议1.0 不支持 keepalive
proxy_set_header Connection ""清空 Connection 头后端收到 Connection: close 主动关连接
keepalive_timeout 60s连接池超时连接长时间不释放
keepalive_requests 1000单连接最多请求数防止单个长连接无限累积状态

🎯 避坑点:Connection: close 是隐形杀手

proxy_set_header Connection "" 这一行看起来"无关紧要",实际上是最容易踩的坑。如果不写,Nginx 默认会把客户端的 Connection: close 透传给后端,后端收到后主动关闭 TCP 连接——keepalive 形同虚设。

现象:监控看到 Nginx 到后端的连接数正常,但后端 netstat -an 看到大量 TIME_WAIT 状态。100% 是这个参数没配

3.5 脑裂:Keepalived 最大的坑

脑裂(split-brain):Master 和 Backup 同时认为自己是 Master,同时持有 VIP。结果:

  • 用户请求被随机分到两台机器
  • 两台机器状态不一致,数据被双向覆盖
  • 监控告警狂响,运维一脸懵

脑裂的 3 大成因

  1. 网络分区:Master 和 Backup 之间的心跳线中断,但 Master 实际还活着
  2. 防火墙拦截 VRRP 通告:VRRP 用多播地址 224.0.0.18,某些安全策略会拦
  3. 优先级配置错误:两台机器优先级都设成 100,谁都觉得自己是 Master

脑裂的 3 道防线

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 防线 1:使用单播 VRRP 通告(避开多播问题)
vrrp_instance VI_1 {
    unicast_src_ip 192.168.1.10           # 本机 IP
    unicast_peer {
        192.168.1.11                      # 对端 IP
    }
}

# 防线 2:双心跳线(主备之间连 2 根网线 + 2 个交换机)
# 防线 3:业务侧防脑裂检测(脚本检查双 VIP 冲突时报警)

🛑 误区警示:脑裂 = 数据灾难

脑裂一旦发生,比单点故障更可怕。单点故障至少业务中断是确定的,脑裂是"两台机器各自服务一部分用户,状态还不一致"——数据被双向覆盖,等发现时已经无法回滚

防御永远比治疗重要:单播 VRRP + 独立心跳线 + 业务侧冲突检测,三道防线缺一不可


四、LVS:跨城市流量调度的内核王牌

4.1 为什么需要 LVS:Nginx 的天花板

Nginx + Keepalived 能扛 1-5 万 QPS(同城双活),但遇到大促、双 11 之类 50 万 QPS 场景就远远不够。Nginx 是用户态进程,受限于:

  • 单进程事件循环(虽然多 worker 提升有限)
  • HTTP 协议解析开销
  • 内存带宽

LVS 走的是另一条路:直接在内核协议栈里转发数据包,根本不进入用户态。理论上限是几十万到百万 QPS,是 Nginx 的 10-50 倍。

4.2 LVS 的 4 种工作模式

LVS 有 4 种转发模式(NAT / DR / TUN / FULLNAT),每种对应不同的网络拓扑:

4.2.1 NAT 模式(Network Address Translation)

特点

  • 进出都经过 LVS(进出流量都改 IP)
  • Real Server 可以跨网段(甚至跨 VLAN)
  • LVS 是性能瓶颈(双向流量都过它)
  • 适合小规模、Real Server 少的场景

4.2.2 DR 模式(Direct Routing,直接路由)⭐ 最常用

特点

  • 请求进 LVS,响应直接出 Real Server(响应不经过 LVS)
  • LVS 只负责"改 MAC 地址"(L2 转发)
  • 性能最高(LVS 只承担一半流量)
  • Real Server 必须和 LVS 在同一 VLAN(L2 互通)
  • Real Server 需要配置 VIP 在 lo 网卡上(让它能处理目的 IP 是 VIP 的包)

💡 原理:DR 模式为什么快?

DR 模式 LVS 做的只是改 MAC 地址——把数据包的目的 MAC 从 LVS 自己的改成 Real Server 的。包头 IP 都不动

整个过程在内核的 IPVS 模块完成,单次转发 < 10 微秒。而 NAT 模式要改 IP + 改端口 + 算校验和,慢 10 倍

4.2.3 TUN 模式(IP Tunneling,IP 隧道)

把数据包再封装一层 IP 头,通过隧道发给 Real Server。Real Server 可以跨地域(公网可达即可),但需要在 Real Server 上做解封装。

4.2.4 FULLNAT 模式(阿里独创)

双向都做 SNAT + DNAT,Real Server 看不到客户端真实 IP(双向都改 IP)。解决了 DR 模式必须同 VLAN 的限制,是阿里云 SLB 的核心实现。

4.2.5 4 种模式对比

模式性能Real Server 跨网段Real Server 看到真实 IP配置复杂度典型场景
NAT中(双向过 LVS)小规模、内网环境
DR⭐⭐⭐ 最高❌(必须同 VLAN)同城机房(最常用)
TUN✅(隧道可达)跨地域、异构网络
FULLNAT中高云厂商、跨 VPC

4.3 IPVS:LVS 的内核态实现

LVS 本身是一个框架,核心实现叫 IPVS(IP Virtual Server),是 Linux 内核 2.6 后内置的模块:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 查看本机是否支持 IPVS
modprobe ip_vs
lsmod | grep ip_vs

# IPVS 的工作流程(内核态)
# 1. 数据包从网卡进入
# 2. netfilter 钩子(PREROUTING)拦截
# 3. IPVS 模块判断目的 IP 是否是 VIP
# 4. 若是 VIP → 查 LVS 规则 → 选一个 Real Server → 改包头(MAC/IP)→ 发出
# 5. Real Server 处理后直接回客户端

IPVS vs iptables:很多人误以为 LVS 是用户态工具,其实所有转发逻辑都在内核ipvsadm 只是配置命令(ipvsadm -A -t VIP:80 -s rr 添加规则),规则写入内核后,包转发完全在内核态完成

4.4 LVS 调度算法

LVS 支持 10+ 种调度算法,常用的有 5 种:

算法含义适用场景
rr(Round Robin)轮询后端性能一致(最常用)
wrr(Weighted RR)加权轮询后端性能不均
lc(Least Connection)最少连接长连接服务
wlc(Weighted LC)加权最少连接⭐ 推荐默认
sh(Source Hashing)源 IP 哈希需要会话保持
1
2
3
4
5
# 配置示例:8 台 Real Server 轮询
ipvsadm -A -t 192.168.1.100:80 -s rr
ipvsadm -a -t 192.168.1.100:80 -r 192.168.1.20:80 -g
ipvsadm -a -t 192.168.1.100:80 -r 192.168.1.21:80 -g
# ... 一共 8 台

-g 表示 DR 模式(gatewaying),-m 是 NAT,-i 是 TUN。

4.5 LVS 真实性能数据

生产环境的 LVS 集群典型数据:

业务LVS 模式Real Server 数峰值 QPS备注
阿里云 SLBFULLNAT数百千万级单集群扛住双 11
美团 MGWDR数十百万级入口流量调度
某电商大促DR850 万单机房
某金融核心NAT45 万强一致场景

📌 实践:LVS 的"理论极限"

单 LVS 节点性能上限取决于包转发能力(pps):

  • 普通服务器(千兆网卡):148 万 pps ≈ 50 万 QPS(小包)
  • DPDK 优化:千万 pps
  • 内核优化irqbalanceRPS/RFS):300 万 pps

实际生产中 LVS 不会是瓶颈——网络才是。千兆网卡满载 = 148 万 pps ≈ 50 万 HTTP QPS(按平均 200 字节包算)。

4.6 LVS 的历史与章文嵩的故事

讲 LVS 不能不提它的作者——章文嵩。1998 年他还是国防科技大学的一名博士生,因为实验室的 Web 服务器扛不住大量并发访问,自己动手写了 IPVS 模块并贡献给 Linux 内核。这是中国人主导的、为数不多的内核级基础设施项目

LVS 的几个关键时间节点

  • 1998:章文嵩在国防科大开发 LVS 1.0 版本
  • 2002:LVS 2.0 发布,加入 NAT / DR / TUN 三种模式
  • 2004:IPVS 正式并入 Linux 2.6 内核主线
  • 2010s:阿里基于 LVS 开发 FULLNAT 模式,解决云环境跨 VPC 问题
  • 2020s:LVS 仍是云厂商负载均衡的事实标准内核

💡 原理:为什么 LVS 20 年不过时?

LVS 之所以能在 Service Mesh、Envoy、eBPF 等新技术层出不穷的今天仍占据入口调度,核心原因:它在内核。任何用户态的方案(Nginx / HAProxy / Envoy)都要走系统调用 → 内核协议栈的路径,而 LVS 本身就在内核协议栈里,少一次上下文切换就少 5-10 微秒

这是物理规律决定的,不是软件能优化的。eBPF 虽然也在内核,但目前 L4 场景仍未全面取代 IPVS。

4.7 LVS vs HAProxy:4 层反代的"双雄之争"

HAProxy 也是 4 层反代的常见选择(同公司还有 Nginx Ingress 也在用),与 LVS 形成竞争:

维度LVSHAProxy
实现位置Linux 内核(IPVS)用户态进程
性能百万级 QPS10-30 万 QPS
配置复杂度中(ipvsadm 命令)低(haproxy.cfg 文件)
健康检查TCP_CHECK / HTTP_GET更丰富(支持脚本)
日志内核态,无业务日志详细业务日志
典型场景入口抗量、超大规模中等规模、需业务可观测

实战选择

  • 超大规模(> 50 万 QPS):LVS
  • 中等规模 + 需详细日志:HAProxy
  • 云环境 / 容器环境:HAProxy(K8s 默认 kube-proxy 用的就是 iptables/IPVS,HAProxy 在 Ingress 层常见)

五、LVS + Keepalived + Nginx:异地多活流量层完整链路

5.1 经典三层塔架构

把 LVS、Keepalived、Nginx 串起来,就是异地多活流量层的"前哨班"完整链路:

为什么需要 3 层而不是直接 LVS → App?

  • LVS 看不到 HTTP 头:无法按 Host / URL 路由(4 层限制)
  • LVS 无法终结 TLS:HTTPS 终结在 Nginx 更省 CPU
  • LVS 无法做应用层健康检查:Nginx 的 upstream_check 模块能做更精细的健康探测

💡 原理:三层塔的职责边界

  • LVS 层:抗量 + 故障切换(不在乎协议内容)
  • Nginx 层:七层路由 + TLS 终结 + 应用层健康检查
  • 应用层:业务逻辑 + 微服务路由

每一层只做自己擅长的事,这是分布式系统设计的"第一性原则"。

5.2 双 Keepalived 实例设计

实战中,LVS 层和 Nginx 层都要 Keepalived 保护

2 个 Keepalived 实例的分工

  • Keepalived 1(外层):保 LVS,VIP 是公网入口
  • Keepalived 2(内层):保 Nginx,VIP 是内网入口

双 VIP 的好处

  • LVS 切换不影响 Nginx:LVS 挂了,Nginx 还在,对应用零感知
  • Nginx 切换不影响 LVS:Nginx 挂了,LVS 还在,VIP 还在
  • 故障定位更细:告警能精确到"LVS 层故障"或"Nginx 层故障"

5.3 阿里 / 美团 / 字节的真实架构

5.3.1 阿里云 SLB

阿里云 SLB(Server Load Balancer)的内核就是 LVS + Tengine 的组合:

  • LVS 集群(FULLNAT 模式):公网入口,扛 100 万级 QPS
  • Tengine 集群:七层路由 + 业务分流
  • 多 AZ 部署:同城双活 + 异地灾备

关键技术:

  • FULLNAT 模式(阿里自研):Real Server 跨 VPC 部署
  • ECS 健康检查:30 秒无响应自动剔除
  • 会话保持:基于 Cookie 4 层哈希

5.3.2 美团 MGW

美团自研的 MGW(Meituan Gateway)架构:

  • HAProxy 做 L4:替代 LVS,部分场景性能更好
  • Tengine 做 L7:替代纯 Nginx,加了很多自研模块
  • 多机房单元化:MGW 内部带"机房标签"

5.3.3 字节跳动 BFE

字节的 BFE(Baidu Front End,注:字节和百度都叫 BFE):

  • 七层网关为主:直接用 Go 自研 BFE 做入口
  • Service Mesh 协同:跨机房调用通过 Mesh 自动处理
  • 多语言 SDK:Go / Java / Python 都接入

📌 实践:自研还是用开源?

  • < 50 万 QPS:直接用 LVS + Keepalived + Nginx,业界成熟方案
  • 50 万 - 500 万 QPS:LVS + Tengine + 自研健康检查平台
  • > 500 万 QPS:必须自研(参考阿里 SLB、美团 MGW、字节 BFE)

不要过早自研。开源方案 80% 场景够用,自研成本是 5-10 倍。

5.4 异地多活的流量调度策略

5.4.1 DNS 层:用户就近接入

  • 智能 DNS:根据用户 IP 归属地返回最近的 VIP
  • 健康检查融合:DNS 探测机房健康度,故障机房自动从 DNS 摘除
  • DNS TTL 权衡:TTL 太短切换快但 DNS 压力大,太长切换慢

5.4.2 应用层:单元化路由

承接系列第 1 篇的单元化设计——LVS 不做单元化判断,单元化路由在 Nginx 或 Spring Cloud Gateway 层做:

1
2
3
4
5
6
# Nginx 根据用户 ID 哈希分流
upstream backend {
    hash $cookie_userid consistent;   # 一致性哈希
    server 192.168.1.20:8080;
    server 192.168.1.21:8080;
}

5.4.3 故障切换:DNS + LVS 双层

机房级故障时:

  1. DNS 切流(分钟级):把 VIP 摘除,流量自动去其他机房
  2. LVS VIP 切流(秒级):同机房内 Keepalived 切换
  3. Nginx upstream 切流(毫秒级):健康检查失败自动剔除

3 层切换配合:DNS 解决"机房级"、LVS 解决"机器级"、Nginx 解决"应用级"。

5.5 为什么 K8s 内部不直接用 Service/Ingress 而要 LVS?

很多读者会问:"我已经在 K8s 上了,为什么入口还要 LVS?"

K8s 内部确实有 Service(kube-proxy 实现)和 Ingress(nginx-ingress / traefik),但它们只解决"集群内"问题,解决不了"集群外"问题:

层级K8s 组件解决的问题解决不了的问题
Pod → Podkube-proxy (iptables/IPVS)集群内服务发现 + 负载均衡公网入口
集群入口Ingress (nginx-ingress)七层路由 + TLS 终结抗量级(单 ingress 几万 QPS)
机房入口LVS / 硬件 LB抗量 + 故障切换业务路由
跨机房DNS 调度用户就近接入秒级切流

K8s 环境的典型架构

LVS 在 K8s 环境承担的角色:

  • 抗量:nginx-ingress 单实例最多 1-3 万 QPS,几十个 pod 才能扛百万级
  • 入口高可用:nginx-ingress pod 漂移,VIP 还在 LVS 上
  • 跨节点均衡:多个 ingress pod 分布在不同节点,LVS 统一对外

📌 实践:K8s 入口的 3 种方案

  1. LVS + nginx-ingress(推荐):抗量 + 高可用 + 业务路由齐全
  2. 云厂商 LB + nginx-ingress:省运维,付出云费用
  3. 纯 Ingress(不要 LVS):只适合中小规模,大促会爆

六、4 种方案选型矩阵 + 真实案例

6.1 选型矩阵

方案抗量级RTO复杂度成本适用场景
单机 Nginx1-3 万 QPS不可用最低1xDemo、内部工具、低可用业务
Nginx + Keepalived 双机1-5 万 QPS< 10 秒2x中小业务、3 个 9 可用性
LVS-DR + Nginx + Keepalived5-50 万 QPS< 5 秒3-4x同城双活主流方案
LVS + Keepalived + Nginx 三层塔10-100 万 QPS< 3 秒5-8x异地多活、大促、4 个 9
自研 LB(SLB / MGW / BFE)> 100 万 QPS< 1 秒极高10x+阿里、美团、字节级别

6.2 真实案例对照

公司流量调度方案特点
某 SaaS 初创(日活 5 万)单机 Nginx1 台 4C8G 跑一年,挂了重启 5 分钟
某电商中型(日活 100 万)Nginx + Keepalived 双机2 台 8C16G,3 个 9 可用性
美团外卖MGW(自研 HAProxy + Tengine)单元化 + 异地双活
阿里双 11SLB(LVS + Tengine)三地五中心,50 万 + QPS
字节 TikTokBFE(自研 Go 网关)海外多 Region

6.3 决策流程

6.4 反例:选错的代价

反面案例 1:金融初创选了"单机 Nginx"

某支付初创公司 2019 年上线,日活 5 万,CTO 觉得"用 LVS 成本太高",选了单机 Nginx。2020 年春节红包活动,单机 Nginx 进程被大流量打挂,业务中断 2 小时。事后估算损失 200 万,远超"上 LVS 节省的 30 万成本"。

教训成本不是按"现在用不用得上"算,是按"挂了损失多少"算。即使是中小业务,强一致性场景也要至少 Nginx + Keepalived。

反面案例 2:电商中型选了"LVS 但没配 Keepalived"

某电商 2021 年自建机房,CTO 觉得"单 LVS 够用"。结果 LVS 机器硬件故障(电源烧了),入口整个挂掉,所有用户访问超时。事后抢修换机器,1 小时恢复。

教训LVS 本身是高可用组件里的"被保"对象。没有 Keepalived 保护,LVS 挂了入口就挂。永远要有"保 LVS 的人"

反面案例 3:直播平台选了"LVS + Nginx 但没做异地多活"

某直播平台 2022 年大促,所有流量集中在北京机房。北京机房的光纤被市政施工挖断,平台全挂 6 小时。事后损失数千万。

教训LVS + Nginx 解决的是"机房内"容灾,“机房外"必须异地多活。两个层次都要做,不能只看一个。

6.5 选型决策清单

业务特征推荐方案
日活 < 10 万、可用性要求低单机 Nginx
日活 10-100 万、3 个 9Nginx + Keepalived
日活 10-100 万、强 SLA 4 个 9LVS + Keepalived + Nginx 三层塔
日活 > 100 万、4 个 9LVS 三层塔 + 异地多活
日活 > 1000 万 或 上市级 SLA自研 LB(参考 SLB / MGW / BFE)

🎯 避坑点:选型不是越贵越好

见过最离谱的案例:日活 2 万的内部 OA 系统,上了 LVS + Keepalived + 自研监控全套。3 个人的运维团队维护 6 台服务器,效率极低

选型原则:用最少的组件满足需求。日活 2 万单机 Nginx 就够,别为了"高可用"过度设计


七、实操手册:一步步搭建 LVS + Keepalived + Nginx

本节是完整可复制粘贴的命令清单。准备 3 台 Linux 机器(CentOS 7 或 Ubuntu 20.04+),按角色分配:

角色IP用途
LVS-Master192.168.1.10LVS 主节点(也做 Keepalived Master)
LVS-Backup192.168.1.11LVS 备节点(也做 Keepalived Backup)
Nginx-Real192.168.1.20后端 Nginx 真实服务器(DR 模式)
VIP192.168.1.100对外服务的虚拟 IP

💡 小贴士:至少 2 台机器也能跑(Real Server 用 Docker 启 Nginx 模拟)。生产环境请按上面 3 台分配。

7.1 准备环境

7.1.1 关闭防火墙和 SELinux

1
2
3
4
5
# 3 台机器都执行
systemctl stop firewalld
systemctl disable firewalld
setenforce 0
sed -i 's/SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config

7.1.2 开启 IP 转发(DR 模式必需)

1
2
3
4
5
6
7
8
# LVS Master 和 Backup 上执行
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
sysctl -p

# 关闭 RP_FILTER(防 IP 伪造过滤)
echo "net.ipv4.conf.all.rp_filter = 0" >> /etc/sysctl.conf
echo "net.ipv4.conf.eth0.rp_filter = 0" >> /etc/sysctl.conf
sysctl -p

7.2 安装 IPVS(LVS 内核模块)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# LVS Master 和 Backup 上执行
yum install -y ipvsadm   # CentOS
# 或 apt install -y ipvsadm  # Ubuntu

# 加载 IPVS 内核模块
modprobe ip_vs
modprobe ip_vs_rr
modprobe ip_vs_wrr
modprobe ip_vs_sh

# 开机自启
echo "ip_vs" >> /etc/modules-load.d/ipvs.conf
echo "ip_vs_rr" >> /etc/modules-load.d/ipvs.conf
echo "ip_vs_wrr" >> /etc/modules-load.d/ipvs.conf
echo "ip_vs_sh" >> /etc/modules-load.d/ipvs.conf

# 验证
lsmod | grep ip_vs

7.3 安装 Nginx(Real Server 上)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 在 192.168.1.20 上执行
yum install -y nginx   # 或 apt install -y nginx

# 配置 Nginx 监听 VIP(在 lo 网卡上)
cat > /etc/nginx/conf.d/vip.conf << 'EOF'
server {
    listen 192.168.1.100:80;   # 监听 VIP
    server_name _;
    location / {
        return 200 "Hello from Real Server 192.168.1.20\n";
        add_header Content-Type text/plain;
    }
}
EOF

# 配置 lo 网卡绑 VIP
cat > /etc/init.d/realserver.sh << 'EOF'
#!/bin/bash
VIP=192.168.1.100
case "$1" in
  start)
    ifconfig lo:0 $VIP netmask 255.255.255.255 broadcast $VIP up
    echo "1" > /proc/sys/net/ipv4/conf/lo/arp_ignore
    echo "2" > /proc/sys/net/ipv4/conf/lo/arp_announce
    echo "1" > /proc/sys/net/ipv4/conf/all/arp_ignore
    echo "2" > /proc/sys/net/ipv4/conf/all/arp_announce
    ;;
  stop)
    ifconfig lo:0 down
    echo "0" > /proc/sys/net/ipv4/conf/lo/arp_ignore
    echo "0" > /proc/sys/net/ipv4/conf/lo/arp_announce
    echo "0" > /proc/sys/net/ipv4/conf/all/arp_ignore
    echo "0" > /proc/sys/net/ipv4/conf/all/arp_announce
    ;;
esac
EOF

chmod +x /etc/init.d/realserver.sh
/etc/init.d/realserver.sh start

🎯 避坑点:arp_ignore / arp_announce 必须配

这是 DR 模式最容易踩的坑。不配的话:

  • arp_ignore = 0(默认):Real Server 会对 ARP 请求"积极响应”,把 VIP 的 MAC 地址告诉别人
  • 结果:外部请求都到 Real Server 了,LVS 收不到包

arp_ignore = 1:只对"目标 IP 是本机接口的"ARP 请求响应。配合 arp_announce = 2(只向同网段宣告自己接口的 IP)才能正常工作。

启动 Nginx:

1
2
3
4
5
6
systemctl start nginx
systemctl enable nginx

# 验证
curl http://192.168.1.100
# 应输出 "Hello from Real Server 192.168.1.20"

7.4 安装 Keepalived

1
2
# LVS Master 和 Backup 上执行
yum install -y keepalived   # 或 apt install -y keepalived

7.4.1 LVS Master 配置 /etc/keepalived/keepalived.conf

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
! Configuration File for keepalived

global_defs {
    router_id LVS_MASTER
    script_user root
    enable_script_security
}

# 监控 LVS 自身进程
vrrp_script check_lvs {
    script "/usr/local/bin/check_lvs.sh"
    interval 2
    weight -20
    fall 3
    rise 2
    timeout 2
}

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/24 dev eth0 label eth0:1
    }
    track_script {
        check_lvs
    }
    nopreempt
    notify_master "/usr/local/bin/notify.sh master"
    notify_backup "/usr/local/bin/notify.sh backup"
}

# LVS 虚拟服务配置(DR 模式)
virtual_server 192.168.1.100 80 {
    delay_loop 6                    # 健康检查间隔
    lb_algo wlc                    # 加权最少连接
    lb_kind DR                     # DR 模式
    persistence_timeout 60         # 会话保持 60 秒
    protocol TCP

    real_server 192.168.1.20 80 {
        weight 1
        TCP_CHECK {
            connect_timeout 3
            nb_get_retry 3
            delay_before_retry 3
            connect_port 80
        }
    }
    # 如果有更多 Real Server,复制上面块
}

7.4.2 LVS Backup 配置(只改 3 处)

1
2
3
4
5
vrrp_instance VI_1 {
    state BACKUP               # ← 改
    priority 90                # ← 改
    # 其它和 Master 一致
}

7.4.3 健康检查脚本

1
2
3
4
5
6
7
8
# /usr/local/bin/check_lvs.sh(Master 和 Backup 各一份)
#!/bin/bash
# 检查 IPVS 是否加载
lsmod | grep ip_vs > /dev/null
if [ $? -ne 0 ]; then
    exit 1
fi
exit 0
1
chmod +x /usr/local/bin/check_lvs.sh

7.4.4 启动 Keepalived

1
2
3
4
5
6
7
# 两台机器分别启动
systemctl start keepalived
systemctl enable keepalived

# Master 上验证
ip addr show eth0
# 应看到 eth0:1 192.168.1.100

7.5 验证整套链路

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 1. 在 LVS Master 上查 IPVS 规则
ipvsadm -Ln
# 应看到:
# TCP  192.168.1.100:80 wlc
#   -> 192.168.1.20:80           Route   1      0          0

# 2. 客户端访问 VIP
curl http://192.168.1.100
# 应返回 "Hello from Real Server 192.168.1.20"

# 3. 看连接数
ipvsadm -Lnc

7.6 故障演练

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 演练 1:停止 Real Server 的 Nginx
ssh 192.168.1.20 "systemctl stop nginx"
# 30 秒后 IPVS 应自动剔除
ipvsadm -Ln
# 192.168.1.20 应消失

# 演练 2:停止 Master 的 Keepalived
systemctl stop keepalived
# 5 秒内 Backup 应接管 VIP
ssh 192.168.1.11 "ip addr show eth0"
# 应看到 eth0:1 192.168.1.100

# 演练 3:恢复 Master
systemctl start keepalived
# 因为配了 nopreempt,不会自动抢占(让 Backup 继续工作)

📌 实践:nopreempt 是好习惯

默认 Keepalived 会在 Master 恢复后自动抢占回 VIP。这种行为在生产中容易导致"脑裂"(短暂的"双 Master")。

配 nopreempt 后:Master 恢复后保持 Backup 状态,只有 Backup 挂了才接管。切换更稳定,但要手动维护(定期巡检 VIP 是否在期望的机器上)。

7.7 配套 Nginx upstream

如果 Real Server 跑的不是裸 Nginx,而是反向代理(Nginx → 多个微服务),配置就稍微复杂:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# /etc/nginx/conf.d/upstream.conf
upstream backend {
    server 192.168.1.30:8080 weight=1;
    server 192.168.1.31:8080 weight=1;
    keepalive 32;                      # ⭐ 与后端保持长连接
}

server {
    listen 192.168.1.100:80;
    location / {
        proxy_pass http://backend;
        proxy_http_version 1.1;        # ⭐ HTTP/1.1
        proxy_set_header Connection ""; # ⭐ 清空 Connection 头
        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_read_timeout 60s;
    }
}

3 个 ⭐ 参数

  • keepalive 32:与后端保持 32 个长连接池
  • proxy_http_version 1.1:HTTP/1.1(keepalive 必须)
  • proxy_set_header Connection "":清空 Connection 头(避免后端主动关闭)

7.8 容器化场景:Docker / K8s 下的等价配置

如果你的后端服务跑在 Docker / K8s 容器里,Nginx 的 upstream 配置需要相应调整:

Docker Compose 场景

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# docker-compose.yml
version: "3.8"
services:
  nginx:
    image: nginx:1.25
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - app1
      - app2
    networks:
      - appnet

  app1:
    image: myapp:1.0
    networks:
      - appnet
  app2:
    image: myapp:1.0
    networks:
      - appnet

networks:
  appnet:
    driver: bridge

对应的 Nginx upstream(用容器名代替 IP):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
upstream backend {
    server app1:8080;          # 容器名直接当主机名
    server app2:8080;
    keepalive 32;
}

server {
    listen 80;
    location / {
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        # ... 其他配置
    }
}

K8s 场景

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# ConfigMap + Deployment
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-upstream
data:
  upstream.conf: |
    upstream backend {
        server myapp.default.svc.cluster.local:8080;
        keepalive 32;
    }
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-proxy
spec:
  replicas: 3
  template:
    spec:
      containers:
      - name: nginx
        image: nginx:1.25
        volumeMounts:
        - name: upstream
          mountPath: /etc/nginx/conf.d
      volumes:
      - name: upstream
        configMap:
          name: nginx-upstream

📌 实践:K8s 中 keepalive 的"坑"

K8s 中 Pod IP 随时可能变化(重启、调度、扩缩容)。如果把 Pod IP 写死在 upstream 里,Pod 重启一次就全部失效

正确做法:用 Service 的 DNS 名称(myapp.default.svc.cluster.local),Nginx 启动时 DNS 解析一次。Service 后端 Pod 变化时,长连接池内残留的旧连接会失败,触发自动重连

7.9 性能压测:wrk 与 ab 实战

搭建完链路后,必须做压测确认性能。推荐 2 个工具:

wrk(推荐,Lua 脚本扩展)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 安装
git clone https://github.com/wg/wrk.git
cd wrk && make

# 4 线程压 30 秒
wrk -t4 -c1000 -d30s http://192.168.1.100/

# 输出示例
# Running 30s test @ http://192.168.1.100/
#   4 threads and 1000 connections
#   Thread Stats   Avg      Stdev     Max   +/- Stdev
#     Latency    12.34ms   15.67ms 250.00ms   89.32%
#     Req/Sec     8.50k     1.23k    9.87k    72.45%
#   900000 requests in 30.00s, 240.00MB read
# Requests/sec:  30000.00
# Transfer/sec:      8.00MB

ab(Apache Bench,简单场景)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 1 万请求,100 并发
ab -n 10000 -c 100 http://192.168.1.100/

# 关键看三个指标
# Requests per second:    30000 [#/sec]   ← QPS
# Time per request:       3.333 [ms]      ← 平均延迟
# Percentage of requests served within a certain time (ms)
#   50%      2
#   95%      8
#   99%     15
#   100%     30

压测要看 P99 延迟,不要只看平均

  • 平均延迟 5ms,但 P99 500ms → 长尾问题严重
  • P99 < 50ms 才算健康

八、踩坑与监控

8.1 10 个常见坑速查

#现象解决
1arp_ignore / arp_announce 没配LVS 收不到包,外部直接连 Real ServerReal Server 上配 arp_ignore=1, arp_announce=2
2Nginx upstream 没配 keepaliveTIME_WAIT 飙升,端口耗尽keepalive 32 + proxy_http_version 1.1 + Connection ""
3Keepalived 脑裂双 Master 互相抢 VIP单播 VRRP + 独立心跳线 + 业务侧冲突检测
4LVS DR 模式跨 VLANReal Server 收不到 ARP同 VLAN 部署,或换 FULLNAT
5VIP 漂移后 ARP 表未更新客户端缓存旧 MAC 地址在 LVS 配 notify_master 脚本,主动广播 gratuitous ARP
6nopreempt 没配Master 恢复后双 Master 抢 VIPnopreempt,避免频繁切换
7LVS Master 单点LVS 挂了整个入口挂必须 Keepalived 双机 + 监控
8ip_forward 没开DR 模式包转发失败sysctl -w net.ipv4.ip_forward=1
9Real Server lo:0 没绑 VIP拒绝服务(认为非本机 IP)Real Server 上 ifconfig lo:0 VIP netmask 255.255.255.255
10LVS 健康检查误判偶发抖动导致全摘调大 nb_get_retrydelay_before_retry

8.2 监控告警

8.2.1 必监控指标

指标阈值工具
VIP 是否在期望机器上漂移即告警Keepalived notify_* 钩子 + Prometheus
IPVS 活跃连接数> 80% 容量告警ipvsadm -Ln + node_exporter
Real Server 健康状态任何 down 立即告警Keepalived virtual_server 块内置检查
LVS Master / Backup 进程存活进程消失告警systemd + Prometheus
Nginx upstream 失败率> 1% 告警Nginx ngx_http_upstream_check_module
跨机房专线延迟> 50ms 告警自研 ping 探针

8.2.2 拨测(公网心跳)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# /usr/local/bin/probe.sh(部署在公网多地域)
#!/bin/bash
TARGET="http://192.168.1.100/health"
for i in {1..10}; do
    code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 $TARGET)
    if [ "$code" != "200" ]; then
        echo "ALARM: probe failed, code=$code" | mail -s "LVS probe failed" ops@example.com
        break
    fi
    sleep 6
done

拨测要部署在公网,机房内的监控不能替代公网探测。

🎯 避坑点:监控盲区

最常见的监控盲区是"机房内一切正常,机房外访问不到"。常见原因:

  • 机房出口防火墙拦截
  • 跨机房专线中断
  • 公网 BGP 路由异常

唯一可靠的判断是"公网能否正常访问"。拨测是异地多活的"生命线"。

8.3 3 个真实故障案例(公开复盘整理)

案例中数字来源于公开复盘报告,已脱敏处理。重点看"现象 → 根因 → 修复"三段式分析。

案例 1:Keepalived 切换抖动导致业务频繁超时

  • 现象:某电商 2022 年大促,业务监控发现每隔 30-60 秒就有 1-2 秒的请求超时。QPS 越高峰越明显。
  • 根因:Keepalived 主备之间的 VRRP 心跳被交换机 MAC 表老化干扰,导致短暂的双 Master / 双 Backup 切换。每次切换有 1-2 秒的 ARP 重学习窗口,期间流量黑洞。
  • 修复:(1) 调整交换机 MAC 表老化时间到 30 分钟;(2) Keepalived 配 unicast_src_ip 单播模式,避免多播丢包;(3) 监控加上"VIP 1 分钟内切换次数"告警。

案例 2:LVS DR 模式跨 VLAN 部署,ARP 全部失败

  • 现象:某金融公司 2021 年新机房部署 LVS,所有请求都直连到 Real Server,LVS 收不到包。Keepalived 健康检查一直告警。
  • 根因:运维把 LVS 和 Real Server 放在不同 VLAN(中间隔了路由器),但 LVS DR 模式是 L2 转发,L2 广播域不通就无法做 ARP 替换
  • 修复:(1) 把 LVS 和 Real Server 改到同一 VLAN;(2) 如果必须跨 VLAN,改用 FULLNAT 模式(阿里云方案);(3) 在网络拓扑图里显式标注 LVS 边界,避免后续运维踩坑。

案例 3:nginx keepalive 配置错,TPS 暴跌 70%

  • 现象:某 SaaS 公司 2023 年迁移到新机房,TPS 从 5000 降到 1500,后端 CPU 反而飙到 90%。
  • 根因:运维在 nginx.conf 里漏了 proxy_set_header Connection ""(Nginx 模板是复制的,这一行没复制到)。结果后端收到 Connection: close 主动关闭 TCP 连接每秒多 5000 次 3 次握手 + 4 次挥手
  • 修复:(1) 补全 3 个 keepalive 参数;(2) 加上 wrk 压测 baseline 监控,配置变更后自动对比基线;(3) 把 3 个 keepalive 参数写进 Ansible 模板,代码化防止遗漏

🛑 误区警示:3 个案例的共性

这 3 个案例的共性:

  1. 配置层面的错误,不是软件 bug
  2. 监控告警虽然响了,但没被及时处理(大促期间没空看)
  3. 变更没有"基线对比"——配置改完没和历史数据对比,TPS 跌 70% 都没发现

防御方法:

  • 配置变更走 PR + 评审 + 灰度
  • 关键指标(TPS / P99 / 错误率)配基线告警(偏离基线 30% 立刻报警)
  • 重要配置写进代码(Ansible / Helm Chart),不要让人肉 cp 配置文件

8.4 5 条运维铁律

最后给做流量调度的运维 5 条铁律:

  1. 任何变更先在预发环境跑 1 周:配置错误往往不是立即暴露,是运行 3-7 天后才出问题(如内存泄漏、连接耗尽)
  2. 关键配置做成"代码":用 Ansible / Helm Chart 管理,避免人肉 cp 配置文件
  3. 监控 + 告警 + Runbook 三件套:监控发现问题,告警通知人,Runbook 指导人怎么修。缺一不可
  4. 故障演练常态化:每月 1 次小演练,季度 1 次大演练。没演练过的高可用 = 假高可用
  5. 复盘文化:每次故障 24 小时内复盘,输出 Action Item 并跟踪。不复盘的故障 = 还会再发生

总结

9.1 流量调度的演进小结

演进的内在动力

  • 业务量增长 → 单机撑不住 → 多机
  • 可用性要求提高 → 单点故障不能接受 → Keepalived
  • 性能天花板 → Nginx 不够 → LVS
  • 极致性能 + 异地多活 → 三层塔 + 自研

9.2 3 大核心要素

承接系列第 1 篇的"业务 / 数据 / 流量"三分法,流量调度层的 3 大核心

  1. 冗余:每层都有主备,任意一层挂了下一层接住
  2. 分层:LVS 抗量 / Nginx 路由 / App 业务,各司其职
  3. 自动化:健康检查 + 自动剔除 + 自动接管,无需人工介入

9.3 系列预告

这是 Java Web 微服务系列 的第 2 篇。后续计划:

  • 第 3 篇Spring Cloud Gateway:应用层网关的精细化治理 —— 微服务内部路由、灰度、限流、鉴权
  • 第 4 篇Nacos + Sentinel + Seata 三件套实战 —— 服务发现 + 流量治理 + 分布式事务
  • 第 5 篇SkyWalking 全链路追踪 —— 异地多活下的链路可观测性
  • 第 6 篇自研 LB 设计要点 —— 100 万 QPS 级别的流量调度系统

回到系列开篇的命题:异地多活不是"装个软件就完事",是"用钱换命“的系统工程。流量调度作为"前哨班”,看似只是几个开源组件的拼接,实际踩过的坑足够写 100 篇博客

本篇的实操清单是"最小可运行版本",生产环境请结合 §8.2 的监控指标 + 业务场景做调整。异地多活的"前哨班"搭好了,业务系统的可用性才能真正"扛得住"。


附录:常见问题 FAQ

本节是面向架构评审、面试、新人入职的速查清单——把读者最常问的问题集中解答。

A1:LVS 和 Nginx 能不能装在同一台机器上?

技术上可以,生产上不推荐

LVS 是内核态转发,Nginx 是用户态进程。装同一台会:

  • 资源争抢:LVS 转发吃 CPU,Nginx 处理 HTTP 也吃 CPU,互相挤兑
  • 故障域重叠:机器挂了 LVS 和 Nginx 一起挂双层塔降级成单层
  • 监控混乱:出问题时分不清是 LVS 还是 Nginx 的锅

正确做法:LVS 机器只跑 LVS + Keepalived,Nginx 机器只跑 Nginx + 应用。职责单一

A2:Keepalived 主备之间是用多播还是单播?

生产环境推荐单播unicast_src_ip + unicast_peer)。

多播(默认)的问题:

  • 很多云厂商默认禁止多播(224.0.0.0/4 段)
  • 跨网段多播经常被丢
  • 安全扫描工具会报"未加密的多播协议"漏洞

单播的优点:

  • 点对点通信,经过路由器也能通
  • 更安全(不会被同网段其他机器听到 VRRP 通告)
  • 可观测(TCP 单播能用 tcpdump 抓包分析)

A3:DR 模式下 Real Server 为什么需要把 VIP 绑到 lo 网卡?

因为 DR 模式 LVS 只改目的 MAC、不改目的 IP。Real Server 收到包后发现目的 IP 是 VIP,不是自己的 IP(如 192.168.1.20),默认会丢弃(认为不是发给自己的)。

把 VIP 绑到 lo 网卡后,Real Server 觉得"这个 IP 是我的",会正常处理。lo 是 loopback 接口,不对外,所以不会引起 ARP 冲突。

A4:Keepalived 检测到 Nginx 挂了,VIP 切到 Backup,但 Backup 的 Nginx 没起来怎么办?

典型场景:Keepalived 进程比 Nginx 进程先起

Keepalived 默认只检测自身进程是否存活,不会检查 Nginx。所以 Nginx 没起来,Keepalived 也会把 VIP 拉过来——用户访问 VIP,结果是连接被拒

正确做法:用 vrrp_script 自定义健康检查(参考 §3.3.2 脚本):

1
2
3
4
5
6
7
#!/bin/bash
# check_nginx.sh
count=$(ps -ef | grep "nginx: master process" | grep -v grep | wc -l)
if [ $count -eq 0 ]; then
    exit 1
fi
exit 0

脚本返回非 0 = 优先级降 20 = 自动让出 VIP。这样只有 Nginx 也起来了,Backup 才接管 VIP。

A5:异地多活的流量调度和同城双活的本质区别是什么?

同城双活:机房之间 RTT < 5ms,LVS/Nginx 跨机房 RPC 几乎无感。技术上同城双活就是"在一个城市里"。

异地多活:机房之间 RTT 30-100ms,任何跨机房调用都是 60-200 倍延迟。所以严格禁止跨机房 RPC,必须靠单元化路由 + DNS 调度让用户"绑死"在一个机房。

对应到流量调度

  • 同城双活:LVS 可在机房之间漂移(Keepalived + 跨机房心跳)
  • 异地多活:每个机房一套独立的 LVS + Keepalived 集群,机房之间通过 DNS 互导

A6:硬件 LB(F5 / A10)和软件 LVS 怎么选?

维度硬件 LB软件 LVS
性能千万级 QPS百万级 QPS
成本几十万到上百万几千到几万
可定制黑盒,只能配完全开源,可深度定制
故障恢复厂商技术支持靠自己
典型用户银行、证券、政企互联网公司

实战选择

  • 钱多 + 求稳:硬件 LB
  • 钱紧 + 求灵活:LVS(互联网公司主流)
  • 混合:核心入口用硬件 LB,业务入口用 LVS

A7:为什么很多公司直接用云厂商 LB 而不自己搭 LVS?

直接原因:省事。云厂商 LB 帮你搞定:

  • 多 AZ 高可用(自带 Keepalived)
  • 健康检查 + 自动剔除(自带 LVS 健康检查)
  • 跨 Region 调度(自带 DNS 调度)
  • DDoS 防护(云厂商自带)
  • TLS 终结(证书托管)

深层原因大部分公司没有"自建 LB"的运维能力。一个 5 人运维团队,维护业务都吃力,没人专门优化 LVS 内核参数用云厂商 LB 是 ROI 更高的选择

自建 LVS 的场景

  • 业务量足够大(单 LB 月费用 > 50 万
  • 有专门的 SRE 团队
  • 公有云满足不了的特殊需求(如跨云、混合云)

A8:流量调度层和应用层网关(Spring Cloud Gateway)是什么关系?

层级不同

层级职责典型组件
流量调度层(本篇)机房入口 + 抗量 + 7 层路由LVS、Nginx、HAProxy
应用层网关(系列第 3 篇)微服务内部路由 + 灰度 + 限流 + 鉴权Spring Cloud Gateway、Kong、APISIX

举例

  • 用户访问 https://api.example.com/orderLVSNginx(按 host 路由)→ Spring Cloud Gateway(按 URL 路由到 order 微服务)→ order 服务
  • Nginx 只看 host / URL 前缀Spring Cloud Gateway 看完整微服务元数据(服务名、版本、灰度标签)

性能差异

  • LVS 单实例 50 万 QPS
  • Nginx 单实例 1-5 万 QPS
  • Spring Cloud Gateway 单实例 1-5 千 QPS(Java 业务逻辑开销)

所以入口用 LVS/Nginx 抗量,业务内部用 Spring Cloud Gateway 精细治理两个层都不可少

A9:怎样评估"流量调度层是否需要升级"?

3 个信号

  1. 容量告警ipvsadm -Ln 看到活跃连接数持续 > 80% 单机容量
  2. 延迟告警:监控显示 P99 延迟持续上涨,已经超过业务容忍上限
  3. 故障切换告警:Keepalived 1 周内切换 > 3 次,说明单点故障频繁

升级路径

  • 单机 Nginx 撑不住 → Nginx + Keepalived(水平扩展)
  • Nginx + Keepalived 撑不住 → LVS + Keepalived + Nginx(引入 LVS 抗量)
  • LVS 也撑不住 → 多 LVS 集群 + 云厂商 LB(水平扩展 LVS)
  • 自研成本低于云厂商 LB 费用 → 自研 LB(参考 SLB/MGW/BFE)

不要"为了升级而升级"。日活 50 万的电商上 LVS 是合理,日活 5 万的内部 OA 上 LVS 就是浪费

A10:流量调度的未来趋势是什么?

3 个明确方向

  1. eBPF 替代 IPVS:eBPF 可以在内核更早的钩子点(XDP)处理包转发,比 IPVS 性能高 5-10 倍。Cilium 等项目已经在用 eBPF 替代 kube-proxy
  2. Service Mesh 接管 7 层:Envoy + Istio 在 K8s 内部已经能做精细流量治理,部分场景可以替代 nginx-ingress
  3. 云原生 LB:云厂商 LB 越来越智能,自带灰度、A/B 测试、流量镜像,未来自建 LB 的场景会越来越少

但这不意味着传统 LVS/Nginx 会被淘汰

  • 物理机 / 虚拟机场景仍是主流(很多公司没上 K8s)
  • LVS 内核态转发的性能优势是物理规律,eBPF 只是更近一步
  • 简单可靠的技术会长期存在(KISS 原则)

🛑 误区警示:追新 ≠ 用对

见过最离谱的:日活 10 万的电商,为了"用 Service Mesh"硬上 Istio。结果团队 3 个人花了 3 个月学 Istio,业务没提升,运维复杂度暴涨

原则:用最熟悉、最稳定、最容易招聘运维的技术新技术的红利期过了再用


参考文章

本系列共 16 篇,本文为第 2 篇 · 查看全部
使用 Hugo 构建
主题 StackJimmy 设计