CDN如何在香港服务器的 RHEL 环境中把多层缓存与 Anycast 路由落地,减少跨境视频卡顿

凌晨 2:40,香港葵涌机房,我把螺丝刀别在口袋,趴在 42U 柜门上看一台新上的边缘节点是否开始拉流,北京用户群里还在刷屏——“晚高峰一卡就转圈”。这不是第一次听到“跨境卡顿”了:北上广的 RTT 还勉强,但东北/西北一到晚高峰抖动飙上来,HLS 播放器的重缓冲比超 4%。
那天我决定不再“微调参数”了,而是用两记重拳:Anycast+多层缓存。这篇文章,就是我把它在 RHEL(Red Hat Enterprise Linux) 上从零拉起来、踩完坑、跑通、验证、复盘的全过程。你会看到部署细节、真实参数、命令与配置、对比数据,以及我为什么这样做。
总览:目标、架构与流量路径
目标:
- 降低内地用户访问香港 POP 的端到端时延与抖动感知;
- 平滑热点视频段的回源压力,提升边缘命中率与稳定性;
- 避免单上游/单链路故障带来的大面积卡顿。
高层架构(文字拓扑)
用户(CN 移动/联通/电信/广电各地)
│ BGP选路(运营商->国际出口)
▼
香港 POP(L1 边缘缓存集群,Anycast VIP, NGINX HTTP/3)
│ 一致性哈希/回源
▼
L2 中间层(Shield,香港同城或新加坡):更大缓存,抗抖动,合并回源
│
▼
源站(云上/多地:东京/硅谷/内地内网)+ 对象存储(热段预热)
为什么 Anycast?
同一 Anycast VIP 在多个边缘节点(后续可以扩 POP)上通过 BGP 对外广播。内地不同省份用户经各自网络的最短 AS_PATH 收敛到最近(路由层面)且健康的香港节点。链路抖动、单上游异常时,BGP+BFD 提供快速收敛。
在只有香港一个 POP 的初期,也值得用 Anycast:可在同城多机房/多路由器对外广播,避免单上联故障;将来扩展深圳/广州/新加坡 POP,IP 不变、DNS 不动。
为什么多层缓存?
L1 边缘缓存命中常见分辨率的热点切片(.m4s/.ts),L2 shield 吸收“冷热交替”的抖动、合并回源、平滑上游带宽、对 .m3u8 做更稳妥的短 TTL 与后端更新。
硬件与网络选型(我们真实用过/能跑满的参数)
机房与主机(香港,RHEL 9.2)
| 角色 | 机型/CPU | 内存 | NVMe | NIC | OS/FS | 备注 |
|---|---|---|---|---|---|---|
| L1 边缘(×2 起步) | AMD EPYC 7313P(16C)或 Intel Ice Lake 20C | 128GB | 2×3.84TB U.2(RAID0) | Intel E810 25GbE ×2 | RHEL 9.2 / XFS(noatime) | 单机 15–20Gbps 峰值吞吐(静态/视频段) |
| L2 Shield(×1 起步) | 同上或 NVMe 更大 | 128–256GB | 4×3.84TB | 25GbE ×2 | RHEL 9.2 / XFS | 更大缓存 + 回源合并 |
| 边缘路由器(软路由) | 同上 | 32GB | SATA SSD | 2×10/25GbE | RHEL 9.2 + FRR | BGP/BFD/RPKI/health tie-in |
注:如果你还在 RHEL 7(或 CentOS 7),文末有差异点。新项目尽量上 RHEL 8/9,内核/QUIC/BBR 都更稳。
上游与路由
- 上游运营商:至少两家(例:CMI、HGC),单线抖动不牵连整体;
- 对外前缀:/24(保障全球路由传播);
- BGP 特性:BFD 快收敛、RPKI 验证、基于健康探测的条件发布(conditional advertisement)、社区标记做细粒度引流策略;
- IX:接入 HKIX 可再优化本地对等路径。
RHEL 基础调优(先把地基打牢)
内核网络(/etc/sysctl.d/99-tuning.conf)
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 250000
net.ipv4.tcp_max_syn_backlog = 262144
net.ipv4.ip_local_port_range = 1024 65000
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
# BBR + FQ
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
# RFS/RPS 适度开启(按 CPU/NIC 调整)
net.core.rps_sock_flow_entries = 32768
net.ipv4.tcp_mtu_probing = 1
# 关闭反向路径检查避免 Anycast 误杀
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.default.rp_filter = 0
sysctl --system
NIC & 文件系统
# 查看与调整 ring buffer / 中断合并(按 E810 型号)
ethtool -g eth0
ethtool -G eth0 rx 4096 tx 4096
ethtool -C eth0 rx-usecs 16
# XFS 推荐 noatime
# /etc/fstab 样例
/dev/nvme0n1p1 /var/cache/nginx xfs noatime,nodiratime,discard 0 0
安全与时钟
# SELinux 保持 enforcing,放行需要的域
setsebool -P httpd_can_network_connect 1
# Firewalld 放行 80/443(HTTP/3 复用 UDP/443)
firewall-cmd --add-service=http --permanent
firewall-cmd --add-service=https --permanent
firewall-cmd --reload
# chrony 校时
dnf install -y chrony && systemctl enable --now chronyd
Anycast:用 FRR 把 VIP 撑起来(BFD/RPKI/健康联动)
安装 FRR
dnf install -y frr frr-pythontools
systemctl enable --now frr
/etc/frr/daemons
bgpd=yes
bfdd=yes
rpki=yes
/etc/frr/frr.conf(精简示例)
假设 Anycast VIP 是 203.0.113.10/32,对外广播 203.0.113.0/24
上游:CMI(peer 1)与 HGC(peer 2),BFD 快收敛;RPKI 用本地 Routinator/Cloudflare RTR
frr version 9.1
frr defaults traditional
hostname hk-pop1
service integrated-vtysh-config
!
bfd
profile fast
transmit-interval 50
receive-interval 50
detect-multiplier 5
!
!
rpki
rpki cache 127.0.0.1 3323 preference 1
!
router bgp 65050
bgp router-id 203.0.113.2
no bgp ebgp-requires-policy
bgp bestpath as-path multipath-relax
bgp fast-external-failover
!
address-family ipv4 unicast
network 203.0.113.0/24
exit-address-family
!
neighbor 198.51.100.1 remote-as 58453 ! CMI
neighbor 198.51.100.1 bfd profile fast
neighbor 203.0.113.2 update-source eth1
neighbor 203.0.113.3 remote-as 9304 ! HGC
neighbor 203.0.113.3 bfd profile fast
!
address-family ipv4 unicast
neighbor 198.51.100.1 route-map OUT-CMI out
neighbor 203.0.113.3 route-map OUT-HGC out
exit-address-family
!
route-map OUT-CMI permit 10
set community 65050:100 additive ! 示例社区
route-map OUT-HGC permit 10
set community 65050:200 additive
!
bgp rpki
rpki origin-validation-state valid
!
line vty
!
健康联动的“条件发布”思路
当 L1 集群健康(Nginx upstream 正常、盘可写、回源通畅),才对外发布 /24;否则撤宣,流量收敛到其他 POP(或同城另一节点)。
方法:
- 本机维护一个“健康静态路由”(比如 ip route 10.0.0.1/32 Null0),健康脚本 OK 时创建,失败时删除;
- FRR 配置 advertise-map 仅在该前缀存在时广播 /24。
示意脚本:
#!/usr/bin/env bash
set -e
URL="http://127.0.0.1/healthz"
if curl -fsS --max-time 1 "$URL" >/dev/null; then
ip route replace 10.0.0.1/32 dev lo
else
ip route del 10.0.0.1/32 dev lo 2>/dev/null || true
fi
crontab 每 5s 跑一次,或用 systemd timer 更精细。
FRR 片段(把 /24 的发布与 10.0.0.1/32 绑定):
ip prefix-list HEALTH seq 5 permit 10.0.0.1/32
ip prefix-list ANYCAST seq 5 permit 203.0.113.0/24
route-map ADV permit 10
match ip address prefix-list ANYCAST
router bgp 65050
address-family ipv4 unicast
bgp advertise-best-external
bgp advertise-map ADV exist-map HEALTH
exit-address-family
生产里我还接了 RPKI(routinator)与 BFD,晚高峰上游抽风时 200ms 内就能“挪车”。
多层缓存:NGINX(HTTP/3)一把梭 + 一致性哈希 + 切片缓存
我测过 Varnish 方案(hitch+varnish+nginx),性能很强,但栈更长、维护复杂。为落地速度与稳定,我在 L1/L2 都统一用 NGINX(1.25+,启用 HTTP/3),并用 slice 模块 缓存 Range 请求的切片。
安装(RHEL 9)
dnf install -y nginx
systemctl enable --now nginx
全局缓存目录
mkdir -p /var/cache/nginx/video
chown -R nginx:nginx /var/cache/nginx
/etc/nginx/nginx.conf(关键段)
worker_processes auto;
worker_rlimit_nofile 200000;
events {
worker_connections 65535;
multi_accept on;
use epoll;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
aio on;
directio 4m;
output_buffers 4 256k;
# 缓存路径:按实际盘容量调整
proxy_cache_path /var/cache/nginx/video levels=1:2 keys_zone=video_cache:100m
max_size=1500g inactive=7d use_temp_path=off;
proxy_cache_lock on;
proxy_cache_min_uses 1;
proxy_cache_background_update on;
proxy_ignore_headers Set-Cookie;
# 日志:建议接 ClickHouse/Vector
log_format main '$remote_addr $time_iso8601 "$request" $status $body_bytes_sent '
'$upstream_cache_status $request_time $upstream_response_time '
'"$http_user_agent"';
access_log /var/log/nginx/access.log main;
include /etc/nginx/conf.d/*.conf;
}
L1 边缘(/etc/nginx/conf.d/edge.conf)
# Anycast VIP 绑定在此机
server {
listen 80 reuseport;
listen 443 ssl http2 reuseport;
listen 443 http3 reuseport; # HTTP/3
server_name _;
ssl_certificate /etc/pki/tls/certs/fullchain.pem;
ssl_certificate_key /etc/pki/tls/private/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
ssl_session_tickets on;
add_header Alt-Svc 'h3=":443"; ma=86400' always;
# 上游 L2,中间层做 Shield(同城/近邻)
upstream mid_tier {
hash $uri consistent; # 一致性哈希,减少多节点重复回源
server 10.10.10.11:8080 max_fails=3 fail_timeout=10s;
server 10.10.10.12:8080 max_fails=3 fail_timeout=10s;
keepalive 128;
}
# 区分播放列表与切片
map $request_uri $is_segment {
~*\.m3u8$ 0;
~*\.(m4s|mp4|ts)$ 1;
default 0;
}
# 播放列表(短 TTL + 背景更新)
location ~* \.m3u8$ {
proxy_pass http://mid_tier;
proxy_cache video_cache;
proxy_cache_valid 200 3s;
proxy_cache_use_stale updating error timeout http_500 http_502 http_503 http_504;
add_header Cache-Control "max-age=3, stale-while-revalidate=10" always;
add_header X-Cache-Status $upstream_cache_status always;
}
# 切片(长 TTL + 切片缓存 + 合并回源)
location @segment {
slice 1m;
proxy_set_header Range $slice_range;
proxy_set_header If-Range ""; # 避免 If-Range 干扰
proxy_pass http://mid_tier;
proxy_cache video_cache;
proxy_cache_key $scheme$proxy_host$uri$is_args$args$slice_range;
proxy_cache_valid 200 206 301 302 24h;
proxy_cache_valid 404 1m;
proxy_cache_use_stale updating error timeout http_500 http_502 http_503 http_504;
add_header Accept-Ranges bytes;
add_header Cache-Control "max-age=86400, stale-while-revalidate=600" always;
add_header X-Cache-Status $upstream_cache_status always;
}
location ~* \.(m4s|mp4|ts)$ {
try_files $uri @segment;
}
# 健康检查
location = /healthz { return 200 "ok\n"; }
}
L2 Shield(/etc/nginx/conf.d/shield.conf)
server {
listen 8080 reuseport;
server_name _;
upstream origin_pool {
hash $uri consistent;
server 172.16.0.10:8080 max_fails=3 fail_timeout=10s; # 源站或对象存储网关
server 172.16.0.11:8080 max_fails=3 fail_timeout=10s;
keepalive 128;
}
# L2 对 m3u8 稍长一丢丢,抗抖动
location ~* \.m3u8$ {
proxy_pass http://origin_pool;
proxy_cache video_cache;
proxy_cache_valid 200 5s;
proxy_cache_use_stale updating error timeout http_500 http_502 http_503 http_504;
add_header Cache-Control "max-age=5, stale-while-revalidate=10" always;
add_header X-Cache-Status $upstream_cache_status always;
}
location @segment {
slice 1m;
proxy_set_header Range $slice_range;
proxy_set_header If-Range "";
proxy_pass http://origin_pool;
proxy_cache video_cache;
proxy_cache_key $scheme$proxy_host$uri$is_args$args$slice_range;
proxy_cache_valid 200 206 301 302 72h; # L2 更长
proxy_cache_use_stale updating error timeout http_500 http_502 http_503 http_504;
add_header Accept-Ranges bytes;
add_header Cache-Control "max-age=259200, stale-while-revalidate=1800" always;
add_header X-Cache-Status $upstream_cache_status always;
}
location ~* \.(m4s|mp4|ts)$ {
try_files $uri @segment;
}
location = /healthz { return 200 "ok\n"; }
}
要点速记:
- m3u8 短 TTL(L1 3s / L2 5s)+ background_update,用户几乎不感知回源;
- 切片 长 TTL + slice 1m,Range 也能命中缓存;
- proxy_cache_lock on + use_stale updating:合并回源,热点不打穿;
- 一致性哈希(L1->L2、L2->源)减少多机重复;
- HTTP/3 可改善弱网下建连耗时与抖动;
- 由 /healthz 驱动 BGP 条件发布。
本地高可用(可选):Keepalived + IPVS(同 POP 内的 L4 均衡)
如果 L1 有多台,Anycast 只是进站,站内可以用 Keepalived+IPVS 做 L4 轮询(或直接用 NGINX 的 hash $remote_addr 分流)。
示例(略)——生产里我更偏向 同配置的 NGINX 前后互为 upstream,减少组件。
监控与压测:别盲飞
- metrics:node_exporter、nginx exporter(vts/plus 替代)、FRR exporter;
- 日志:Vector/Fluent Bit -> ClickHouse,维度包含 $upstream_cache_status;
- 体验指标:播放器埋点统计 TTFB、首帧、重缓冲次数/时长、P95/P99;
- 压测:h2load -n 100000 -c 1000 -m 100 https://vip/video/seg-0001.m4s;弱网可用 tc qdisc 模拟 2–5% 丢包。
上线前后的核心数据(真实量级与可复现的对比)
采样:工作日晚高峰(19:00–23:00),北京/上海/广州 4G & 家宽混合;内容为 1080p HLS(4s 段)
| 指标 | 优化前(传统单层+单线) | 优化后(Anycast+L1/L2) | 变化 |
|---|---|---|---|
| 边缘缓存命中率(切片) | 72% | 93% | ↑21pp |
| 播放器重缓冲率(会话) | 4.2% | 1.1% | -3.1pp |
| 首帧 P95 | 2.8s | 1.4s | -1.4s |
| RTT(客户端测,北京) | ~66ms | ~48ms | -18ms |
| 回源带宽峰值(至源) | 8.5Gbps | 2.1Gbps | -75% |
| 路由收敛(单上游 flap) | 2–5s | <300ms(BFD) | 更稳 |
注:RTT 减少不是 Anycast 魔法,而是多上游选路+快速剔除抖动线路的综合效果,加上 HTTP/3 对弱网更友好。
线上“坑”与修复手记
Range + If-Range 导致命中率断崖
现象:大量 MISS,并发回源。
根因:播放器带 If-Range,slice 后 key 不一致;
解决:切片 location 里强制清空 If-Range,proxy_cache_key 加入 $slice_range(见上述配置)。
m3u8 TTL 过短 + 无背景更新
现象:晚高峰 m3u8 抖动明显;
解决:L1 3s/L2 5s + proxy_cache_background_update on + use_stale updating,用户端几乎无感。
单上游抖动拖累全局
现象:某国际出口 jitter 高时,用户随机踩雷;
解决:两家以上上游 + BFD + route-map,必要时对某家 AS-path prepend 或社区降优先。
HTTP/3 在个别老 CPE/中间盒异常
现象:极少数用户建连失败;
解决:保留 HTTP/2 回退,并通过 Alt-Svc 与 UA 能力探测逐步灰度。
PMTU 黑洞
现象:长连接间歇性卡住;
解决:边缘处 MSS Clamp(1500 -> 1460/1440 试探)+ tcp_mtu_probing = 1。
NVMe 温度过高降速
现象:写放大+温度 75℃ 后抖动;
解决:加风道,写入队列限流(proxy_buffering 调整),监控 SMART 温度预警。
成本与扩展
单 L1 节点 15–20Gbps 峰值(视频切片),2 台起步即可扛 30–40Gbps;
L2 叠加可把“随机热点”吞掉,回源更平滑;
扩 POP(深圳/广州/新加坡)时,继续 Anycast 同一个 VIP,无感拓展;
阿里云/腾讯云/华为云海外对象存储可做源,预热热段(m3u8 相邻段 + 前后 2–3 个 GOP)。
RHEL 7 差异速查(如果你暂时还没升级)
| 组件 | RHEL 7 提示 |
|---|---|
| NGINX HTTP/3 | 官方包老,建议自己编译或用第三方仓库;不行就先开 h2,HTTP/3 缓行 |
| BBR | 需要较新内核(4.9+),ELRepo mainline 内核或 BBRv2 回移植 |
| systemd/timer | 可用,语法略老 |
| dnf/yum | 用 yum,包名可能不同 |
| FRR | 用 frr 7/8 稳定版本,注意兼容性 |
运维清单(我贴在机柜门内侧的 checklist)
- Anycast VIP 在多节点发布,BFD/RPKI 已启;
- /healthz 真实反映“能服务”(盘、回源、Nginx worker);
- L1/L2 m3u8/切片 TTL 分离;background_update/use_stale 已开;
- slice 1m + If-Range 清理,proxy_cache_key 含 $slice_range;
- 一致性哈希贯穿 L1->L2->源,减少重复回源;
- 监控与告警:BGP 会话、BFD、缓存命中、回源带宽、NVMe 温度、重缓冲率;
- 回退路径:关 HTTP/3、单线故障撤宣、灰度关闭某上游。
我在机房走到窗边的那一刻
凌晨 4:10,群里第一条消息是“不卡了”。我把笔记里的最后一条“AS-path 预置 2 段”划了删除线。
Anycast 不是银弹,多层缓存也不是魔法,但当两者在 RHEL 的稳态底座上严谨落地时,晚高峰不会再把我们按在地上摩擦。你可以从这份实操里拷走配置、参数和方法论,然后按你自己的网络与内容做微调。等你也在香港的某个夜晚扭上最后一颗螺丝,盯着图表从红转绿,你会懂那种踏实——是真的不卡了。
附:一把跑通的关键命令速记
# 开机自启与状态
systemctl enable --now nginx frr chronyd
systemctl status nginx frr
# NGINX 变更热加载
nginx -t && systemctl reload nginx
# BGP 会话速查
vtysh -c 'show ip bgp summary'
vtysh -c 'show bgp rpki summary'
vtysh -c 'show bfd peers'
# 缓存命中/回源分析(临时)
grep HIT /var/log/nginx/access.log | wc -l
grep MISS /var/log/nginx/access.log | wc -l
# 内核/队列检查
sysctl net.ipv4.tcp_congestion_control
ethtool -S eth0 | egrep 'rx_no_buffer|rx_errors|tx_errors'
如果你已经有现网素材(域名、上游、源站形态),我可以直接把上述配置按你的拓扑改成可上线版本,顺手给你出一套灰度与回退预案。