HTTP/3 已经从实验性协议逐步走向主流,Chrome、Firefox、Safari 等浏览器均已默认支持,其底层传输协议 QUIC 基于 UDP 实现,彻底绕开了 TCP 的队头阻塞问题。对于运行 Apache 的站点来说,如果能在代理架构中引入 HTTP/3 支持,再配合缓存层减少回源,弱网用户和高并发场景下的访问体验会有明显改善。本文以 rs10100 这类高并发业务环境为背景,从原理、安装、配置到排错,完整走一遍 Apache 代理缓存与 HTTP/3 的落地过程。

一、先弄明白:QUIC 与 HTTP/3 到底解决了什么问题
传统的 HTTP/2 虽然在应用层实现了多路复用,但它跑在 TCP 之上,只要某一个 TCP 包丢失,后面所有已到达的数据都要排队等待重传,这就是传输层队头阻塞。QUIC 直接在 UDP 之上重新实现了可靠传输、流控和拥塞控制,把流与流之间的依赖彻底解开,单个流丢包不会阻塞其他流。
其次,QUIC 把传输层握手和 TLS 1.3 握手合并在一起,首次连接通常只需 1 个 RTT 就能完成加密协商,后续连接甚至可以做到 0-RTT。对于移动端用户频繁切换网络的场景,QUIC 的连接迁移特性还能通过 Connection ID 保持连接不断,不会像 TCP 那样换一个 IP 就得重新握手。
需要提醒的是,Apache 官方的 mod_http3 长期处于实验状态,生产使用前务必评估稳定性。如果你的 Apache 版本较老(比如 2.4.41 之前的版本),建议先升级到 2.4.5x 系列,再考虑编译第三方模块。整体链路的思路是:客户端通过 QUIC/HTTP/3 直连 Apache 监听的 UDP 端口,Apache 内部通过 mod_proxy 转发到后端,同时用 mod_cache 做代理缓存,减少重复回源。
二、编译安装 mod_http3 并启用 UDP 监听
mod_http3 依赖 ngtcp2 和 nghttp3 两个库,前者负责 QUIC 传输层,后者负责 HTTP/3 语义解析。编译前先准备好依赖环境,以 CentOS 系的 yum 或 Debian 系的 apt 安装基础工具链即可。
# 安装编译依赖 yum install -y git gcc cmake make autoconf libtool openssl-devel # 编译 nghttp3 git clone --recursive https://github.com/ngtcp2/nghttp3.git cd nghttp3 autoreconf -i ./configure --enable-lib-only make && make install # 编译 ngtcp2(需要 openssl 支持 QUIC API) git clone --recursive https://github.com/ngtcp2/ngtcp2.git cd ngtcp2 autoreconf -i ./configure --enable-lib-only --with-openssl make && make install # 编译 mod_http3 git clone https://github.com/icing/mod_http3.git cd mod_http3 ./configure --with-apxs=/usr/bin/apxs make && make install
编译完成后,需要在 Apache 配置中加载模块并开启 UDP 监听。注意 HTTPS 仍然要在 TCP 443 上正常服务,因为不支持 HTTP/3 的客户端会回落到 TCP 上的 HTTP/2 或 HTTP/1.1,双栈并行是必需的。
LoadModule http3_module modules/mod_http3.so
# TCP 监听保持不变
Listen 443
# UDP 监听,用于 QUIC
Listen 443 udp
Protocols h2 h3 http/1.1
<VirtualHost *:443>
ServerName www.ipipp.com
SSLEngine on
SSLCertificateFile /etc/httpd/ssl/www.pem
SSLCertificateKeyFile /etc/httpd/ssl/www.key
# 开启 HTTP/3,并通告 Alt-Svc
ProtocolsH3 on
</VirtualHost>
配置里的 ProtocolsH3 on 会让 Apache 在响应头中自动输出 Alt-Svc: h3=":443",浏览器第一次通过 TCP 访问后,后续请求就会尝试升级到 HTTP/3。用 curl --http3 或浏览器的开发者工具(协议列显示 h3)即可验证是否生效。
三、搭建代理缓存层:mod_proxy 与 mod_cache 组合
单纯启用 QUIC 只解决了传输效率问题,后端压力依然存在。在 rs10100 这类高并发场景下,代理缓存是必须的一环。思路是:Apache 收到请求后先查本地缓存(磁盘或内存),命中则直接返回;未命中才通过 mod_proxy 回源到后端应用服务器。
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule cache_module modules/mod_cache.so
LoadModule cache_disk_module modules/mod_cache_disk.so
<VirtualHost *:443>
ServerName www.ipipp.com
ProtocolsH3 on
# 代理转发到后端
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:8080/
ProxyPassReverse / http://127.0.0.1:8080/
# 启用磁盘缓存
CacheEnable disk /
CacheRoot /var/cache/httpd/proxy
CacheDirLevels 2
CacheDirLength 1
CacheMaxFileSize 5000000
CacheIgnoreNoLastMod On
# 静态资源缓存时间控制
<LocationMatch "\.(jpg|png|css|js|woff2)$">
CacheDefaultExpire 86400
</LocationMatch>
</VirtualHost>
几个参数值得说明:CacheDirLevels 和 CacheDirLength 控制缓存目录的层级结构,文件量大时适当加深层级可以避免单目录文件过多导致文件系统性能下降;CacheMaxFileSize 用于限制单个缓存文件大小,避免大文件占满磁盘;对于没有 Last-Modified 头的动态内容,CacheIgnoreNoLastMod On 可以让它按过期时间参与缓存。
另一个关键点是缓存清理。缓存不会自动删除过期但已失效的内容,建议配合 htcacheclean 定时任务控制缓存总占用,例如限制在 10GB 以内,每天凌晨清理一次。同时在后端发生内容更新时,通过主动 PURGE 或调整 Cache-Control 头来保证一致性,避免用户长时间看到旧内容。
四、性能验证与常见问题排查
部署完成后,先做协议验证再压测性能。协议层面用 curl --http3 -v https://www.ipipp.com/ 观察握手过程,输出中出现 HTTP/3 200 即说明 QUIC 链路已通。性能层面可以对比同一资源在 h2 与 h3 下的加载耗时,重点观察弱网模拟(限速加丢包)下的差距,通常丢包率越高,QUIC 的优势越明显。
# 验证 HTTP/3 是否生效 curl --http3 -sv https://www.ipipp.com/ -o /dev/null 2>&1 | grep -i "HTTP/3" # 定时清理缓存 htcacheclean -p /var/cache/httpd/proxy -l 10G -t # 观察缓存命中情况 tail -f /var/log/httpd/access_log | grep -E "cache (hit|miss)"
常见问题方面,第一类是 QUIC 完全握手失败,多半是防火墙没有放行 UDP 443 端口,或者中间的云安全组只放行了 TCP,用 nc -ul 443 在服务端配合客户端测试即可定位;第二类是模块加载报错,通常因为 nghttp3、ngtcp2 安装到了非标准路径,执行 ldconfig 刷新动态库缓存或配置 LDFLAGS 指定路径即可;第三类是缓存命中率为零,需要检查后端是否返回了 Set-Cookie 或 Cache-Control: no-store,这类响应默认不会被缓存,需要在应用层调整响应头。
最后强调一点,HTTP/3 的收益主要集中在高延迟、高丢包的网络环境,如果你的用户绝大多数都在同机房低延迟内网,开启 QUIC 的提升可能很有限,此时把精力放在缓存策略和回源优化上反而收益更大。两者结合,才是 rs10100 这类场景下最优的加速方案。