把应用部署在Koyeb这类边缘云平台上之后,很多同学会发现一个尴尬的现象:平台本身的速度不错,但从国内或弱网环境访问时,首屏延迟依然居高不下。原因往往不在Koyeb,而在传输链路。传统的HTTP/2跑在TCP上,一次完整握手加上TLS协商动辄两三个往返,丢包时还要受队头阻塞拖累。HTTP/3改用QUIC协议,基于UDP传输,把传输层和加密层合并,能明显缩短建连时间。本文讨论如何在Apache反向代理层开启HTTP/3,同时利用mod_cache做代理缓存,把Koyeb后端的响应加速到边缘。

一、QUIC协议解决了什么问题
QUIC由Google设计,后被IETF标准化为RFC 9000,成为HTTP/3的底层传输协议。它之所以放弃TCP而选择UDP,核心动机有三个。
第一是握手开销。TCP需要三次握手,TLS 1.3还要再来一次往返,两者叠加导致首次请求延迟较高。QUIC把传输握手和加密握手合并,理想情况下客户端第一个包就能携带业务数据,也就是所谓的零往返(0-RTT)建连。对于边缘节点到Koyeb源站这种跨区域回源场景,每减少一个往返都意味着几百毫秒的节省。
第二是队头阻塞。HTTP/2虽然实现了流多路复用,但所有流共享一条TCP连接,一旦某个包丢失,整条连接上的所有流都要等待重传。QUIC在传输层为每个流独立管理序号与确认,某个流的丢包只阻塞该流本身,其他流照常收发。这对代理场景尤其重要——边缘节点并发回源拉取大量资源时,抗丢包能力直接决定回源效率。
第三是连接迁移。QUIC用连接ID标识连接而不是四元组,客户端从Wi-Fi切换到蜂窝网络时,IP变了连接仍在,无需重新握手。移动端用户访问Koyeb应用时体验会更平滑。
二、在Apache中开启HTTP/3支持
Apache官方在2.4.x的后续版本中通过mod_http3(基于ngtcp2与nghttp3的实验模块)提供了HTTP/3支持。需要注意,该模块仍处于实验阶段,生产环境使用前务必充分测试。先确认版本与依赖:
# 查看当前Apache版本,mod_http3要求2.4.51以上 apachectl -v # 安装编译依赖(以Ubuntu为例) apt install build-essential libngtcp2-dev libnghttp3-dev ngtcp2-client libevent-dev
编译安装mod_http3后,在配置文件中启用协议协商。关键点是同时保留HTTP/2和HTTP/1.1作为回退,因为QUIC依赖UDP端口,部分中间网络设备会拦截UDP流量:
LoadModule http3_module modules/mod_http3.so
LoadModule ssl_module modules/mod_ssl.so
Listen 443
Protocols h3 h2 http/1.1
<VirtualHost *:443>
ServerName edge.example.ipipp.com
SSLEngine on
SSLCertificateFile /etc/ssl/certs/edge.pem
SSLCertificateKeyFile /etc/ssl/private/edge.key
# 开启0-RTT早期数据
SSLSessionTickets on
</VirtualHost>配置完成后用curl验证。注意curl需要7.66以上版本且编译时带HTTP/3支持:
curl -I --http3 https://edge.example.ipipp.com/ -v 2>&1 | grep -E "HTTP/3|quic"
如果输出中包含HTTP/3 200字样,说明QUIC链路已经建立。如果协商失败回退到了h2,通常有两种可能:一是防火墙没放行UDP 443端口,二是客户端到服务器之间的网络设备丢弃了UDP包。前者可以通过ufw allow 443/udp解决,后者只能依赖h2回退,这也是为什么Protocols指令里必须保留h2。
三、配置mod_proxy与mod_cache代理缓存Koyeb后端
HTTP/3解决的是客户端到边缘节点的传输效率,而代理缓存解决的是边缘节点到Koyeb源站的回源成本。两者叠加才是完整的加速方案。Apache实现代理缓存需要三个模块协同:mod_proxy负责转发请求,mod_cache负责缓存决策,mod_cache_disk负责磁盘存储。
基础代理配置如下,假设Koyeb应用的默认域名形如your-app.koyeb.app:
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
CacheRoot /var/cache/apache2/proxy
CacheDirLevels 2
CacheDirLength 1
CacheIgnoreNoLastMod On
ProxyPreserveHost Off
ProxyRequests Off
<VirtualHost *:443>
ServerName edge.example.ipipp.com
Protocols h3 h2 http/1.1
SSLEngine on
SSLCertificateFile /etc/ssl/certs/edge.pem
SSLCertificateKeyFile /etc/ssl/private/edge.key
ProxyPass / http://your-app.koyeb.app/
ProxyPassReverse / http://your-app.koyeb.app/
# 对静态资源启用缓存
<LocationMatch "\.(js|css|png|jpg|webp|woff2)$">
CacheEnable disk /
CacheDefaultExpire 86400
CacheHeader on
</LocationMatch>
</VirtualHost>这里有几个容易踩坑的点需要展开说明。
首先是缓存失效策略。Koyeb默认每次部署都会生成新版本,静态资源如果带内容哈希文件名,可以放心设置较长的CacheDefaultExpire;但如果资源路径固定(比如/app.js),就必须依赖源站的Cache-Control头控制过期。可以在Koyeb应用侧加上Cache-Control: max-age=300, stale-while-revalidate=60这类响应头,让边缘在过期后短暂返回旧内容同时后台刷新,避免回源风暴。
其次是HTTPS回源。上面示例用HTTP回源图省事,生产环境建议改用https://your-app.koyeb.app/,并确保系统CA证书齐全。Koyeb的免费证书由Let's Encrypt签发,Apache默认信任链一般没问题。
最后要注意CacheIgnoreNoLastMod。Koyeb上一些动态接口不返回Last-Modified和ETag,默认情况下mod_cache会拒绝缓存这类响应。打开这个开关并配合CacheDefaultExpire可以强制缓存,但务必只对确定可缓存的路径生效,避免把用户个性化数据缓存到公共层。
四、验证缓存命中与整体压测
配置完成后需要确认缓存真的生效了。开启CacheHeader on后,Apache会在响应里输出X-Cache相关的命中信息,也可以直接看Age头:
# 第一次请求,MISS,回源Koyeb curl -sI https://edge.example.ipipp.com/app.js | grep -iE "age|x-cache" # 第二次请求,应命中缓存,Age大于0 curl -sI https://edge.example.ipipp.com/app.js | grep -iE "age|x-cache"
如果第二次请求的Age头有值且响应时间从几百毫秒降到几毫秒,说明磁盘缓存命中。还可以结合apachectl -M确认模块加载情况,用htcacheclean -d30 -p/var/cache/apache2/proxy -l500M定时清理缓存目录防止磁盘占满。
端到端性能方面,建议用支持HTTP/3的压测工具对比h2与h3的表现,例如h2load或curl批量脚本。一般弱网模拟(加延迟加丢包)下,h3的优势最明显;本地低延迟环境下差距不大属于正常现象,不要因此否定配置的正确性。
整体方案回顾:客户端通过QUIC直连Apache边缘节点,静态资源命中本地磁盘缓存直接返回,未命中或动态请求经由mod_proxy回源Koyeb。传输层加速与缓存层加速各司其职,这套架构对任何基于Koyeb部署的Web应用都适用,迁移成本低,效果立竿见影。
Apache代理缓存HTTP/3QUIC修改时间:2026-09-09 11:47:15