
凌晨 2:40,我在香港葵涌机房的走廊盯着手机上那条曲线:4G/5G 用户的 TTFB 在 780–950ms 上下飘,促销页还没开抢,心里已经发虚。机柜里是我们一台独立香港物理机,当晚的目标只有一个:把移动端首包打到 400ms 以内。
HTTP/2 我们早就上了,TLS1.3 也跑了,但移动网络的抖动和 TCP 层面的队头阻塞还是会恶心人。于是这晚我打算把 **HTTP/3(QUIC, UDP)**带上战场——更快建立连接,绕开 TCP 的那些老毛病,还顺手把一堆内核和内存队列调好。
环境与硬件(实配)
| 类别 | 参数 |
|---|---|
| 机房 | 香港葵涌(运营商/安防就不点名了) |
| 形态 | 1U 独立服务器(非云) |
| CPU | AMD EPYC 7402P(24C/48T) |
| 内存 | 64GB ECC |
| 磁盘 | 2 × 1.92TB NVMe(mdadm RAID1) |
| 网卡 | Intel X710 10GbE(上联 1Gbps Commit) |
| OS(起步) | CentOS 7.9(最小化)【注意:已 EOL】 |
| OS(落地) | AlmaLinux 9.3(后文解释为啥从 7 迁 9) |
| Web | Nginx(QUIC 分支编译)/ 可选 Caddy 2.8+(原生 H3) |
| 证书 | ECDSA P-256(Let’s Encrypt / acme.sh) |
| 反代/CDN | 直源+可选 Cloudflare(只做边缘 H3、不回源 H3) |
为什么从 CentOS 7 迁到 AlmaLinux 9?
两点:内核(CentOS 7 的 3.10 没有现代 UDP GSO/GRO,QUIC 性能天花板低)和 系统生命周期。我先在 CentOS 7 用 ELRepo 升到 kernel-ml 验证过能跑,但最终为了稳定和维护成本,还是切到 AlmaLinux 9。下面我把 两条路线都写出来:
- A 路线(能用就上):CentOS 7 + ELRepo kernel-ml + devtoolset + Nginx/QUIC 编译
- B 路线(推荐长期):AlmaLinux 9 / Debian 12 + Nginx/QUIC 或 Caddy
一、方案对比与选择
HTTP/2 → HTTP/3 的价值点(移动端)
- 减少握手 RTT(TLS1.3 + QUIC 合并握手,复用 0-RTT 可进一步降 TTFB)
- 避免 TCP 层面队头阻塞(QUIC 在 UDP 之上、流级别重传)
- 更好的丢包场景体验(移动网络典型问题)
三种落地方式
- Caddy 2.8+:开箱带 HTTP/3,配置极简,适合“今晚就要上线”。
- Nginx + QUIC(quiche/BoringSSL 编译):可控性最强,企业内多模块依赖时推荐。
- CDN 边缘开 H3,回源 H2/H1:对回源链路要求低,但端到端 H3 体验略打折。
- 我最终选的是 2)Nginx + QUIC做主线,1)Caddy作为回退方案,3)CDN作兜底。
二、A 路线(CentOS 7 保守可用版)
仅当你暂时改不了系统版本。跑得动,但我还是建议尽快迁到 9/12 系列。
1. 升级内核 & 编译工具
# 1) 加 ELRepo,装 kernel-ml(示例为 6.x)
yum install -y https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm
yum --enablerepo=elrepo-kernel install -y kernel-ml
grub2-set-default 0 && reboot
# 2) 安装 devtoolset(GCC 11/12)与依赖
yum install -y centos-release-scl
yum install -y devtoolset-11 devtoolset-11-gcc-c++ git cmake golang \
pcre-devel zlib-devel libuuid-devel
scl enable devtoolset-11 bash
2. 系统内核与 UDP 缓冲
cat >/etc/sysctl.d/99-quic.conf <<'EOF'
net.core.rmem_max=67108864
net.core.wmem_max=67108864
net.core.rmem_default=262144
net.core.wmem_default=262144
net.ipv4.udp_mem=8388608 16777216 33554432
net.ipv4.udp_rmem_min=8192
net.ipv4.udp_wmem_min=8192
net.core.netdev_max_backlog=250000
fs.file-max=1048576
net.ipv4.ip_local_port_range=10240 65000
net.core.default_qdisc=fq
net.ipv4.tcp_congestion_control=bbr # 仅影响 TCP 回退流量
net.netfilter.nf_conntrack_max=1048576
net.netfilter.nf_conntrack_udp_timeout=60
net.netfilter.nf_conntrack_udp_timeout_stream=180
EOF
sysctl --system
ulimit -n 1048576
3. 编译 Nginx(QUIC + BoringSSL)
# 取源码
cd /usr/local/src
git clone --depth=1 https://github.com/google/boringssl.git
git clone --depth=1 https://github.com/cloudflare/quiche.git
git clone --depth=1 https://github.com/nginx/nginx.git -b release-1.25.5
# 打补丁(以 quiche 自带的 nginx 补丁为例)
cd nginx
patch -p01 < ../quiche/extras/nginx/nginx-1.25.patch
# BoringSSL 头与库路径
BORINGSSL=/usr/local/src/boringssl
# 配置并编译(注意开启 http_v3_module)
./auto/configure \
--prefix=/etc/nginx \
--sbin-path=/usr/sbin/nginx \
--with-http_v3_module \
--with-http_v2_module \
--with-http_ssl_module \
--with-pcre --with-compat \
--with-cc-opt="-I${BORINGSSL}/include" \
--with-ld-opt="-L${BORINGSSL}/build/ssl -L${BORINGSSL}/build/crypto -Wl,-rpath,${BORINGSSL}/build/ssl:${BORINGSSL}/build/crypto"
make -j$(nproc) && make install
useradd -r -s /sbin/nologin nginx
4. 证书(ECDSA)
curl https://get.acme.sh | sh
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt
~/.acme.sh/acme.sh --issue -d shop.example.hk --nginx --keylength ec-256
mkdir -p /etc/nginx/ssl
~/.acme.sh/acme.sh --install-cert -d shop.example.hk \
--key-file /etc/nginx/ssl/privkey.key \
--fullchain-file /etc/nginx/ssl/fullchain.cer \
--reloadcmd "nginx -s reload"
5. Nginx 配置(HTTP/3 开启)
worker_processes auto;
worker_rlimit_nofile 1048576;
events { worker_connections 65535; use epoll; }
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
aio threads;
# Brotli/Gzip 二选一或共存(按模块实际情况)
# gzip on; gzip_comp_level 6; gzip_types text/css application/javascript application/json image/svg+xml;
server {
listen 443 ssl http2 reuseport;
listen 443 quic reuseport; # 关键:UDP 443
server_name shop.example.hk;
# TLS1.3 + ECDSA
ssl_certificate /etc/nginx/ssl/fullchain.cer;
ssl_certificate_key /etc/nginx/ssl/privkey.key;
ssl_protocols TLSv1.3;
ssl_early_data on; # 仅对幂等 GET 开;POST 风险详见下文
# 声明 H3 能力,促使浏览器升级
add_header Alt-Svc 'h3=":443"; ma=86400' always;
add_header QUIC-Status $quic always;
root /data/www;
index index.html index.htm;
location / {
# 缓存/压缩/静态优化略……
try_files $uri /index.html;
}
# API/静态可按需细分
}
server {
listen 80;
server_name shop.example.hk;
return 301 https://$host$request_uri;
}
}
6. 防火墙/安全组(开放 UDP 443)
# firewalld
firewall-cmd --add-port=443/udp --permanent
firewall-cmd --add-service=https --permanent
firewall-cmd --reload
# 或 iptables -A INPUT -p udp --dport 443 -j ACCEPT
7. 验证
# 本机
curl -I --http3 https://shop.example.hk
# 看到 HTTP/3 + Alt-Svc、响应头有 QUIC-Status=??? 即可
# 压测(示例)
h2load -n 10000 -c 100 -m 10 https://shop.example.hk/ --h3
三、B 路线(推荐):AlmaLinux 9 / Debian 12
迁移到新系统后,内核自带 UDP GSO/GRO、编译链也省心,性能稳定性都更香。
1. 直接用 Caddy(最快落地)
dnf install -y caddy
# /etc/caddy/Caddyfile
shop.example.hk {
root * /data/www
file_server
encode zstd gzip
tls you@example.com # 自动签证书
# HTTP/3 默认开启;可加入 header 暴露状态
header {
Alt-Svc "h3=\":443\"; ma=86400"
}
}
systemctl enable --now caddy
两分钟见效,移动端 TTFB 大概率直接减半。缺点是复杂的 Nginx 模块生态迁移成本。
2. 继续用 Nginx(同上“编译 Nginx QUIC”步骤)
Alma/Debian 上只要把依赖换成 dnf/yum/apt 对应包名,流程一致。其余 sysctl、防火墙同理。
四、HTTP/3 提升 TTFB 的组合拳(关键优化)
1) UDP 队列与内核网络
- net.core.netdev_max_backlog=250000:高并发避免丢包。
- rmem/wmem/udp_mem:保证 UDP 报文缓存峰值不被挤爆。
- reuseport:在 listen 443 quic reuseport; 开多个队列,缓解热点。
2) TLS 与 0-RTT
- 0-RTT 只对 幂等 GET 放行(如静态资源、搜索建议),千万别给 POST/下单 开,避免重放风险。
- ECDSA 证书 + TLS1.3,握手体积更小。
3) 静态资源与首包路径
- HTML 首包瘦身:把非关键脚本延迟/异步、关键 CSS 内联(<10KB),可再配合 103 Early Hints 或 preload。
- 压缩:移动端上 zstd/gzip 对 HTML/JSON 非常划算,JS/CSS 亦然。
- 缓存:合理的 cache-control + etag + stale-while-revalidate。
4) 观测与落地核验
- Nginx 变量 $quic 出个 header(上面 QUIC-Status),在前端埋点区分 H2/H3。
- 抓包:tcpdump -i eth0 udp port 443 看 QUIC 初始握手。
- 仪表盘:node_exporter + nginx_exporter,分网络/UA 看 P50/P90 TTFB。
五、真实数据(上线前后对比)
采样窗口:香港源站直连,广州/深圳/上海/北京 4G/5G 用户;数据来自前端 RUM + 辅以 h2load 验证。样本量各区 5k–12k。
| 场景 | 协议 | 首次访问 TTFB P50 | 首次访问 TTFB P90 | 复访(会话内)P50 |
|---|---|---|---|---|
| 广州 4G | HTTP/2 | 780ms | 1180ms | 610ms |
| 广州 4G | HTTP/3 | 420ms | 680ms | 310ms |
| 深圳 5G | HTTP/2 | 600ms | 920ms | 470ms |
| 深圳 5G | HTTP/3 | 320ms | 520ms | 220ms |
| 上海 5G | HTTP/2 | 640ms | 980ms | 500ms |
| 上海 5G | HTTP/3 | 360ms | 560ms | 240ms |
| 北京 4G | HTTP/2 | 880ms | 1350ms | 710ms |
| 北京 4G | HTTP/3 | 470ms | 760ms | 340ms |
结论:P50 下降 ~40–50%,P90 下降 ~35%;复访(TLS/QUIC session 可复用)进一步受益。个别小区/路由 UDP 被限流,会优雅回退到 H2(见 Alt-Svc 机制)。
六、部署过程中的坑 & 现场解法
UDP 443 被安防/云安全组拦了
现场症状:浏览器一直 H2,$quic 总是空。
解法:同时开系统防火墙与上游安全组的 UDP/443,不少同学只放了 TCP 443。
CentOS 7 编译各种红字
BoringSSL 版本、GCC 太老、http_v3_module not found 等。
解法:devtoolset-11/12 起步,kernel-ml 提供现代 UDP 能力;不行就直接 B 路线。
conntrack 爆了(高并发 UDP)
症状:间歇 5xx、延迟阶梯型上升。
解法:调大 nf_conntrack_max,优化 *_udp_timeout*;Nginx 层用 reuseport + 合理 worker 数(CPU/2~CPU)。
CDN 回源没 H3
症状:边缘是 H3,但回源 H2,端到端并不纯。
取舍:业务优先的话 允许 CDN 终止 H3,回源保持 H2 即可,先把用户侧体验打下来,回源以后再演进。
0-RTT 误开到下单接口
一度出现重复下单,庆幸有幂等校验。
规则:只对 GET 幂等开,POST 统一关。
MTU/分片导致偶发握手失败
移动网络侧 MTU 不一致,偶有首包丢。
调整:观测后端路由器/隧道开销情况,如需可下发更保守的 quic 包尺寸(不同栈能力不同,这里不赘述),或启用路径 MTU 探测。
七、灰度与回滚剧本(我线上真的会这样做)
灰度:50% 流量按 UA/地区或 Cookie 打到 H3,观测 1 小时。
回滚:
- Nginx:把 listen 443 quic 注释+reload;
- Caddy 备用:systemctl start caddy 并将 443 切向 Caddy(或反之)。
- 告警:以 P50/P90 TTFB、5xx、活动连接数、UDP 丢包率做阈值,灰度期间加严。
八、Checklist(上线前 5 分钟快查)
- 证书:有效期 > 60 天,ECDSA/256 优先
- UDP 443:系统防火墙 + 安全组都放通
- Alt-Svc 生效:浏览器能收到 h3=":443"
- $quic 观测:前端打点区分协议占比
- sysctl:rmem/wmem/udp_mem/backlog/conntrack 配置落地
- 压测:h2load --h3 峰值 RPS 与 95 线不抖
- 0-RTT:只对白名单 GET 路径开启
- 回滚:一条命令能退回 H2
九、附:最短落地(真·两分钟 Caddy 版)
dnf install -y caddy
cat >/etc/caddy/Caddyfile <<'EOF'
shop.example.hk {
root * /data/www
file_server
encode zstd gzip
tls you@example.com
header {
Alt-Svc "h3=\":443\"; ma=86400"
QUIC-Status "{http.request.proto}"
}
}
EOF
systemctl enable --now caddy
凌晨 5:10,我从机房出来走到天桥上,手机里 P50 已经稳在 300ms 级,报警没再响。二十多分钟后,首页流量开始涌入,H3 占比一路上扬,购物车页的转化率也顺着 TTFB 下降而爬升。
这类优化没有奇技淫巧,都是一层一层把链路和参数抠干净:系统、内核、UDP 队列、握手、静态资源、观测与灰度,每一环都靠谱,HTTP/3 自然能把移动端的体验拉起来。
如果你也在香港机房里对着一台热气腾腾的服务器发愁,不妨按这套剧本走一遍;哪怕只用 Caddy 先上车,也能先把 TTBF 打下来,再慢慢把细节打磨到极致