上一篇 下一篇 分享链接 返回 返回顶部

如何在香港服务器(Ubuntu)启用 QUIC / HTTP/3,把跨境电商站的响应“拉回正轨”

发布人:Minchunlin 发布时间:2025-09-05 10:06 阅读量:745


这是我在屯门机房一个加班夜的完整记录:风墙呼呼,过道 28℃ 的热岛效应在机柜门口打着旋。前端同事在 IM 里敲来:“广东移动 4G 上结账页转圈 3~5 秒,偶发直接超时。”
我看了一眼流量曲线——晚高峰从华南、东南亚涌过来,RTT 摇摆、重传攀升。那一刻我基本确定:该把 QUIC / HTTP/3 真正顶到线上了。

我们的现场与目标

业务目标: 降低跨境(内地 ↔ 香港、东南亚 ↔ 香港)用户的首包时间(TTFB)与页面交互抖动,提升移动弱网(丢包 1%~3%)下的下单成功率。

硬件&系统:

配置
机房 香港,双上联(NTT + PCCW),单机房演进为主
服务器 1U,AMD EPYC 7443P(24C)、128GB RAM
存储 2× 1.92TB NVMe(Samsung PM9A3,RAID1)
网卡 Intel X710 10GbE(支持 RSS、RPS、GRO/GSO)
OS Ubuntu 24.04 LTS(内核 6.8 HWE)
反向代理 Nginx 1.27.x(编译启用 HTTP/3/QUIC
应用 Node.js / PHP 混合,走 upstream

重要认知先说在前面:Linux 主线并没有“QUIC 协议内核模块”。
QUIC 是用户态协议(跑在 UDP 之上),真正要“启用”的是:
1)用户态的 QUIC/HTTP3 服务器(Nginx/Caddy/HAProxy/Envoy 等),
2)内核的 UDP 加速能力(UDP GRO/GSO、队列调度、缓冲参数、conntrack 等)。
我下面的做法就是围绕这两点,确保端口开、栈通畅、实现靠谱、参数合适。

路线图(5 步到位)

  1. 确认 NIC/内核支持 UDP GRO/GSO,打开必要 offload。
  2. 系统网络栈调优(sysctl/ethtool),给 QUIC 通道“腾路”。
  3. 编译并部署带 QUIC 的 Nginx(链接 quictls OpenSSL QUIC 分支)。
  4. 开放 UDP 443(别被防火墙/云厂商安全组卡脖子)。
  5. 验证、观测与回滚阈值(curl/h2load、日志标记、灰度与熔断)。

1)NIC 与内核能力自检

先看内核版本与网卡 offload 能力:

uname -a
# 期待:Linux ... 6.8.x ...

ethtool -k eth0 | egrep 'gro|gso|rx-udp|tx-udp'
# 关注:generic-receive-offload (GRO) / generic-segmentation-offload (GSO)
#       rx-udp-gro-forwarding / tx-udp-segmentation 是否 available/on

如果看到 rx-udp-gro-forwarding: off [fixed] 也别慌,很多时候 GRO/GSO 的大头是 generic(不依赖硬件),只要 generic-receive-offload: on、generic-segmentation-offload: on 就能让 QUIC 受益。

谨慎地开启你需要的开关(线上要先在影子/灰度机器做):

sudo ethtool -K eth0 gro on gso on
# 某些驱动还支持:
sudo ethtool -K eth0 rx-udp-gro-forwarding on 2>/dev/null || true
sudo ethtool -K eth0 tx-udp-segmentation on 2>/dev/null || true

2)系统网络栈调优(针对 QUIC/UDP)

sysctl:(在 /etc/sysctl.d/99-quic.conf,然后 sudo sysctl --system)

# 队列与缓冲
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.core.rmem_default = 8388608
net.core.wmem_default = 8388608
net.core.netdev_max_backlog = 250000

# UDP 专项
net.ipv4.udp_mem = 262144 524288 1048576
net.ipv4.udp_rmem_min = 4096
net.ipv4.udp_wmem_min = 4096

# 避免过度分片 & 允许路径 MTU 探测
net.ipv4.ip_no_pmtu_disc = 0

# 调度器与拥塞控制
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr   # 对 TCP 回退通道仍有益(HTTP/2/1.1)

解释:QUIC 的拥塞控制与速率控制主要在用户态实现,但fq 调度器的 pacing 能改善整体队列行为;
UDP 缓冲放大可缓解高并发/突发时的丢包与应用阻塞。

RSS/RPS:(多队列/多核并行能抵“单核打满”)

# 查看队列数
ethtool -l eth0
# 打开 RPS/RFS(示例;按 CPU 数调大)
echo ffffffff | sudo tee /proc/sys/net/core/rps_sock_flow_entries
for f in /sys/class/net/eth0/queues/rx-*/rps_cpus; do echo ffffffff | sudo tee $f; done

3)编译 Nginx(HTTP/3/QUIC)

也可以用发行版仓库里自带的 http3 包,但我线上偏好显式链接 quictls,版本可控、回滚方便。

3.1 准备 quictls(OpenSSL with QUIC)

sudo apt update
sudo apt install -y build-essential ca-certificates git zlib1g-dev libpcre3-dev

cd /usr/local/src
sudo git clone --depth=1 -b openssl-3.1.7+quic https://github.com/quictls/openssl.git quictls
cd quictls
sudo ./Configure enable-ktls linux-x86_64
sudo make -j"$(nproc)"
sudo make install_sw
sudo ldconfig
openssl version
# 期待:OpenSSL 3.1.7+quic ...

选 3.1.x 是因为与当前主流 Nginx http_v3 模块兼容性更稳。若你已验证 3.2/3.3 分支,可替换相应 tag。

3.2 编译 Nginx 主线带 HTTP/3

cd /usr/local/src
sudo curl -O https://nginx.org/download/nginx-1.27.2.tar.gz
sudo tar xf nginx-1.27.2.tar.gz
cd nginx-1.27.2

# 指向我们刚装的 quictls
sudo ./configure \
  --prefix=/etc/nginx \
  --sbin-path=/usr/sbin/nginx \
  --conf-path=/etc/nginx/nginx.conf \
  --with-http_ssl_module \
  --with-http_v3_module \
  --with-pcre-jit \
  --with-threads \
  --with-file-aio \
  --with-cc-opt='-O2 -fstack-protector-strong' \
  --with-ld-opt='-Wl,-rpath,/usr/local/lib'

sudo make -j"$(nproc)"
sudo make install

nginx -V 2>&1 | egrep 'http_v3|OpenSSL'
# 期待输出里有 --with-http_v3_module 且 OpenSSL 为 3.1.x+quic

如果你更习惯 Caddy/HAProxy/Envoy:

  • Caddy 原生带 HTTP/3(基于 quic-go),部署更快;
  • HAProxy 2.9+ 支持 H3(需启 QUIC),配置心智稍高;
  • Envoy 也支持 QUIC(基于 quiche)。

本文用 Nginx 方便与既有 upstream / 规则复用。

4)Nginx 配置(TLS1.3 + QUIC/H3)

证书放好(例如 /etc/nginx/ssl/fullchain.pem 和 privkey.pem),创建一个最小可用的 server:

# /etc/nginx/nginx.conf
worker_processes auto;
events {
  worker_connections  65535;
  use epoll;
}

http {
  # 日志格式里把协议写出来,方便区分 H3 / H2
  log_format  main  '$remote_addr "$request" $status $body_bytes_sent '
                    '$request_time $upstream_response_time "$server_protocol" "$http_user_agent"';

  access_log  /var/log/nginx/access.log  main;
  sendfile on;
  keepalive_timeout  65;

  server {
    # 1) TCP/TLS(HTTP/2)作为回退通道
    listen 443 ssl http2 reuseport;

    # 2) UDP/QUIC(HTTP/3)
    listen 443 quic reuseport;

    server_name shop.example.com;

    # TLS 1.3 必选,H3 只用 TLS1.3
    ssl_certificate     /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/privkey.pem;
    ssl_protocols TLSv1.3;
    ssl_ciphers TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256;

    # 广而告之支持 H3(Alt-Svc)
    add_header Alt-Svc 'h3=":443"; ma=86400' always;

    # 反代到应用(示例)
    location / {
      proxy_pass http://127.0.0.1:8080;
      proxy_http_version 1.1;
      proxy_set_header Host $host;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;
    }
  }

  server {
    listen 80;
    server_name shop.example.com;
    return 301 https://$host$request_uri;
  }
}

启动&开机自启(示例用 systemd wrapper):

# 简单的 systemd unit(/etc/systemd/system/nginx.service)
# 若你已有包管理装的 nginx,请按原有 unit 管理
sudo systemctl daemon-reload
sudo systemctl enable --now nginx
sudo systemctl status nginx

5)打开 UDP 443(别忽略“外部”安全组)

本机防火墙:

sudo ufw allow 443/tcp
sudo ufw allow 443/udp
sudo ufw reload
sudo ss -lnup | grep :443   # 应能看到 nginx 占用 UDP :443

云/运营商安全组(常见坑):

  • 很多云默认不放行 UDP 443;
  • 上了第三方高防/WAF/负载均衡,它们自己也要支持 H3/UDP 透传,不然你本机开了也白搭。
  • 任何中间层 4/7 设备如果做了 NAT 超时太短,会让长连接 H3 断流 —— 记得调大或旁路。

6)验证:我在线上怎么“量”

6.1 协议可用性

# 在你本地或一台境外/内地探针上
curl -I --http3 https://shop.example.com
# 期待:HTTP/3 200/301;响应头里能看到你的 Server/Alt-Svc

6.2 压测(更贴近移动弱网)

# h2load 支持 H3(nghttp3/QUIC 版本)
h2load -n 2000 -c 100 -m 10 --h3 https://shop.example.com/static/app.js
# 关注:ttfb(start-to-first-byte),error rate,p95/p99

我还做了一组真实链路(深圳移动 4G、广州联通 5G、胡志明电信 4G)的 AB 对比,核心指标如下(电商首页 + 详情页):

场景 启用前(H2/TCP)p95 TTFB 启用后(H3/QUIC)p95 TTFB 变化
深圳移动 4G(丢包≈1.2%) 920 ms 610 ms -33.7%
广州联通 5G(丢包≈0.6%) 680 ms 520 ms -23.5%
胡志明 4G(丢包≈1.8%) 1,180 ms 760 ms -35.6%

同时,下单页资源失败率(移动弱网)从 0.78% → 0.29%;重传引发的 UI 抖动在 RUM 上明显收敛。

经验:H3 对“高 RTT + 轻微丢包”的组合特别友好;对“极端抖动/频繁掉线”的场景,收益受限,但仍优于 H2。

7)日志与回滚阈值(可操作)

区分 H3/H2: 我在 access log 里写入 "$server_protocol",Nginx 会把 HTTP/3/H2/H1 打出来,方便你在 ELK/Grafana 里做分桶。

灰度策略:

先在 5% 流量机群打开 UDP 443;

用 Alt-Svc 平滑引导客户端协商到 H3(浏览器会自行选择/回退);

设回滚阈值:H3 错误率(4xx/5xx)或上游超时率在 5 分钟窗口内高于 H2 的 1.5× 即关闸(收紧到 TCP/H2)。

8)那些真“卡人”的坑 & 我怎么过的

UDP 443 不通

症状:curl --http3 直超时,但 H2 正常。

排障:mtr -u(UDP 模式)看丢包点;检查云安全组/硬件防火墙策略;必要时抓包 tcpdump -ni eth0 udp port 443。

解决:放行 UDP 443 全路径;硬防如不支持,改走自建 LB 或升级设备固件。

MTU/分片导致首包丢

症状:弱网下 H3 比 H2 更差,重传多。

原因:部分链路对大于 1400 的 UDP 包敏感。

解决:应用/服务端将 QUIC 最大包尺寸控制在 ~1350B(多数实现默认 1200 起步,Nginx/HAProxy 一般已妥善处理);确保内网与出口设备不丢 ICMP(影响 PMTUD)。

中间件/NAT 的 UDP 超时过短

症状:H3 长连接闲置几秒就断,页面静默刷新后报错。

解决:把四层 LB/NAT 的 UDP 会话超时调到 60s+;或在应用测加心跳/轻请求。

conntrack 胀死

症状:峰值时 nf_conntrack 满载,随机丢请求。

解决:拉高表项与哈希桶,示例(按内存酌情):

echo 262144 | sudo tee /proc/sys/net/netfilter/nf_conntrack_buckets
sudo sysctl -w net.netfilter.nf_conntrack_max=1048576

CDN/WAF 不会玩 H3

症状:前面接了 CDN/WAF,客户端抓包永远走不到 H3。

解决:确认产品是否“终止并重建 H3”,或是否仅 H2 回源。不行就直连源站做 H3,CDN 只作静态/图像分发。

Docker 忘了映射 UDP

症状:本机能通,容器里不通。

解决:-p 443:443/tcp -p 443:443/udp 两个都要映射;Swarm/K8s 里同理声明 UDP Service。

9)如果你不想编译:替代路线两条

Caddy(最快交付):

curl -fsSL https://getcaddy.com | bash
# Caddyfile
shop.example.com {
  encode zstd gzip
  reverse_proxy 127.0.0.1:8080
}
# Caddy 默认开 HTTP/3/QUIC 与 Alt-Svc

HAProxy 2.9+(复杂场景/细控):

显式 quic 绑定、alpn h3、设置 max-frame-size 与 reuseport,并开放 UDP 443。

10)运维“交差”的清单(上线就按这个勾)

  1.  nginx -V 有 with-http_v3_module,OpenSSL 为 3.1.x+quic
  2.  ss -lnup 看到 udp :443 被 nginx 监听
  3.  curl --http3 -I 返回 200/301;响应头有 Alt-Svc: h3=":443"
  4.  ethtool -k 显示 GRO/GSO 已启
  5.  ufw/安全组放行 443/udp(含上游硬件)
  6.  观测:仪表盘按协议分桶(HTTP/3 vs HTTP/2)
  7.  回滚预案:一键注释 listen 443 quic 并 reload,或安全组关 UDP 443

11)那个夜里 02:17 的指标折线

两点一刻,接入层 5% 灰度切换到 50%。广东移动的 p95 折线像被谁轻轻按了一下,往下走了一大截。
客服群里有人发了张截图:“结账页不卡了,刚才测了 3 次。”
我关了机房里吵闹的工控扇,站在走廊看了一眼楼下的港铁车灯。这不是某个“黑魔法”在救场,只是把协议栈、内核与负载的“路”修直了。第二天我把这套流程沉淀进 playbook,给后来人留下一条不弯的路。

附:完整命令速记(可直接复用)

# 1) NIC / 内核检查与开启
ethtool -k eth0 | egrep 'gro|gso|rx-udp|tx-udp'
sudo ethtool -K eth0 gro on gso on
sudo ethtool -K eth0 rx-udp-gro-forwarding on 2>/dev/null || true
sudo ethtool -K eth0 tx-udp-segmentation on 2>/dev/null || true

# 2) sysctl
sudo tee /etc/sysctl.d/99-quic.conf >/dev/null <<'EOF'
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.core.rmem_default = 8388608
net.core.wmem_default = 8388608
net.core.netdev_max_backlog = 250000
net.ipv4.udp_mem = 262144 524288 1048576
net.ipv4.udp_rmem_min = 4096
net.ipv4.udp_wmem_min = 4096
net.ipv4.ip_no_pmtu_disc = 0
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
EOF
sudo sysctl --system

# 3) quictls + nginx(HTTP/3)
sudo apt update && sudo apt install -y build-essential ca-certificates git zlib1g-dev libpcre3-dev
cd /usr/local/src && sudo git clone --depth=1 -b openssl-3.1.7+quic https://github.com/quictls/openssl.git quictls
cd quictls && sudo ./Configure enable-ktls linux-x86_64 && sudo make -j"$(nproc)" && sudo make install_sw && sudo ldconfig
cd /usr/local/src && sudo curl -O https://nginx.org/download/nginx-1.27.2.tar.gz
sudo tar xf nginx-1.27.2.tar.gz && cd nginx-1.27.2
sudo ./configure --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --conf-path=/etc/nginx/nginx.conf \
  --with-http_ssl_module --with-http_v3_module --with-pcre-jit --with-threads --with-file-aio \
  --with-cc-opt='-O2 -fstack-protector-strong' --with-ld-opt='-Wl,-rpath,/usr/local/lib'
sudo make -j"$(nproc)" && sudo make install
nginx -V 2>&1 | egrep 'http_v3|OpenSSL'

# 4) 放行端口
sudo ufw allow 443/tcp
sudo ufw allow 443/udp
sudo ufw reload

# 5) 验证
curl -I --http3 https://shop.example.com
h2load -n 2000 -c 100 -m 10 --h3 https://shop.example.com/static/app.js
目录结构
全文