某批量查询链路启用压缩后,带宽下降 62%,P99 由 180 毫秒升至 260 毫秒。归因结果:阈值设为 1KB,而该链路九成响应为 800 至 1200 字节,压缩净收益接近于零,CPU 与响应头开销照常发生。
一、三笔账的边界
| 账目 | 承担方 | 亏损条件 |
| 字节账 | 网络 | 响应低于数百字节:固定开销 18~20 字节,响应头另增约 47 字节 |
| 往返账 | 客户端体感 | 未跨过 MSS 边界时段数不变,字节节省不转化为往返节省 |
| CPU 账 | 服务端 | 压缩在服务端执行,高并发下成为核的竞争项 |
验收标准:压缩决策必须先声明目标(延迟或带宽),再选阈值。
二、阈值断点
同一列表两类体积的实测:
| 原始体积 | 压缩后 | 段数变化 | 往返收益 |
| 1005 字节 | 173 字节 | 1 → 1 | 无 |
| 4159 字节 | 355 字节 | 3 → 1 | 少两次往返 |
结论:以延迟为目标,阈值取一个 MSS(约 1400 字节);以带宽为目标,阈值可取 200~300 字节。两个目标不应共用同一数值。
三、形状分流
同一体积的 JSON,压缩后比值随形状在 0.18 至 0.78 之间变化:
| 数据形状 | 收益起点(净响应头) | 稳定比值 |
| 重复键名对象列表 | 约 206 字节 | 0.08 |
| 扁平多字段对象 | 约 161 字节 | 0.19 |
| 数值时间序列 | 约 172 字节 | 0.32 |
| UUID / token 列表 | 约 324 字节 | 0.58 |
| base64 二进制块 | 约 651 字节 | 0.76 |
单一体积阈值不可能同时覆盖两类形状。 随机串、base64 块与已压缩格式(图片、视频、压缩包、字体)应直接排除。
压缩级别:小响应上 1 级与 6 级字节差异在 1 字节量级,CPU 相差约一半。动态接口取 13 级,69 级用于静态资源预压缩(离线完成,运行时零成本)。
四、响应头约束
Content-Encoding与Vary必须成对出现,否则中间缓存将压缩与未压缩响应混发;- 压缩后删除或重算
Content-Length,否则客户端按旧长度读取,连接挂起;常驻内存框架的响应对象复用会放大该问题; - 边缘与应用只允许一层执行压缩,双层压缩徒增 CPU 且破坏长度语义;
- 流式响应关闭压缩,避免首字节时间被攒批推迟。
五、决策与验证实现
import json, re, zlib MTU_NEAR = 1400 BANDWIDTH_NEAR = 300 RANDOMISH = re.compile(r"^[0-9a-fA-F-]{32,}$") def shape_of(payload) -> str: text = json.dumps(payload, ensure_ascii=False) if any(v.startswith("data:") for v in _leaf_strings(payload)): return "binary_like" keys = re.findall(r'"([^"]+)":', text) if len(keys) >= 8 and len(set(keys)) < len(keys) / 2: return "repeated_keys" if all(RANDOMISH.match(v) for v in _leaf_strings(payload)[:5] or [""]): return "random_like" return "text" def _leaf_strings(obj): out = [] if isinstance(obj, dict): for k in obj: out.extend(_leaf_strings(obj[k])) elif isinstance(obj, list): for v in obj: out.extend(_leaf_strings(v)) elif isinstance(obj, str): out.append(obj) return out def should_compress(payload, goal="latency"): raw = json.dumps(payload, ensure_ascii=False).encode("utf-8") limit = MTU_NEAR if goal == "latency" else BANDWIDTH_NEAR if len(raw) < limit: return False, 0 if shape_of(payload) in ("binary_like", "random_like"): return False, 0 return True, (1 if len(raw) < 16 * 1024 else 3)
验收标准:上线前后三个指标同看——下载字节数、P99 延迟、首字节时间。只看字节数会得出与延迟指标相反的结论。
验证命令:curl -s -D - -o /dev/null --compressed <url> | grep -i 'content-encoding\|vary' 与 curl -s -o /dev/null -w '%{size_download} %{time_starttransfer}' --compressed <url>。
结论: 压缩是三角权衡,省字节、省往返、花 CPU 三者最多同时取二。决策入口应是「目标声明 + 形状判断」,而非统一阈值。