Kitex RPC 经 Envoy 双跳 sidecar 的端到端时延归因实验。
在 Kitex、netpoll、Envoy 三处源码插桩,按请求打点,把一次 RPC 的时间拆到微秒级: 每一段花在哪个进程、是在算还是在等、是在排队还是在网络上。
[机器 A] kitex-client ──UDS──> envoy-out ─┐
│ TCP 跨机
[机器 B] ┌── envoy-in <─┘
└──UDS──> kitex-server
「上了 mesh 要多付多少钱?」 直连 / 只加出向 sidecar / 完整双跳,三级跨机实测(p50,payload 64 B):
| 端到端 | 比直连多 | |
|---|---|---|
| 直连(无 mesh) | 147.3 µs | — |
| 单跳(出向 sidecar) | 163.2 µs | +15.9 µs(+10.8 %) |
| 双跳(完整 mesh) | 228.2 µs | +80.9 µs(+54.9 %) |
几条不靠猜的结论:
- 两跳严重不对称,入向比出向贵 4.1 倍(65.0 vs 15.9 µs)。 但这是机器不是路径 —— 把两个 Envoy 放同一台机器上,各阶段耗时逐项相等 (编码 5.0 vs 5.0 µs)。做了对照实验才敢这么说。
- 加出向 sidecar 之所以便宜,是因为它替客户端省了钱:客户端不再直接读跨机 TCP, 改读本机 UDS,goroutine 调度延迟从 11.4 µs 降到 2.8 µs,写 socket 从 7.8 µs 降到 1.8 µs。
- 业务 handler 只有 240 ns —— 测的几乎全是框架与传输开销。
- 「等待对端」不再是黑盒:拆成 真等待 / poller 排队 / readv / 缓冲入队 / goroutine 调度 五段。
完整结论见 docs/test-report.md, 原始 merge 输出在 docs/results/2026-08-07/。
| 你想 | 看 |
|---|---|
| 第一次复现,从零到出数据 | docs/runbook-reproduce.md —— 假定网络已通,从下载 Go 一路到分析出图。每步都带 2026-08-07 全量复现的真实耗时与输出 |
| 已跑通过,日常跑一轮实验 | docs/runbook-operations.md —— 构建→运行→采集→分析 |
| 搭环境(镜像、代理、bazel 取舍)、网络排障 | docs/runbook-build-environment.md |
| 看实测结论 | docs/test-report.md |
| 了解每个点位的含义 | docs/probe-points.md |
| 知道还有什么测不到 | docs/probe-coverage-audit.md |
| 搞清加压器与 kitex-benchmark 的关系 | docs/demo-vs-kitex-benchmark.md —— 改了哪 62 行、为什么非改不可、哪些数字可比 |
| 看包大小 × 并发的归因矩阵 | docs/bench-matrix-2026-08-11.md —— 含 1GbE 把矩阵卡掉一半的实测 |
| 专门评测网卡 | docs/runbook-nic-benchmark.md |
| 看设计与取舍 | docs/specs/ |
本仓库只含文档、demo、工具与脚本。插桩本身在另外三个仓库:
| 仓库 | 分支 | 内容 |
|---|---|---|
| envoy | feat/thrift-ttheader-transport |
可上游 —— 让 thrift_proxy 原生识别 Kitex 的 TTHeader(magic 0x1000),含 47 个单测 |
| envoy | wip/kitex-e2e-probe |
基于上一分支追加侵入式打点,每跳 24 个点位,不上游 |
| kitex | feat/detailed-trace-events |
11 个细粒度事件 + 承接 netpoll 读写时间戳的槽位 |
| netpoll | feat/meshlab-read-probe |
读路径 5 点 + 写路径 4 点,客户端与服务端两侧都有 |
| kitex-benchmark | feat/meshlab-probe |
加压器:对 3 个上游文件共 62 行改动 + 1 个适配层文件,runner/、perf/ 一行未动 |
一条被采样的请求在跨机双跳下产生 107 个去重点位名 / 约 119 个事件。
加压器就是 kitex-benchmark 的 kitex-echo(2026-08-11 起)。归因结论的可信度 有一半压在加压器上 —— 自己写一份的话并发模型、限速、预热、分位数算法都是我们的 实现,别人无从核对。改动清单与「为什么每一处非改不可」见 docs/demo-vs-kitex-benchmark.md。
所有改动默认关闭 = 上游行为(不给
-proto就是 Framed,不给-trace就不挂探针),所以同一个二进制既是我们的加压器,也是干净的公认基准。
demo/ Kitex 客户端与服务端,含 probe 包(Tracer + 落盘)
tools/merge/ 把各节点 NDJSON 合并成时序视图并做归因分析
tools/ fixturegen(造 TTHeader 测试向量)、udsdump
envoy-conf/ 六份 Envoy 配置:单跳(同机/跨机)、双跳(同机/跨机)
scripts/ 拓扑拉起、压测、构建环境辅助
docs/ 见上表
四种拓扑怎么拉起:
| 拓扑 | 命令 | 用途 |
|---|---|---|
| 直连(跨机) | TOPO=direct ./scripts/run-cross-machine.sh start |
归因基线:没有 mesh 时是多少 |
| 单跳(跨机) | TOPO=single ./scripts/run-cross-machine.sh start |
只有出向 sidecar |
| 双跳(跨机) | ./scripts/run-cross-machine.sh start |
完整 mesh,真实拓扑 |
| 同机降级 | ./scripts/run-single-hop.sh / run-two-hop.sh / run-direct.sh |
只有一台机器时验证链路通不通 |
做归因阶梯必须用跨机的那三级。同机跑双跳的话,每加一跳就多一组 Envoy 进程挤同一批 CPU,三级的资源竞争程度不同,级差里混着 CPU 争抢,不能归给「多一跳」。
TTHeader 不是 Apache THeader。 magic 是 0x1000 而非 0x0FFF,且 THeader 里的每个
varint 在 TTHeader 里都是定长整数 —— 两者线格式不兼容。所以是新写一个 transport,
不是改现有的。
时钟纪律写进了工具而不是文档。 两台机器实测差 16.34 秒,靠人自觉"不跨机相减"不现实。
merge 强制:同机内才允许相减,跨机只能用差值法导出往返,并提供 --inject-skew
证明分析对偏斜免疫(注入任意偏移后各段时长逐位不变)。
未采样路径必须近乎零开销,否则打点本身会主导压测结果。2026-08-10 三组交错实测 (探针不激活 / 激活但不采样 / 全采样,c=1)差异全部落在噪声内 (+0.9 % / −1.0 %,而同一配置的轮间波动最大到 9.7 %)。 这只证明「低于本底噪声」,不等于零开销 —— c=1 的端到端 p50 被跨机等待主导, 对几微秒的插桩开销本就不敏感。压测归因仍建议用 1 %~5 % 采样, 未决项见 test-report.md §7。
整套插桩的下界是 socket 系统调用 —— 网卡、驱动、中断、协议栈全在盲区。 平时无所谓,但要评测网卡时这恰好是唯一该看的部分,补法见 probe-coverage-audit.md §五。