
凌晨 2:17,NOC 电话把我从折叠床上叫醒:“世界频道掉线密集,活跃连接骤降 20%。”
我披着外套冲进香港机房,交换机风扇像一架小型飞机。Grafana 上,世界频道的 TCP 长连接像雪崩一样往下掉。HAProxy(七层)没顶住尖峰,切回四层 LVS 的计划不得不提前。那一刻我很清楚:要么在半小时内切换到 LVS + Keepalived,要么等日间高峰时看着世界频道继续掉线。
下面是我当夜的实施步骤、所有配置、优化项和我踩过的坑。
1. 目标架构与选择
业务特性: 世界频道是典型的长连接(WebSocket/TCP),消息量大、包小且均匀,连接对抖动异常敏感。
架构目标:
- 四层负载(LVS/DR):极低开销转发,适合大量长连接;
- 主备高可用(Keepalived/VRRP):主机故障秒级漂移 VIP,不感知或尽量少感知;
- 会话保持:同一客户端优先落到同一 RealServer(尽量减少频道上下文抖动);
- 可观测:转发/健康检查/ARP/GARP 都要有指标与日志。
LVS 模式选择:
- 优先:LVS-DR(Direct Routing):LB 只负责入口,回包由后端直出,延迟最低,吞吐最大;前提是 LB 与 RS 同二层(同 VLAN)。
- 备选:LVS-NAT:跨网段时使用,但会引入 LB 上的 conntrack 压力和 SNAT 性能损耗。
本文以 LVS-DR 为主线,文末附 NAT 方案差异化配置。
2. 硬件与网络规划
2.1 设备与参数
| 角色 | 数量 | CPU | 内存 | 网卡 | 系统盘 | 带宽 | 备注 |
|---|---|---|---|---|---|---|---|
| LVS LB(主/备) | 2 | Xeon Silver / EPYC 7xx | 64–128 GB | 2×10GbE(独立上/下行) | NVMe 480G | 10G | Debian 12 (Bookworm) |
| RealServer(游戏网关/频道服) | 4–12 | 同上 | 64–128 GB | 2×10GbE | NVMe 960G | 10G | Debian 12 |
说明:双口 10GbE 能把上行(到公网/边界)和下行(到 RS 业务 VLAN)隔离,减少抢占和抖动。NVMe 对 LVS 不敏感,但对日志与指标有益。
2.2 IP 规划(示例)
| 名称 | 地址/掩码 | 说明 |
|---|---|---|
| VIP(世界频道) | 203.0.113.10/32 |
对公网暴露(BGP/静态均可) |
| LB1(上行) | 203.0.113.101/24 |
出口/管理 |
| LB2(上行) | 203.0.113.102/24 |
出口/管理 |
| LB1(下行) | 10.10.10.11/24 |
至 RS VLAN |
| LB2(下行) | 10.10.10.12/24 |
至 RS VLAN |
| RS1-4 | 10.10.10.21-24/24 |
频道服后端 |
| 网关(下行) | 10.10.10.1 |
ToR 交换机 SVI |
LVS-DR 关键点:VIP 也要配置在每台 RS 的 lo 上(/32),并关闭 ARP 响应,否则会出现 VIP 漂移与 ARP 冲突(后面有完整命令)。
3. 系统准备(Debian 12)
3.1 基础安装与内核模块
# 基础包
apt update
apt install -y ipvsadm keepalived nftables ethtool net-tools chrony jq rsyslog
# 加载 LVS 模块(持久化)
cat >/etc/modules-load.d/ipvs.conf <<'EOF'
ip_vs
ip_vs_rr
ip_vs_wrr
ip_vs_sh
nf_conntrack
EOF
modprobe ip_vs ip_vs_rr ip_vs_wrr ip_vs_sh nf_conntrack
3.2 时间同步
timedatectl set-timezone Asia/Hong_Kong
systemctl enable --now chrony
4. 内核网络调优(LB 与 RS)
下面参数是我在世界频道场景下的“保守增强型”基线;你可以先用它跑通,再按压测曲线微调。
4.1 LB(LVS 节点)建议 sysctl
cat >/etc/sysctl.d/99-lvs-lb.conf <<'EOF'
# 队列/缓冲
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 300000
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.core.rmem_default = 4194304
net.core.wmem_default = 4194304
# TCP 队列与半连接
net.ipv4.tcp_max_syn_backlog = 262144
net.ipv4.tcp_syncookies = 1
# 端口范围
net.ipv4.ip_local_port_range = 1024 65535
# 路由与邻居缓存
net.ipv4.neigh.default.gc_thresh1 = 4096
net.ipv4.neigh.default.gc_thresh2 = 8192
net.ipv4.neigh.default.gc_thresh3 = 16384
# 禁止源路由过滤(VRRP/多口)
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.default.rp_filter = 0
# LVS-DR:LB 不负责回程,减少跟踪
net.netfilter.nf_conntrack_tcp_loose = 1
EOF
sysctl --system
网卡优化(LB):
# 关闭 LRO(低延迟场景更稳),保留 GRO 视压测而定
ethtool -K eth0 lro off
ethtool -K eth1 lro off
# 多队列收发
ethtool -L eth0 combined 8
ethtool -L eth1 combined 8
# 查看并根据 CPU 绑核(示例,具体看 /proc/interrupts)
# irqbalance 在低延迟场景可停用,改用手动绑核
systemctl stop irqbalance && systemctl disable irqbalance
4.2 RS(后端)建议 sysctl
cat >/etc/sysctl.d/99-lvs-rs.conf <<'EOF'
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 300000
net.ipv4.tcp_max_syn_backlog = 262144
net.ipv4.tcp_syncookies = 1
# 长连接更友好:尽快探测死连接
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 5
# TIME-WAIT 参数在新内核已调整,无需设置已废弃选项
net.ipv4.ip_local_port_range = 1024 65535
# ARP 策略(LVS-DR 必配,避免 VIP ARP 冲突)
net.ipv4.conf.all.arp_ignore = 1
net.ipv4.conf.all.arp_announce = 2
net.ipv4.conf.default.arp_ignore = 1
net.ipv4.conf.default.arp_announce = 2
EOF
sysctl --system
5. 部署步骤(LVS-DR 主线)
5.1 在每台 RS 配置 VIP(loopback)
以 VIP=203.0.113.10 为例:
# 添加 VIP 到 lo,掩码 /32,永久化
cat >/etc/systemd/network/lo_vip.netdev <<'EOF'
[NetDev]
Name=lo
Kind=loopback
EOF
cat >/etc/systemd/network/lo_vip.network <<EOF
[Match]
Name=lo
[Address]
Address=203.0.113.10/32
Label=lo:vip
EOF
systemctl enable --now systemd-networkd
也可用 ifupdown / ip 命令直接添加:
ip addr add 203.0.113.10/32 dev lo(需写入开机脚本持久化)
务必确认 ARP 策略已按上节 sysctl 配置,否则出现**“VIP 偶发被 RS 抢答 ARP”**的问题,LB 的 GARP 会被“打回”。
5.2 Keepalived(LB1 / LB2)
我们使用 VRRP 单播(很多机房屏蔽多播),并在 Keepalived 的 virtual_server 段内直接驱动 LVS(无需手写 ipvsadm)。
/etc/keepalived/keepalived.conf(LB1:MASTER)
global_defs {
router_id LVS_HK_LB1
enable_script_security
script_user root
# log 通过 rsyslog 输出
}
vrrp_instance VI_1 {
state MASTER
interface eth0 # 承载 VIP 的出口口
virtual_router_id 51
priority 150
advert_int 1
# 单播(对端为 LB2 上行口 IP)
unicast_src_ip 203.0.113.101
unicast_peer {
203.0.113.102
}
authentication {
auth_type PASS
auth_pass 9V9pQxk3
}
# GARP 加强,解决交换机缓存未更新导致短时黑洞
garp_master_delay 1
garp_master_repeat 5
garp_lower_prio_repeat 3
virtual_ipaddress {
203.0.113.10/24 dev eth0 label eth0:vip
}
# 健康检查脚本(可选,用于拉低优先级触发漂移)
track_script {
chk_world_port
}
nopreempt # 避免抖动时反复主备切换
}
vrrp_script chk_world_port {
script "/usr/local/bin/check_world.sh"
interval 2
timeout 1
rise 2
fall 3
weight -20
}
virtual_server 203.0.113.10 443 {
delay_loop 3
lb_algo sh # 源地址哈希,天然会话保持
lb_kind DR
persistence_timeout 3600 # 连接保持 1h(按业务调)
protocol TCP
real_server 10.10.10.21 443 {
TCP_CHECK {
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
}
}
real_server 10.10.10.22 443 {
TCP_CHECK {
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
}
}
real_server 10.10.10.23 443 {
TCP_CHECK {
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
}
}
real_server 10.10.10.24 443 {
TCP_CHECK {
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
}
}
}
LB2(BACKUP) 配置与 LB1 一致,仅将以下项调整:
router_id LVS_HK_LB2
state BACKUP
priority 100
unicast_src_ip 203.0.113.102
unicast_peer { 203.0.113.101 }
健康检查脚本 /usr/local/bin/check_world.sh:
#!/usr/bin/env bash
# 简单检查:至少有 1 台 RS 的 443 存活且 LVS 表项存在
set -e
timeout 1 bash -c "</dev/tcp/10.10.10.21/443" || \
timeout 1 bash -c "</dev/tcp/10.10.10.22/443" || \
timeout 1 bash -c "</dev/tcp/10.10.10.23/443" || \
timeout 1 bash -c "</dev/tcp/10.10.10.24/443" || exit 1
# LVS 表项是否已加载
ipvsadm -Ln | grep -q 'TCP 203.0.113.10:443'
chmod +x /usr/local/bin/check_world.sh
systemctl enable --now keepalived
会话保持策略:
- 我用 lb_algo sh(源地址哈希) + persistence_timeout,既能让同一客户端稳定落到同一台 RS,又允许 RS 故障时平滑迁移。
- 如果你的客户端在 NAT 后(如运营商 CGNAT),**mh(Maglev Hash)**也值得尝试,hash 分布更均衡。
5.3 验证 LVS 表项
ipvsadm -Ln
# 预期能看到 VIP:443 以及 4 个 RealServer
ipvsadm -Ln --stats
ipvsadm -Ln --rate
5.4 防火墙(nftables 示例)
LB 允许 VRRP、业务端口与健康检查:
cat >/etc/nftables.conf <<'EOF'
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0;
ct state established,related accept
iif "lo" accept
# VRRP
ip protocol vrrp accept
# SSH
tcp dport 22 accept
# 世界频道端口(示例 443)
tcp dport 443 accept
# ICMP
ip protocol icmp accept
ip6 nexthdr ipv6-icmp accept
# 默认丢弃
counter drop
}
}
EOF
systemctl enable --now nftables
LVS-DR 下,LB 回程不走 LB,因此 无需在 LB 上跟踪回包;上面规则只放行入站必要流量即可。
6. 业务接入与灰度
- RS 端口预热:在 RS 上提前建立监听(例如 Nginx/Envoy 反代到世界频道进程,或游戏网关直接监听 443/7000);
- 小流量引流:在边界路由/BGP 上先把 5–10% 流量导向 VIP,观察 10–15 分钟;
观察项:
- ipvsadm -Ln --stats/--rate 的 PPS/Conn 增长是否线性;
- RS 连接数分布是否均衡(ss -s / ss -ant | wc -l);
- 链路延迟与丢包(smokeping/mtr);
- 世界频道的心跳超时与重连次数是否下降。
7. 线上“坑”与现场解法
坑 1:切换后短时仍有掉线
现象:VIP 漂移到 LB1 后,个别运营商段用户 10–30 秒不能连上。
原因:上游交换机/路由缓存了旧的 MAC,GARP 次数不够。
解决:Keepalived 增加 garp_master_repeat 5,必要时手动发送多轮 GARP:
arping -I eth0 -U -c 10 203.0.113.10
坑 2:VIP 被 RS 抢答 ARP
现象:arp -an 看到 RS 的 MAC 与 VIP 绑定,流量绕过 LB。
原因:RS 未按 DR 要求设置 ARP 策略或 lo:vip 未用 /32。
解决:严格执行 arp_ignore=1/arp_announce=2,VIP 用 /32 绑 lo。
坑 3:Keepalived 不主不备,VRRP 一直切
原因:机房禁用多播,配置了 vrrp 多播模式。
解决:改用 unicast,配置 unicast_src_ip/unicast_peer。
坑 4:LVS-NAT 下连接打满 conntrack
现象:dmesg/conntrack -S 报告表满,延迟飙升。
解决:
增大 nf_conntrack_max 与哈希表;
更建议回到 DR 模式,把回程从 LB 拆掉。
坑 5:GRO/LRO 导致延迟陡增
现象:尾延迟 p99 偶发上扬。
解决:LB 上关闭 LRO,GRO 视压测决定。
8. 监控与可观测
LVS 指标:
# 累计统计
cat /proc/net/ip_vs_stats
# 粒度统计
ipvsadm -Ln --stats
ipvsadm -Ln --rate
Keepalived 日志:
- /var/log/syslog(或 rsyslog 定向)
- 关注 VRRP 状态变更、脚本触发、GARP 发送次数
RS 侧:
- ss -ant 'sport = :443' | wc -l 连接数
- 世界频道进程的心跳超时/重连计数(接入应用层监控)
9. 压测与优化要点
调度算法选择:
- sh(源地址哈希)适合 NAT 用户分布广;
- mh(maglev hash)分布更均匀,故障时重映射更稳定;
- wrr 可用于按 RS 算力配权。
- persistence_timeout:长连接业务可设 30–120 分钟;过短会导致上下文迁移。
- Nagle/延迟:应用层关闭 Nagle(TCP_NODELAY)能降低消息尾延迟。
- 多端口分摊:世界频道与登录服分用不同 VIP/端口,避免相互影响。
- IRQ/RPS 绑核:高并发环境下能显著降低抖动(结合 ethtool -x/-X)。
10. 方案 B:LVS-NAT(跨网段时)
差异要点:
- virtual_server 里改为 lb_kind NAT;
- LB 上配置 SNAT(nftables)到 LB 下行口 IP,与 conntrack 配套调大;
监控 conntrack -S,并增大:
cat >/etc/sysctl.d/98-conntrack.conf <<'EOF'
net.netfilter.nf_conntrack_max = 10485760
net.netfilter.nf_conntrack_buckets = 524288
EOF
sysctl --system
压力较大时优先考虑回归 DR 或者在 RS 上做就近回程(策略路由)。
11. 常用命令速查
# 查看 LVS 状态
ipvsadm -Ln
ipvsadm -Ln --stats --rate
# 添加/删除 RS(临时)
ipvsadm -a -t 203.0.113.10:443 -r 10.10.10.25:443 -g
ipvsadm -d -t 203.0.113.10:443 -r 10.10.10.25:443
# 手工 GARP
arping -I eth0 -U -c 10 203.0.113.10
# 观察连接
ss -s
ss -ant | awk 'NR>1{print $1}' | sort | uniq -c
12. 最小可运行清单(你可以直接照抄)
在 RS:
- ip addr add 203.0.113.10/32 dev lo(或持久化)
- sysctl:arp_ignore=1、arp_announce=2
- 监听世界频道端口(如 443)
在 LB(主/备):
- 安装 ipvsadm keepalived
- keepalived.conf:vrrp_instance 单播 + virtual_server(DR, sh 调度,persistence_timeout)
- 开启 keepalived,确认 ipvsadm -Ln 有表项
网络:
- VIP 对外可达,GARP 生效(garp_* 参数与 arping)
- 边界路由/BGP 指向 VIP 所在 LB 段
观测:
- ip_vs_stats、--stats/--rate、应用层心跳/重连计数
到 5:40,掉线告警安静了,在线曲线慢慢回到熟悉的坡度。
我靠着机柜坐下,把刚才的步骤和参数一条条补全到变更单里。LVS + Keepalived 没有花哨的仪表盘,但在这种“长连接、低延迟、峰值高”的场景里,它像台钝重但可靠的老机器。
日出之前,我又手动发了一遍 GARP,确认交换机记住了新主。世界频道里,又开始有人在晒夜宵。
附:完整配置模板(可直接套用)
A. LB(MASTER)完整 keepalived.conf
global_defs {
router_id LVS_HK_LB1
enable_script_security
script_user root
}
vrrp_script chk_world_port {
script "/usr/local/bin/check_world.sh"
interval 2
timeout 1
rise 2
fall 3
weight -20
}
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 150
advert_int 1
unicast_src_ip 203.0.113.101
unicast_peer {
203.0.113.102
}
authentication {
auth_type PASS
auth_pass 9V9pQxk3
}
garp_master_delay 1
garp_master_repeat 5
garp_lower_prio_repeat 3
virtual_ipaddress {
203.0.113.10/24 dev eth0 label eth0:vip
}
track_script {
chk_world_port
}
nopreempt
}
virtual_server 203.0.113.10 443 {
delay_loop 3
lb_algo sh
lb_kind DR
persistence_timeout 3600
protocol TCP
real_server 10.10.10.21 443 { TCP_CHECK { connect_timeout 3 nb_get_retry 3 delay_before_retry 3 } }
real_server 10.10.10.22 443 { TCP_CHECK { connect_timeout 3 nb_get_retry 3 delay_before_retry 3 } }
real_server 10.10.10.23 443 { TCP_CHECK { connect_timeout 3 nb_get_retry 3 delay_before_retry 3 } }
real_server 10.10.10.24 443 { TCP_CHECK { connect_timeout 3 nb_get_retry 3 delay_before_retry 3 } }
}
B. LB(BACKUP)差异
router_id LVS_HK_LB2
state BACKUP
priority 100
unicast_src_ip 203.0.113.102
unicast_peer { 203.0.113.101 }
C. RS(loopback VIP + ARP 策略)
# VIP
ip addr add 203.0.113.10/32 dev lo
# ARP(持久化建议写入 /etc/sysctl.d)
sysctl -w net.ipv4.conf.all.arp_ignore=1
sysctl -w net.ipv4.conf.all.arp_announce=2
sysctl -w net.ipv4.conf.default.arp_ignore=1
sysctl -w net.ipv4.conf.default.arp_announce=2