在Qoddi这类现代 PaaS 上跑 Web 服务时,后端应用往往只提供 HTTP/1.1 或 HTTP/2 接口,而用户侧浏览器已经普遍支持 HTTP/3。如果直接在 Apache 前层做代理并开启 QUIC,就能让终端在丢包网络下仍保持稳定吞吐。更关键的是,Apache 的代理缓存可以把经 HTTP/3 拿到的响应暂存,后续请求连后端 QUIC 往返都省掉。下面我们先看整体架构示意图。

Qoddi 环境中编译与启用 Apache HTTP/3 模块
Qoddi 的构建环境默认附带的是较新版本的 Apache,但官方镜像通常不会打开 mod_http3 与 mod_proxy_http3。我们需要在部署脚本里自行从源码补充编译,或者切换至带 QUIC 支持的运行时。核心依赖是 nghttp3 与 quiche 这类底层库,Apache 通过 mod_http3 调用它们实现 0-RTT 与连接迁移。编译时要确保 OpenSSL 版本不低于 1.1.1,否则 QUIC 的 TLS 1.3 握手无法完成。
启用模块后,必须在虚拟主机中声明监听 UDP 443,并广播 Alt-Svc 头告诉浏览器本机支持 h3。很多新手只写了 Listen 443 却忘了 UDP,导致客户端永远走不上 QUIC。下面是一段最小可运行的配置片段,展示如何加载模块并打开协议。
LoadModule http3_module modules/mod_http3.so
LoadModule proxy_http3_module modules/mod_proxy_http3.so
Listen 443 udp
Protocols h2 h3 http/1.1
<VirtualHost *:443>
ServerName app.example.qoddi.io
SSLEngine on
SSLCertificateFile /etc/ssl/qoddi/cert.pem
SSLCertificateKeyFile /etc/ssl/qoddi/key.pem
Header always set Alt-Svc "h3=":443"; ma=86400"
</VirtualHost>
这段配置中 Protocols 指令把 h3 放在靠后但不是最后的位置,兼容旧客户端。注意 Alt-Svc 的值必须用中文弯引号外的标准双引号包裹在 Apache 配置里,但发给浏览器的响应头里不能带错格式。部署完可用 curl --http3 验证端口是否真正返回 QUIC 响应。
用 mod_cache 缓存经 QUIC 代理的响应
Apache 的 mod_cache 和 mod_cache_disk 能够缓存任何后端响应,包括由 mod_proxy_http3 拉取的 HTTP/3 内容。关键点在于:缓存发生在代理之后,因此后端即使只支持 HTTP/1.1,只要 Apache 用 QUIC 去连另一个上游或自身回源,缓存层就能透明存储。实践中我们常把 Apache 同时作为面向用户的 QUIC 终结点和面向后端的 HTTP/1.1 代理,中间插入磁盘缓存。
配置缓存时要小心 Cache-Control 头部。如果后端应用返回 no-store,Apache 不会落地任何内容。我们可以在代理阶段用 Header merge 改写,但更推荐在应用里按路由区分静态与动态。下面的例子展示如何对静态前缀开启一小时磁盘缓存,并避开带会话 cookie 的请求。
CacheRoot "/var/cache/apache_qoddi"
CacheEnable disk "/"
CacheDiskExpr "%{REQUEST_URI} =~ m#^/static/#"
CacheHeader on
CacheDefaultExpire 3600
<Location "/static/">
ProxyPass "https://backend.internal:443/"
ProxyPassReverse "https://backend.internal:443/"
Header set Cache-Control "public, max-age=3600"
</Location>
这里 CacheDiskExpr 限制只缓存静态目录,避免把用户个性化接口写进磁盘。在 Qoddi 的临时文件系统上,建议把 CacheRoot 指向挂载的持久卷,否则实例重启缓存即失。经过该层后,第二次请求静态资源时 Apache 直接以 HTTP/3 回给用户,不再接触后端。
常见故障与协议降级处理
最典型的故障是浏览器收不到 Alt-Svc,从而一直用 TCP 访问。排查时先用 curl -I 看响应头是否含 h3 广播,再确认防火墙是否放行 UDP 443。Qoddi 的边缘网络有时默认只暴露 TCP,需要在控制台手动开启 QUIC 端口,否则服务端配置正确也无效。
另一类是后端降级:当上游不支持 HTTPS 或证书不匹配,mod_proxy_http3 会直接报错而非回退。我们可以用 ProxyPass 的 acquire 超时与 retry 参数缓解,但根本办法是在后端前置一个最小 HTTP/1.1 终结点。下表对比三种模式的差异:
| 模式 | 用户到 Apache | Apache 到后端 | 缓存收益 |
|---|---|---|---|
| 仅 TCP 代理 | HTTP/1.1 | HTTP/1.1 | 有,但弱网慢 |
| QUIC 代理无缓存 | HTTP/3 | HTTP/3 | 无,每次回源 |
| QUIC 代理加缓存 | HTTP/3 | HTTP/1.1 或 HTTP/3 | 明显,省回源 |
从表中可见,只有同时开启 QUIC 与缓存才能兼顾传输层抗丢包与命中率。若后端是 Qoddi 同一区域内的容器,Apache 到后端用 HTTP/1.1 延迟极低,不会影响整体体验。最后提醒,mod_cache 不会自动清理过期文件,需要搭配 htcacheclean 定时任务,防止磁盘被静态资源写满。
性能验证与上线建议
上线前应使用支持 HTTP/3 的客户端做对比测试。例如用 curl --http3 -o /dev/null -w "%{time_starttransfer}" 分别测带缓存与不带缓存的首字节时间。在模拟 3% 丢包的网络中,纯 TCP 代理可能超过 800ms,而 QUIC 加缓存通常能压到 200ms 内。这种差距在移动端首屏上非常直观。
建议先在预发布环境打开 CacheHeader on,观察 X-Cache 值是 HIT 还是 MISS,确认广播与端口无误后再切流量。Qoddi 的日志面板可以过滤 UDP 443 的访问,若发现占比极低,多半是 Alt-Svc 未生效。整体方案改动量小,却能让中小站点立即获得 HTTP/3 与边缘缓存的双重红利。