除 traceparent 外一同流动的两样东西
一句话总结
同一个请求上,除了 traceparent,还会一起流动 baggage 和 tracestate。一个把我们定义的业务值运往下游,一个是跟踪工具之间互相传递信息的位置。两者的共同点是,都是从外部传进来的字符串。
为什么需要它
打开支付跟踪,问“这个请求属于哪个租户”的瞬间,大多数团队就卡住了。根跨度上带着租户,但往下六跳的数据库跨度上却没有。光看那个跨度,无法知道是哪个客户的慢查询,于是人只能沿着父级往上找到根,再从那里读取值。一条跟踪能做到,但要聚合一万条就做不到。
baggage 正是为这个位置而设。放进去一次,从该请求向外延伸的所有下游调用的请求头里就都会带上同样的值。不过这里有两个陷阱。第一,放进 baggage 的值不会自动变成跨度属性。它只是存在于上下文中,每个服务必须把它取出来,抄写到自己的跨度上,才能成为可查询的数据。第二,这个请求头会被带到所有下游请求。一个请求调用下游四十次,同样的字节就会发出四十次。
tracestate 性质不同。它不是运送业务值的位置,而是跟踪工具记录自身状态的位置。两者混用的话,我们的值放不进规范所保证的大小范围内,会被悄悄截掉。
工作原理
W3C Baggage 规范把请求头定义为 key=value 列表。键是 RFC 7230 中定义的 token,值如果超出规定的 ASCII 范围,就必须做百分号编码。大小规则见 Baggage 3.3.2 Limits——只要生成的 baggage 字符串不超过 64 个条目、不超过 8192 字节,平台就必须传递所有条目。超过这个条件后,丢弃哪些条目由实现决定,规范连顺序都没有规定。也就是说,64 个和 8192 字节不是上限,而是保证中断的节点。
Python SDK 的 W3CBaggagePropagator 还在此之上加了自己的上限。单个条目超过 4096 个字符就去掉该条目,整体超过 8192 字节就在那里截断。重要的是,不会抛出异常。把一个 9 千字符的值放进 baggage,程序会悄无声息地运行,请求头发出去时只是少了那个条目。当下游收到“有时值是空的”这类反馈时,就该怀疑这里。
tracestate 的规则见 Trace Context 3.3.1.5。列表最多 32 个条目,值是可打印 ASCII,最多 256 个字符(不能有逗号和等号),厂商合计至少要传递 512 个字符。需要截断时,要整条丢弃条目,但先丢弃超过 128 个字符的条目,之后再从末尾开始丢弃。并且,如果我们加入或修改了自己的条目,要把该条目移到最左边——因为最左边是“刚刚写入 traceparent 的系统”的位置。我们没有动过的条目,顺序必须保持原样,其他工具的关联才能保留下来。
读取 traceparent 四个字段的方法,本课程前面的实验已经讲过。这里不再拆分那一行——只需记住规范中的一句话:tracestate 如果没有 traceparent 单独到来,就必须丢弃。
信任边界是最后一块。在公开 API 前面收到的 baggage 是别人写的字符串。原样抄成跨度属性,指标的基数就由别人来决定;原样往下游放行,我们的内部服务就会把它当作我们的值来读。所以在边界上要用白名单来接收——只收认识的键、只收规定形状的值、只收到规定的个数。规范也朝同一个方向写道:baggage 中不要放机密,或者让跨越信任边界的请求不携带 baggage。认证课程中的上下文边界实验处理的是发出一侧,按目的地选择放什么;这里要做的过滤器处理的是进来的一侧——决定别人发来的东西中,哪些可以当作我们的东西接受。
在现场相遇的样子
有个团队为了调试方便,把请求体的摘要放进了 baggage。开发环境运行良好,到了生产环境,只有下游调用多的路径延迟变长了。一个值是 800 字节,那个请求调用了下游三十次。每个请求要多往网络上发 24KB,在触到代理的请求头大小限制的路径上,还出现了 502。往 baggage 里放什么,要按“这个值是不是所有下游都会用”来划分。
另一起事故在另一头。在接收合作方请求的入口处,把收到的 baggage 原样抄成了跨度属性,其中有一个是每个请求都不同的 ID。一个属性让时间序列分裂成几十万条,存储成本在几天之内涨了几倍。在入口处加上白名单、连值的形状都检查之后,当天就止住了。从外面来的上下文不是数据,而是输入。
下一项实验要做什么
放入 baggage 并往下一个服务传递,用两个跨度亲自确认它不会自动变成跨度属性。测量四组候选的请求头字节数,看哪个超出了规范的保证范围,也看看 9 千字符的值是怎样悄悄消失的。然后对来自信任边界之外的 baggage 亲手做一个白名单过滤器,把留下了什么、丢弃了什么记录到跨度上。最后按规范读取 tracestate,做出一个转换:保留进来的条目顺序,同时把我们的条目放到最前面,把它固化成规则文件,再应用到第二个服务上。