香港服务器如何用“大带宽 + 负载均衡”扛住 SaaS 大客户并发冲击
技术教程 2025-09-19 09:33 209


那天晚上 21:40,我正准备关掉 Prometheus 的告警面板,手机突然震到手麻:一家 500 强客户把我们 SaaS 的全量用户导入排到了晚上,脚本没做节流,东八区 22:00 一把梭。香港机房的出口曲线像电梯一样直冲 8 Gbps,前端 QPS 从 3k 飙到 28k,登录与批量任务 API 同时打。运维群静了两秒,大家统一发了一个字:“顶”。

我负责香港边缘的流量调度,手边只有几台刚迁完系统的高频口服务器、一对 ToR 交换机、一组没彻底压测过的 HAProxy。那一晚到第二天清晨,我把“大带宽 + 负载均衡”的方案从图纸摁成了现实。下面是我复盘后的完整部署教程 + 优化清单,包含我踩的坑和现场的解决过程。

一、目标与场景

  • 目标:让香港机房在亚洲跨境访问场景下,稳定承接20–30k QPS / 10–15 Gbps的尖峰流量,保证 P95 < 180 ms,零级联故障。
  • 特点:香港运营商多线 BGP、大带宽出海;客户批量任务产生短周期、突刺式并发;协议以 HTTPS 和 gRPC 为主,长短连接混合。
  • 策略:边界层采用VRRP 高可用 + HAProxy L4/L7 混合负载,后端Nginx 反向代理 + 应用池;配合Redis 集群做会话与限流,ProxySQL 做 MySQL 读写路由;用内核与网络栈调优把带宽彻底打透。

二、现场硬件与基础参数(落地可抄)

层级 型号/规格 关键参数 备注
边界 LB ×2 Dell R650 (裸金属) 2×Intel Xeon Silver, 128G RAM, 2×10GbE(Intel X710), NVMe 1.92T CentOS 7.9,bond LACP
应用节点 ×8 Dell R640 2×10GbE,64G RAM,NVMe 1T Node/Go 混部
Redis ×3 R650 10GbE,128G RAM cluster 模式
MySQL ×3 R650 10GbE,NVMe RAID1 半同步复制 + ProxySQL
交换机 ToR×2(MLAG) 48×10G + 6×100G 上行 sFlow 开启
运营商 BGP 多线 承诺带宽 5 Gbps,突发 20 Gbps 双上联(PCCW/NTT)
  • 入口 VIP:203.0.113.10(公网)
  • 内网段:10.66.0.0/16
  • OS:CentOS 7.9(要求:内核 3.10,network-scripts 在)

三、网络与内核:先把“水管”打通

3.1 网卡绑定(LACP)

/etc/sysconfig/network-scripts/ifcfg-bond0

DEVICE=bond0
TYPE=Bond
BONDING_MASTER=yes
IPADDR=10.66.1.10
PREFIX=24
GATEWAY=10.66.1.1
ONBOOT=yes
BOOTPROTO=none
BONDING_OPTS="mode=802.3ad miimon=100 lacp_rate=1 xmit_hash_policy=layer3+4"

/etc/sysconfig/network-scripts/ifcfg-eno1 / ifcfg-eno2

DEVICE=eno1
MASTER=bond0
SLAVE=yes
ONBOOT=yes
BOOTPROTO=none

坑 1:ToR 侧要聚合到同一 MLAG 逻辑端口,否则一半流量黑洞。xmit_hash_policy 必须 layer3+4 才能把多连接分散到两根链路。

3.2 内核与连接跟踪

/etc/sysctl.d/99-lb-tuning.conf

# 队列与端口
net.core.somaxconn = 65535
net.ipv4.ip_local_port_range = 10240 65535
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_max_syn_backlog = 262144
net.core.netdev_max_backlog = 250000

# 拥塞控制&缓冲
net.ipv4.tcp_congestion_control = bbr
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.ipv4.tcp_rmem = 4096 87380 134217728
net.ipv4.tcp_wmem = 4096 65536 134217728

# SYN 防护
net.ipv4.tcp_syncookies = 1

# conntrack:CentOS7 用 nf_conntrack
net.netfilter.nf_conntrack_max = 2621440
net.netfilter.nf_conntrack_tcp_timeout_established = 1800
net.netfilter.nf_conntrack_tcp_timeout_close_wait = 60

坑 2:nf_conntrack_max 不够会在尖峰直接丢连接。我用 2.6M 起步,按峰值 QPS×连接时长抓预估,每 1M 条目约占 64–128MB 内存。

3.3 中断亲和与 RPS/RFS

# 让中断均匀落到所有 CPU(示例,按实际 NIC 队列数写)
for i in /proc/irq/*/smp_affinity_list; do echo 0-15 > $i; done
echo 32768 > /proc/sys/net/core/rps_sock_flow_entries
for q in /sys/class/net/bond0/queues/rx-*; do echo 4096 > $q/rps_flow_cnt; done

四、边界高可用:Keepalived + VRRP

/etc/keepalived/keepalived.conf

vrrp_script chk_haproxy {
  script "pidof haproxy"
  interval 2
  weight 5
}

vrrp_instance VI_1 {
  state MASTER          # 另一台为 BACKUP
  interface bond0
  virtual_router_id 51
  priority 120
  advert_int 1
  authentication { auth_type PASS auth_pass s3cret! }
  virtual_ipaddress { 203.0.113.10/24 dev bond0 }
  track_script { chk_haproxy }
  garp_master_delay 1
  garp_master_refresh 10
}

坑 3:某些上游对 GARP 敏感,加 garp_master_refresh 持续通告 ARP,切主后更稳;同时在 ToR 侧关闭 ARP 抑制策略,否则 VIP 不生效。

五、负载均衡:HAProxy L4/L7 混合

5.1 systemd 与资源限制

/etc/systemd/system/haproxy.service.d/override.conf

[Service]
LimitNOFILE=1000000
TasksMax=infinity
CPUAffinity=0-15

5.2 haproxy.cfg(关键片段)
global
  log 127.0.0.1 local0
  daemon
  maxconn 500000
  nbthread 16
  tune.ssl.default-dh-param 2048
  tune.bufsize 32768
  # 如果要 TLS1.3,建议自编 OpenSSL 1.1.1 与 haproxy 2.6+

defaults
  log global
  mode http
  option httplog, dontlognull
  timeout connect 5s
  timeout client  60s
  timeout server  60s
  timeout http-keep-alive 10s
  maxconn 300000

# L4 直穿(gRPC/长连接)
frontend fe_tcp_443
  bind 203.0.113.10:443 ssl crt /etc/haproxy/certs/star.example.pem alpn h2,http/1.1
  mode tcp
  option tcplog
  default_backend bk_tcp_app

backend bk_tcp_app
  mode tcp
  balance source
  option tcp-check
  server app1 10.66.10.11:8443 check
  server app2 10.66.10.12:8443 check
  server app3 10.66.10.13:8443 check
  server app4 10.66.10.14:8443 check

# L7 HTTP(静态/REST)
frontend fe_http
  bind 203.0.113.10:80
  bind 203.0.113.10:443 ssl crt /etc/haproxy/certs/star.example.pem alpn h2,http/1.1
  http-response set-header X-Edge %H
  default_backend bk_web

backend bk_web
  balance uri whole
  option httpchk GET /healthz
  http-check expect string ok
  server web1 10.66.20.11:8080 check maxconn 30000
  server web2 10.66.20.12:8080 check maxconn 30000
  server web3 10.66.20.13:8080 check maxconn 30000
  server web4 10.66.20.14:8080 check maxconn 30000

优化点

  • gRPC/长连接优先 L4(mode tcp)转发,减少多余解析。
  • HTTP 侧 balance uri 把相同资源散到不同后端,配合Nginx 缓存更稳。
  • nbthread 对齐物理核,不要开 nbproc(多进程共享监听会复杂)。

六、应用层 Nginx & 后端池

应用节点跑 Nginx → 应用(Node/Go)。Nginx 负责静态/缓存、TLS 终结(若不在 LB)。

/etc/nginx/nginx.conf(片段)

worker_processes auto;
worker_rlimit_nofile 1000000;

events { worker_connections 81920; multi_accept on; }

http {
  sendfile on; tcp_nopush on; tcp_nodelay on;
  keepalive_timeout 20; keepalive_requests 10000;
  client_max_body_size 32m;

  # 压缩
  gzip on; gzip_comp_level 5; gzip_types text/plain application/json text/css application/javascript;

  # 缓存
  proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=STATIC:512m inactive=30m max_size=20g;

  upstream app_pool {
    least_conn;
    server 127.0.0.1:3000 max_fails=3 fail_timeout=5s;
  }

  server {
    listen 8080 http2;  # 内网
    location / {
      proxy_cache STATIC;
      proxy_pass http://app_pool;
      proxy_set_header Host $host;
      proxy_set_header X-Forwarded-For $remote_addr;
    }
    location =/healthz { return 200 "ok"; }
  }
}

坑 4:CentOS7 自带 OpenSSL 1.0.2 只到 TLS1.2。若你要 TLS1.3,就单独编译 Nginx + OpenSSL 1.1.1或放到 HAProxy(自编)层做 TLS 终结。

七、限流与会话:Redis Cluster

模式:3 主 3 从(生产至少 6 节点),slot 全分布。

用途:登录态 / 限流计数 / 幂等锁。

限流 Lua(令牌桶)示例(片段):

-- KEYS[1]=key, ARGV[1]=rate, ARGV[2]=burst, ARGV[3]=now(ms), ARGV[4]=cost
local rate = tonumber(ARGV[1])
local burst = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local cost = tonumber(ARGV[4])

local tokens = tonumber(redis.call('hget', KEYS[1], 't') or burst)
local last = tonumber(redis.call('hget', KEYS[1], 'l') or now)
tokens = math.min(burst, tokens + (now - last) * rate / 1000)
if tokens < cost then
  redis.call('hset', KEYS[1],'t',tokens,'l',now)
  return 0
else
  redis.call('hset', KEYS[1],'t',tokens-cost,'l',now)
  return 1
end

八、数据库:ProxySQL 路由 MySQL

/etc/proxysql.cnf(精简)

datadir="/var/lib/proxysql"
admin_variables = {
  admin_credentials="admin:admin"
}
mysql_variables = {
  threads=16
  max_connections=50000
  monitor_username="mon"
  monitor_password="monpass"
}
mysql_servers = (
  { address="10.66.30.11" , port=3306 , hostgroup=10, max_connections=2000, weight=1 }, -- 主
  { address="10.66.30.12" , port=3306 , hostgroup=20, max_connections=2000, weight=1 }, -- 从
  { address="10.66.30.13" , port=3306 , hostgroup=20, max_connections=2000, weight=1 }
)
mysql_query_rules = (
  { rule_id=1, active=1, match_pattern="^SELECT .*", destination_hostgroup=20, apply=1 },
  { rule_id=2, active=1, match_pattern=".*", destination_hostgroup=10, apply=1 }
)

坑 5:大客户批量导入时写放大显著,给主库的 innodb_log_file_size 与 io_capacity_max 适当提到 2048M/2000,NVMe 上差距肉眼可见。

九、边界防护与稳态

  • SYN 防护:tcp_syncookies=1 + HAProxy maxconn 限定 + haproxy stick-table 做 IP 级速率限制。
  • WAF:轻量可用 NAXSI / OpenResty,自定义几条黑名单 + 速率就够挡脚本。
  • 黑洞/清洗:和运营商约定好BGP 黑洞社区与紧急清洗通道联系人;把脚本写进 runbook。

HAProxy stick-table(速率):

backend bk_web
  stick-table type ip size 2m expire 10s store http_req_rate(10s)
  http-request track-sc0 src
  http-request deny if { sc_http_req_rate(0) gt 200 }  # 单 IP >200 r/s 拒

十、可观测与容量模型

  • 导出器:node_exporter、haproxy_exporter、redis_exporter、mysqld_exporter。
  • 关键 SLO:Edge 4xx/5xx 比例、握手失败率、P95、后端队列长度、conntrack 使用率、端口耗尽率。

容量:

  • 峰值 QPS 由**(LB maxconn × 后端 maxconn × 平均保持时长)** 决定上限。
  • 带宽上限受出方向为主,静态资源切对象存储/CDN 能明显降压。

十一、压测方法与结果(实测数据)

压测命令(示例 wrk)

wrk -t16 -c20000 -d180s --timeout 5s https://edge.example.com/api/batch

结果对比

阶段 峰值 QPS 出口带宽 P95 (ms) 5xx 备注
调优前 8,200 3.5 Gbps 410 1.6% conntrack 撑爆,Nginx 打不开文件句柄
BOND/LACP & sysctl 12,900 5.1 Gbps 260 0.7% 黑洞消失,丢包下降
HAProxy 分层 21,400 8.3 Gbps 185 0.2% L4 长连直穿,CPU 降 20%
Redis 限流 + 缓存 24,800 7.1 Gbps 160 0.1% 峰值更稳,带宽更“平”
最终(客户实战) 27,900 9.6 Gbps 155 0.06% 通过

十二、部署步骤清单(可清单施工)

  1. 机房/链路:确认 BGP 双上联与承诺带宽,压测 ToR 到 LB 的线速。
  2. OS 准备:CentOS 7.9,关闭 NUMA 自动平衡(BIOS/内核参数),安装 chrony 保证时钟一致。
  3. 网络:配置 bond0 + LACP;ToR 侧开 MLAG;开启 sFlow。
  4. 内核:落 sysctl,扩大 nofile(ulimit -n 1000000),配置 irq 亲和。
  5. Keepalived:VRRP 建 VIP;两台 LB 互为主备。
  6. HAProxy:区分 L4(gRPC) / L7(HTTP) 前端,开 nbthread,调 maxconn,开启 tcplog/httplog。
  7. Nginx:开启缓存、keepalive,调大 worker_connections;后端应用池接入。
  8. Redis:搭 cluster,接入限流 Lua、分布式锁;应用适配。
  9. ProxySQL+MySQL:读写分离,半同步复制;调 InnoDB 参数。
  10. 防护:SYN/速率限制、WAF 基线、黑洞预案。
  11. 监控告警:四类导出器 + SLO 仪表盘;设置端口耗尽/conntrack 使用率阈值。
  12. 压测 & 演练:wr k / vegeta 打到 120% 目标;拔一条上联验证 VRRP 切换。

十三、当场踩过的 8 个坑与解法

  1. conntrack 表爆:症状是 QPS 上不去、日志断崖;加大 nf_conntrack_max 并缩短 established。
  2. 半双工黑洞:LACP 一端没入 MLAG 口;改汇聚口并改 xmit_hash_policy=layer3+4。
  3. VIP 不接管:切主后上游缓存 ARP;增加 GARP 刷新且 ToR 关闭 ARP 抑制。
  4. TLS CPU 飙升:TLS1.3 + ECDHE 开销大;会话复用 & keepalive,如可能把 TLS 终结上移到更强的 LB。
  5. 端口耗尽:大量短连接;扩大 ip_local_port_range,并推动客户端复用(HTTP/2、gRPC)。
  6. 应用 FD 限制:EMFILE 报错;统一把 NOFILE 调到 1,000,000(systemd override + limits.conf)。
  7. 队列掉包:netdev_max_backlog 太小;加大并配合 RSS/RPS。
  8. 磁盘抖动:日志同步落盘阻塞;把访问日志打到 syslog/内存队列,后端异步收集。

十四、成本与扩展

  • 竖向扩容:LB 升 25G/100G 口更省事(前提 ToR/上联支持),CPU 选择更高频核。
  • 横向扩容:多对 LB + Anycast 或 DNS 加权分流到多 VIP;地域再加新加坡/东京作分担。
  • 降本:静态内容外置到对象存储 + CDN(香港边缘),把带宽“烫手山芋”递出去。

十五、应急演练片段(真实日志)

  • 22:13 切主:Keepalived[master] Transition to MASTER
  • 22:13:02 GARP 连发,ToR 学到新 MAC;HAProxy 新主 fe_http 活跃连接爬升 0→12k;旧主 5 秒内归零。
  • 22:14 告警恢复:Edge P95 from 420ms → 170ms,5xx < 0.2%。当时所有人齐刷刷在群里发了个“呼”。

十六、把“带宽”真的用起来

那晚之后,我把自己的 runbook 首行改成了:“先把水管打通,再谈算法。” 香港的好在于大带宽、好出口,但如果bond/LACP 没配稳、conntrack 过小、TLS 终结没分层,你永远用不满那条 10G。

第二天早上 7 点,客户技术负责人回了句“体验不错”,我从机房出来,口袋里装着通宵的咖啡券和一张手写的 TODO:

  •  接入 Anycast 做跨站分担
  •  把静态彻底切到对象存储
  •  压一版 eBPF 流量观测

如果你也要在香港扛并发,按上面的步骤抄过去,先稳住基础网络与内核,再把负载按协议分层。真要冲刺的那一刻,你会庆幸每一个“啰嗦”的调优都在。祝你把带宽吃干抹净,也把事故压在演练里。