HTTP/2 的头部压缩依赖 HPACK 动态表,连接建立后的前几个请求往往只能使用静态索引,动态表需要逐步填充。Nginx 反向代理回源如果采用 HTTP/2,日志中很多请求长度异常往往和这个动态表的冷启动、驱逐有关。尤其在长连接复用场景下,上游服务器偶尔调整 SETTINGS_HEADER_TABLE_SIZE,Nginx 作为 HTTP/2 客户端必须维护本地动态表,这些内部状态变化虽然不直接出现在默认 access log 里,却可以通过变量组合与日志策略还原出来。

理解回源日志与 HPACK 动态表的关系,首先要明白动态表不是一个全局共享的资源,而是每条 HTTP/2 连接独立维护的字典。当 Nginx 与上游服务器复用同一条连接时,动态表会积累头部字段;一旦连接断开重建,动态表清零,后续请求就得重新发送完整的字面量头部。因此,通过日志观察连接复用率、连接建立频率以及请求头长度波动,就能间接判断 HPACK 动态表的实际表现。
一、回源日志为何看不到 HPACK 动态表
HTTP/2 规范将头部压缩和流控都放在连接层处理,而 Nginx 的 access log 是以单个请求为单位记录信息的。每个请求的日志行可以包含请求方法、URI、状态码、字节数等,但默认不会记录该请求发送到上游时使用了哪条 HTTP/2 连接,也不会记录这条连接的动态表当时包含多少个条目。这就造成一个问题:如果回源头部膨胀是因为动态表被重置,日志里只会看到 $request_length 或 $upstream_response_length 变大,却无法直接定位到 HPACK 层面。
更进一步,HPACK 压缩后实际发送到上游的头部字节数与原始头部字节数并不相等。Nginx 作为客户端发送请求头时,如果某个头部字段已经在动态表中,就会被替换为一个整数索引;如果不在表中,则会以字面量编码发送。这种编码差异对应用层完全透明,access log 中的 $request_length 又是客户端发来的原始请求长度,而非回源时压缩后的长度,所以用默认日志几乎无法感知动态表效率。
从 RFC 9770 的角度看,动态表的容量限制和条目驱逐规则更加细化。当上游服务器通过 SETTINGS 帧减小 HEADER_TABLE_SIZE 时,Nginx 必须立即从本地动态表中移除超出容量的条目。如果后续请求恰好依赖这些被驱逐的字段,压缩率会骤降。这类行为只有结合连接级事件才能解释,单靠请求级日志是不够的。
二、Nginx 日志字段与 HPACK 压缩的可观测性映射
虽然 Nginx 没有直接输出动态表大小的变量,但可以借助 log_format 将多个变量组合起来,形成一套间接观测体系。常用的变量包括 $protocol(客户端协议)、$http2(是否使用 HTTP/2)、$request_length(客户端请求总长度)、$upstream_protocol(回源协议)、$upstream_connect_time(建立上游连接耗时)以及 $upstream_header_time(接收上游响应头耗时)。其中 $upstream_protocol 在 Nginx 1.17 及以上版本可用,能明确显示回源是否使用 HTTP/2。
建议在日志格式中同时记录时间戳、客户端协议、回源协议、上游地址、请求长度和连接建立耗时。下面是一个面向 HPACK 排障的日志模板:
log_format hpack_audit '$remote_addr $time_iso8601 $request_method $scheme $host$request_uri '
'client_proto=$protocol http2=$http2 req_len=$request_length '
'upstream=$upstream_protocol $upstream_addr conn_time=$upstream_connect_time '
'header_time=$upstream_header_time resp_len=$upstream_response_length sent=$bytes_sent';
access_log /var/log/nginx/hpack_audit.log hpack_audit;
光有这些基础变量还不够。要量化 HPACK 动态表的压缩效率,必须知道实际发送到上游的头部大小。Nginx 本身没有直接暴露该指标,但可以通过 njs 模块在 header_filter 或 log 阶段计算请求头的原始大小和字段数量,形成一个自定义变量。下面的 njs 示例统计了请求头中每个字段名称和值长度的总和,可以粗略代表未压缩的头部字节数:
function headerSize(r) {
var total = 0;
if (r.headersIn) {
for (var h in r.headersIn) {
if (r.headersIn.hasOwnProperty(h)) {
total += h.length + r.headersIn[h].length + 2;
}
}
}
return total;
}
export default { headerSize };
将 headerSize 注册成变量后,可以在 log_format 中加入该值。配合客户端的 $request_length 和回源的实际传输量,就能对比出头部压缩的大致效果。如果发现某个时间段内自定义头部大小没有变化,但回源连接重建频率增加,基本可以判断动态表被清空导致压缩失效。
三、RFC 9770 带来的动态表管理变化与配置调整
RFC 9770 对 HPACK 动态表的容量计算、条目插入和驱逐条件做了更加细致的规范。它强调代理设备在处理 SETTINGS 参数变更时,必须同步更新本地状态,不能延迟驱逐或提前插入。对于 Nginx 回源场景来说,这意味着当上游服务器通过 SETTINGS 将 HEADER_TABLE_SIZE 从默认的 4096 字节调低时,Nginx 需要立即收缩动态表;如果上游服务器调高该值,Nginx 也可能需要重新分配内部缓冲区。
从配置层面看,Nginx 作为 HTTP/2 客户端回源时,其头部解析限制由 http2_max_field_size 和 http2_max_header_size 等指令控制,但这些指令主要针对接收客户端请求,并不直接约束回源时向上游发送的头部大小。真正的瓶颈在于上游 HTTP/2 连接池的复用策略。如果配置了 proxy_http_version 2.0,Nginx 会尽量复用与上游的 HTTP/2 连接,动态表才有机会积累。反之,如果因为超时或并发限制频繁新建连接,动态表每次都要从零开始填充。
与 HPACK 动态表相关的几个关键配置包括 http2_max_requests(单条 HTTP/2 连接最多处理多少请求)、http2_idle_timeout(空闲连接超时时间)和 http2_max_concurrent_streams(并发流上限)。适当增大 http2_max_requests 和 http2_idle_timeout,可以让连接存活更久,减少动态表重建频率。但要注意内存占用:动态表容量越大,每条连接占用的内存越多,大量空闲连接会放大内存消耗。因此需要根据实际流量和上游服务器能力权衡。
在实际配置中,可以通过 keepalive 和 proxy_http_version 配合控制回源连接。例如,在 upstream 块中设置 keepalive 64,并使用 proxy_http_version 2.0,同时在上游服务器侧保持合理的 SETTINGS_HEADER_TABLE_SIZE。这样既能保证动态表有足够的复用机会,又能避免连接数膨胀带来的副作用。
四、从日志到结论:三步定位动态表引起的回源头部膨胀
当回源流量明显增加,而上游 CPU 或网络开销同步上升时,可以按照以下三步定位是否是 HPACK 动态表问题。第一步,观察日志中 $upstream_protocol 的值。如果回源不再使用 HTTP/2,或者频繁在 HTTP/1.1 和 HTTP/2 之间切换,说明连接协商不稳定,动态表无法稳定存在。第二步,对比 $request_length 与自定义的头部大小变量。如果客户端请求长度基本不变,但回源传输字节数明显增加,说明头部的压缩比例下降,很可能是动态表被驱逐或连接重建导致。
第三步,分析连接建立频率。将日志按 $remote_addr 和 $upstream_addr 分组,统计 conn_time 非零的记录数量。如果大量请求都触发了新的上游连接,说明 keepalive 配置不足或者上游服务器主动关闭连接。每次新连接都会重置 HPACK 动态表,前几个请求的头部无法使用动态索引,压缩效率自然偏低。此时可以检查 http2_max_requests 是否设置过小,或者上游服务器的 SETTINGS_MAX_CONCURRENT_STREAMS 是否限制了并发流导致连接被快速关闭。
如果以上步骤仍然无法定位,可以借助 Nginx 的 debug 日志查看 HTTP/2 帧级别的信息。在编译时启用 --with-debug,然后在 error_log 中设置 debug 级别,就能看到 HPACK 编码前后的头部字段以及动态表插入和驱逐的详细记录。不过 debug 日志量很大,只适合短时间开启。结合 RFC 9770 对动态表管理的明确要求,你可以更有针对性地检查 SETTINGS 变更时的日志事件,确认上游是否在连接中途调整了 HEADER_TABLE_SIZE。
最终目标不是让动态表一直满载,而是让压缩效率与资源消耗保持平衡。通过合理的日志变量组合和配置参数,HPACK 动态表就从一个看不见的压缩黑盒变成了可以通过数据趋势判断的指标。当发现回源头部膨胀时,不再需要盲目重启服务或猜测连接状态,而是可以从日志中快速判断是动态表冷启动、容量收缩还是连接池复用不足,并据此做出准确调整。
Nginx日志HTTP/2 HPACK动态表RFC9770修改时间:2026-08-28 22:36:10