HTTP/3已经从草案阶段的实验协议逐步走向主流,Chrome、Firefox、Safari等浏览器默认开启QUIC支持,Cloudflare、Google等大厂也早已大规模落地。但对于大量仍在使用Apache的自建站点来说,想要吃到HTTP/3的红利并不容易:Apache官方的mod_http3长期处于实验状态,功能不完整且更新缓慢。好在还有另一条路——LiteSpeed团队开源的lsquic库是目前最成熟的QUIC协议实现之一,基于它可以构建独立的QUIC前端,负责HTTP/3的协议终结,再把请求以HTTP/1.1或HTTP/2转发给后端的Apache,同时利用Apache的mod_cache做代理缓存,形成一套HTTP/3接入加缓存加速的完整方案。

为什么选择lsquic而不是其他QUIC实现
目前开源生态里的QUIC实现不少,包括Google的Chromium QUIC、Cloudflare的quiche、Mozilla的Neqo以及LiteSpeed的lsquic。选型时主要考虑协议完整度、性能和可集成性三个维度。lsquic是LiteSpeed Web Server的商业级实现剥离出来的开源库,经过多年生产环境验证,对QUIC版本协商、连接迁移、0-RTT恢复这些关键特性支持得非常完整。
lsquic的架构设计有一个突出特点:它只负责协议处理,不绑定任何网络模型。库本身通过回调接口把事件循环交给上层应用,你可以用epoll、kqueue甚至自己的事件框架驱动它。这种解耦设计意味着它既能嵌入到Web服务器进程内,也能做成独立的代理进程。相比之下,quiche更偏Rust生态,与Apache的C模块体系集成成本较高;Chromium QUIC则耦合在浏览器代码库里,剥离使用非常困难。
性能层面,lsquic做了大量优化,包括零拷贝的包处理、批量加密解密、可插拔的SSL后端(支持BoringSSL、OpenSSL等)。在同等硬件条件下,单核能轻松处理数万QUIC连接,这对前端接入层来说是够用的。另外它的证书管理和ALPN协商接口比较友好,做HTTP/3终结代理时配置量不大。
编译lsquic并构建HTTP/3前端代理
lsquic依赖较少,核心是BoringSSL或特定版本的OpenSSL提供加密支持,另外需要zlib和Brotli。以Ubuntu为例,先安装基础依赖,然后从源码编译。这里给出一个完整的编译流程:
#!/bin/bash # 安装依赖 apt-get update apt-get install -y build-essential cmake git zlib1g-dev libevent-dev # 编译BoringSSL(lsquic推荐搭配) git clone https://boringssl.googlesource.com/boringssl cd boringssl cmake -B build && cmake --build build -j$(nproc) cd .. # 编译lsquic git clone https://github.com/litespeedtech/lsquic.git cd lsquic git submodule init git submodule update cmake -B build \ -DBORINGSSL=../boringssl \ -DBORINGSSL_LIB=../boringssl/build \ -DCMAKE_BUILD_TYPE=Release cmake --build build -j$(nproc)</code>
编译完成后,lsquic源码里自带一个http_client和测试用的服务端程序,但要做生产级的前端代理,建议基于lsquic的engine接口自己写一个轻量转发器,或者直接使用社区维护的基于lsquic的代理项目。核心思路是:启动一个QUIC监听端口(通常是443/UDP),接收HTTP/3请求后,把请求以HTTP/1.1转发到Apache监听的内部端口。下面是一段简化的转发逻辑示例:
#include "lsquic.h"
#include "lsquic_types.h"
// 初始化lsquic引擎,注册回调
struct lsquic_engine_api api;
memset(&api, 0, sizeof(api));
api.ea_new_stream_cb = on_new_stream; /* 新流到达时的回调 */
api.ea_stream_read_cb = on_read; /* 收到请求头/Body */
api.ea_stream_write_cb = on_write; /* 可写事件 */
api.ea_conn_close_cb = on_conn_close; /* 连接关闭 */
struct lsquic_engine *engine = lsquic_engine_new(
LSENG_SERVER | LSENG_HTTP, &api);
/* 事件循环中处理UDP socket的读写,交给engine消化 */
lsquic_engine_packet_in(engine, buf, nread,
(struct sockaddr *)&local_addr,
(struct sockaddr *)&peer_addr, NULL, 0);这段代码展示了最核心的引擎初始化和包处理入口。实际项目中还需要处理证书加载、ALT-SVC头注入、连接超时等细节。特别提醒一点:HTTP/3的发现机制依赖HTTP/2或HTTP/1.1响应中的Alt-Svc头,浏览器看到这个头才会尝试用QUIC连接,所以后端Apache或前端代理必须注入类似alt-svc: h3=":443"; ma=86400的响应头,否则客户端永远不会主动走HTTP/3。
配置Apache代理缓存层
前端QUIC终结之后,Apache侧的配置重点是反向代理和缓存策略。假设Apache监听在127.0.0.1:8080,前端代理通过HTTP/1.1回源,Apache的配置如下:
# 加载必要模块
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
Listen 127.0.0.1:8080
<VirtualHost 127.0.0.1:8080>
ServerName www.ipipp.com
# 开启磁盘缓存
CacheEnable disk /
CacheRoot /var/cache/apache2/proxy
CacheDirLevels 2
CacheDirLength 1
CacheMaxFileSize 10000000
CacheMinFileSize 500
# 缓存有效期:命中后最长缓存2小时
CacheDefaultExpire 7200
CacheDetailFormat on
# 静态资源强缓存,HTML走协商缓存
<LocationMatch "\.(jpg|png|css|js|woff2)$">
Header set Cache-Control "public, max-age=86400"
</LocationMatch>
# 反向代理到真正的应用后端
ProxyPass "/" "http://127.0.0.1:9000/"
ProxyPassReverse "/" "http://127.0.0.1:9000/"
# 对后端响应注入Alt-Svc声明HTTP/3端口
Header always set Alt-Svc 'h3=":443"; ma=86400'
</VirtualHost>这里有几个配置点值得展开说明。第一,CacheEnable disk配合mod_cache_disk是吞吐和实现复杂度最均衡的方案,缓存文件直接落在磁盘上,重启不丢失,配合htcacheclean可以自动清理过期内容。如果内存充裕且流量极大,也可以考虑mod_cache_socache,直接把缓存放进共享内存,省去磁盘IO。
第二,缓存键的生成默认包含请求的Host、端口、路径和Vary头。要注意如果你的站点对登录用户和匿名用户返回不同内容,后端必须正确返回Vary: Cookie之类的头,否则会把登录态页面缓存给匿名用户,造成严重的信息串页事故。建议在上线前用CacheDetailFormat on配合日志仔细核对每类URL的缓存行为。
第三,HTTP/3终结代理回源时会把请求头里的HTTP版本改写成HTTP/1.1,后端日志统计时要注意区分。可以在前端代理转发时额外注入一个自定义头(如X-Proto: h3),方便后端统计HTTP/3的真实流量占比。
性能验证与调优建议
部署完成后,验证HTTP/3是否生效最直接的方式是检查浏览器开发者工具的协议列,或者用curl编译QUIC支持后测试。命令行验证可以这样:
# 需要支持HTTP/3的curl版本(8.1+) curl --http3-only -I https://www.ipipp.com/ # 预期响应头中应包含: # alt-svc: h3=":443"; ma=86400 # 且连接协议显示为HTTP/3
调优方面,UDP缓冲区是第一个要调的参数。QUIC跑在用户态,内核默认的UDP接收缓冲区(通常只有200KB左右)在高丢包重传场景下会大量丢包,建议通过sysctl调大:net.core.rmem_max=16777216和net.core.wmem_max=16777216,同时在lsquic初始化时设置对应的上限,否则内核允许了但应用层没申请,等于白调。
缓存层调优的核心是命中率。可以通过Apache的mod_cache日志统计命中率,一般静态资源站点的命中率能做到95%以上,动态内容为主的站点争取做到60%到70%也有可观的回源削减效果。命中率不理想时优先排查两类问题:一类是响应头带了Set-Cookie导致默认不缓存,可以在代理层用CacheIgnoreHeaders Set-Cookie放宽;另一类是URL带了随机参数导致缓存键爆炸,用CacheIgnoreQueryString或规范化参数解决。
最后提一个容易踩的坑:如果前端QUIC代理和Apache之间走了多层NAT或者容器网络,务必确认MTU设置。QUIC初始包较小问题不大,但数据流阶段的包如果超过路径MTU会触发持续分片重传,表现为速度反而比HTTP/2还慢。可以用lsquic提供的引擎参数限制最大UDP payload大小,一般设为1200到1350之间比较稳妥。
整体来看,lsquic加Apache代理缓存的组合,是在不推翻现有Apache架构的前提下接入HTTP/3的务实方案。前端用专门的QUIC进程做协议终结,Apache专注做代理和缓存,各司其职,后续Apache官方的HTTP/3支持成熟了,也可以只替换前端这一层,迁移成本几乎为零。
lsquicHTTP/3Apache代理缓存修改时间:2026-09-14 06:10:49