暗无天日

=============>DarkSun的个人博客

读:为什么 Trace ID 要用 128 位

原文:Why Should a Trace ID Be 128 Bits?

一个下单请求可能在你的系统里需要经过 20 个服务:API 网关、认证、购物车、库存…… 每个服务干完活还可能调别的服务。当出了问题要排查时,就得把这个请求所有相关的日志找出来。于是就有了trace ID :它在入口处生成一次,然后通过 HTTP 头部(比如 traceparent )一路传下去。

那这个 ID 为什么是 128 位,不是 64 位或 256 位?

为什么不能用自增计数器

直觉上最简单的方案是自增计数器:第 1 个请求 ID 为 1,第 2 个为 2,以此类推。但计数器需要协调:服务 A 和服务 B 都要生成下一个 ID 时,必须找一台中心服务器分配,还得记住之前分到几了。分布式系统中这种协调会变得非常复杂。

所以 trace ID 的生成方式是:各服务自己随机生成,互不通气。既然是随机的,就有碰撞的可能。碰撞能不能接受,取决于 ID 有多大。

生日悖论:直觉为什么会翻车

64 位有 2^64 ≈ 1.8 × 10^19 个可能值。这个数字大到离谱,直觉上随机从中取值不可能碰上。但这个直觉是错的。

想象一个 23 人的班级,两个人同一天生日的概率是多少?换个角度算更容易:先算所有人生日都不同的概率:

P(无重复) = 365/365 × 364/365 × 363/365 × ... × 343/365 ≈ 0.493
P(有重复) = 1 - 0.493 ≈ 0.507

23 人就有超过 50% 的概率出现重复生日。这就是生日悖论,它告诉我们:碰撞概率取决于 配对数量 ,而不是个体数量。

k 个 ID 之间的配对数是 k(k-1)/2,近似 k²/2。当 k 增长 10 倍,配对数会增长 100 倍。把配对数乘上每对碰撞的概率 1/N(N 是 ID 空间大小),再用 1-x ≈ e^(-x) 做近似,得到碰撞概率公式:

P(碰撞) ≈ 1 - e^(-k²/2N)

这个公式把情况分成三段:

  • 安全区 :k²/2N 远小于 1 时,碰撞概率约等于 0
  • 危险区 :k²/2N 接近 1 时,碰撞概率跳到约 63%
  • 必然区 :k²/2N 远大于 1 时,碰撞几乎是肯定的

k(生成的 ID 数量)你控制不了,但 N(ID 空间大小)可以选,位数越大 N 越大。

64 位 vs 128 位:算一笔账

64 位(N = 2^64 ≈ 1.8 × 10^19):

累计 ID 数 (k) k²/2N 碰撞概率
10 亿 0.027 ~2.7%
100 亿 2.71 ~93%
1000 亿 271 ≈100%

一个中等规模公司积累 10 亿个 trace ID 很正常,碰撞率已经 2.7%。到了 100 亿,碰撞率超过 93%,几乎必然会碰上。

128 位(N = 2^128 ≈ 3.4 × 10^38):

即使累计到万亿(10^12)个 ID,k²/2N 也只有约 1.5 × 10^-15。要达到有意义的碰撞风险,需要生成的 ID 数量超过地球上所有可观测平台产生的 trace 之和。

为什么不是 256 位

数学上 256 位更安全,实际中没人这么干。原因很朴素:每个 trace ID 都要在每次服务间调用时通过 HTTP 头传递,在每个 span 旁边存储,在后端建索引,在每行日志里出现。128 位是 16 字节,256 位是 32 字节。128 位的碰撞余量已经远超实际需求,再翻一倍换来的只是所有 trace 数据的存储和带宽开销翻倍,不值当。

而且 128 位恰好等于 UUID 的长度,数据库、编程语言、协议都原生支持,不用额外适配。

分布式系统 : 可观测性 : 概率论 : 生日悖论