QPACK 头部压缩

HPACK 为何在乱序交付下失效、编码器/解码器流、Required Insert Count 与阻塞流。

作者 David更新于 第 13 篇(共 16 篇)

RFC 9204。它存在的唯一原因:HPACK 在乱序交付的传输上会失效。

HPACK 为什么不能直接用

HPACK 的动态表要求编解码双方严格按同一顺序演进:

h2 (TCP,全局有序):
  请求1: 插入 "cookie: abc" → 动态表索引 62
  请求2: 引用索引 62
  → TCP 保证请求 1 先于请求 2 到达,解码必定成功

QUIC 的流是独立交付的——这正是它消除队头阻塞的方式,但也意味着没有全局顺序:

h3 (QUIC,流间无序):
  流A: 插入 "cookie: abc" → 索引 62
  流B: 引用索引 62
  → 若流 A 的包丢失,流 B 先到达
  → 解码器不知道索引 62 是什么 → 解码失败

若为此强制全局有序,就等于把队头阻塞又装了回来——HTTP/3 的意义荡然无存。

QPACK 的解法:把「动态表更新」与「字段块引用」分离到不同的流上,并让编码器自己决定冒多大的阻塞风险。

三条流

流方向内容
请求流双向HEADERS 帧,含字段块(引用表项)
编码器流单向动态表插入指令
解码器流单向插入计数确认、流取消

编码器把「插入 cookie: abc」发在编码器流上,把「引用它」发在请求流上。两条流独立交付。

静态表

99 个条目(索引 0–98),比 HPACK 的 61 个更大,且是按 HTTP/3 实测流量重新统计选出的。节选:

索引字段名值
0:authority—
1:path/
17:methodGET
25:status200
31content-typeapplication/json
95user-agent—

静态表无状态,任何流随时可引用,永不阻塞。

动态表与绝对索引

QPACK 的动态表条目有绝对索引(从 0 起单调递增,永不回绕)。字段块中用两种相对形式引用:

  • 相对索引:相对于该字段块的 Base。
  • 后基索引(post-base):引用在本字段块编码之后才插入的条目。

这套设计让编码器可以在同一往返中「边插入边引用」。

Required Insert Count

每个字段块的前缀里带 Required Insert Count(RIC)——解码本块所需的最小动态表插入数。

解码器的逻辑:

若 已收到的插入数 >= RIC:
    立即解码
否则:
    该流「阻塞」,等编码器流补齐后再解

RIC = 0 表示该块完全不引用动态表(只用静态表和字面量),可无条件立即解码——永不阻塞。

阻塞流与风险控制

SETTINGS_QPACK_BLOCKED_STREAMS(默认 0)声明解码器最多容忍多少条流处于阻塞状态。

编码器据此权衡:

编码器策略压缩率阻塞风险
只用静态表 + 字面量(RIC 恒 0)低零
积极引用最新插入的条目高高——丢包时多条流一起等
只引用已被确认的条目中零(确认意味着解码器已收到)

第三种是生产中的常见折中:编码器通过解码器流上的 Section Acknowledgement 得知哪些插入已确认,只引用这些,从而在拿到大部分压缩收益的同时完全避免队头阻塞。

注意默认值:QPACK_MAX_TABLE_CAPACITY 与 QPACK_BLOCKED_STREAMS 都默认为 0——即默认不启用动态表,退化为「静态表 + 字面量 + Huffman」。要用动态表必须双方显式协商。

指令格式

编码器流指令

指令首位模式作用
Set Dynamic Table Capacity001xxxxx调整动态表容量
Insert With Name Reference1Txxxxxx插入,名字引用静态/动态表
Insert With Literal Name01Mxxxxx插入,名字为字面量
Duplicate000xxxxx复制已有条目(把老条目挪到表头防淘汰)

解码器流指令

指令首位模式作用
Section Acknowledgement1xxxxxxx确认某流的字段块已解码
Stream Cancellation01xxxxxx流被取消,其引用作废
Insert Count Increment00xxxxxx告知已处理的插入数

Section Acknowledgement 是关键的反馈通道:它让编码器知道「这个条目对端确实有了」,从而安全地重复引用。

与 HPACK 的对照

HPACK (h2)QPACK (h3)
静态表61 项99 项
表更新与引用同一通道(严格有序)分离到不同流
乱序容忍无(必须有序)有(RIC + 阻塞机制)
表失步后果COMPRESSION_ERROR 杀连接QPACK_DECOMPRESSION_FAILED 杀连接
默认启用动态表是(4096 字节)否(容量默认 0)
整数/字符串编码前缀整数 + Huffman相同(复用 HPACK 的编码原语与 Huffman 表)
安全表示Never Indexed同样有(N 位)

编码原语是一样的——变的只是「如何在无序传输上安全地维护共享状态」这一层。

下一篇:连接与服务发现。

本页目录