TT Lab
开始
学习 学习路径 课程

分布式链路断掉的地方

让指标和追踪回答同一个问题

在 TT Lab 中继续学习

目标

只用跨度转储文件数出请求数、错误数和延迟分布,与指标一侧的暴露格式文件对照,找出名称和值错位的地方并调整埋点。换着用采样器,亲手让同一份数据得出不同的错误率,用采样概率的倒数校正之后,再做一座为每个区间挑选代表性跟踪的桥梁,把规则固化成文件,应用到第二个服务。

为什么重要

指标和跟踪往往是分别做出来的。指标一侧由框架放入路径模板,跟踪一侧手工放入地址,于是九条时间序列的指标,要和三十多个跨度分组面对面。在这种状态下说“就打开这个峰值里的一个慢请求看看”,没有人能回答。更糟的是用跨度来数比例——在错误全部保留、成功每五个只保留一个这种常见的采样策略下,按跨度数出来的错误率会比实际膨胀将近四倍,如果没有把采样概率写在跨度上,连还原的办法都没有。把两个信号用同样的名称、同样的值连起来,并把概率一起写下,个数和比例就从指标里读,跟踪用来找例子,各回各位。指标 SDK 的累积与增量是别的模块的事,这里做的是属性规则文件和对照两个信号的检查器。

步骤

  1. 创建 /root/tp-metrics/collect.py(默认转储路径 /root/tp-metrics/01-spans.jsonl)。依次处理材料 tracelab.tp_metrics.traffic 的 SHOP,为每个请求创建根跨度 GET <주소>(占位符为地址),并在开始跨度时传入五个属性——request.id、request.index、http.target(请求的 path)、http.response.status_code(请求的 status)、user.id(请求的 user)。在跨度内调用 traffic.work(tracer, req)。然后在 /root/tp-metrics/01-from-spans.tsv 中写用制表符分隔的两列四行——requests<탭><루트 스팬 수>、errors<탭><상태 코드 500 이상인 루트 수>、p50_ms<탭><값>、p95_ms<탭><값>(占位符依次为制表符、根跨度数、制表符、状态码 500 以上的根跨度数、制表符、值、制表符、值)。分位数是把根跨度的 duration_ms 按升序排列,从 1 开始数,取第 올림(비율 × 개수)(占位符依次为向上取整、比例、个数)个值,保留到小数点后第三位。
  2. 指标管道给出的文件在 /opt/app/tracelab/tp_metrics/metrics/shop-api.prom 中(Prometheus 暴露格式)。在 /root/tp-metrics/02-mapping.tsv 中写用制表符分隔的三列三行——第一列是指标标签,依次为 handler、code、svc,第二列是在第 1 步转储文件里打算与该标签对应的位置(http.target、http.response.status_code、service.name),第三列是这两者的值是否原样一致,写 yes 或 no。再在 /root/tp-metrics/02-gap.txt 中写两行——metric_series= 后面写该文件中 http_requests_total 的时间序列数,span_groups= 后面写把第 1 步转储文件的根跨度按 http.target 的值分组时得到的组数。
  3. 创建 /root/tp-metrics/aligned.py(默认转储路径 /root/tp-metrics/03-aligned.jsonl)。处理与第 1 步相同的流量,但在根跨度上额外传入 http.route(请求的 route,即路径模板),跨度名称也定为 GET <경로 틀>(占位符为路径模板)。用于调查的 http.target 保持不变。采样器用 samplers.keep_all()。运行之后,在 /root/tp-metrics/03-joined.tsv 中,每个时间序列写一行用制表符分隔的四列——<handler><탭><code><탭><지표 값><탭><그 짝의 루트 스팬 수>(占位符依次为 handler、制表符、code、制表符、指标值、制表符、与其配对的根跨度数),按 handler 升序,相同则按 code 升序。两个数字在每个时间序列上必须相同。
  4. 创建 /root/tp-metrics/sampled.py。从命令行参数接收 nth5 或 errbias,分别用 samplers.every_nth(5) 和 samplers.errors_and_nth(5) 作为采样器,其余与第 3 步完全相同地埋点。默认转储路径是 /root/tp-metrics/04-<인자>.jsonl(占位符为命令行参数)。运行两次,做出 /root/tp-metrics/04-nth5.jsonl 和 /root/tp-metrics/04-errbias.jsonl 之后,连同第 3 步的转储文件一共三个,在 /root/tp-metrics/04-rates.tsv 中写用制表符分隔的四列三行——第一列依次为 full、nth5、errbias(full 是第 3 步的转储文件),后面是 <오류 루트 수><탭><전체 루트 수><탭><비율>(占位符依次为错误根跨度数、制表符、根跨度总数、制表符、比率),比率保留到小数点后第四位。
  5. 两个样本转储文件的根跨度上写有 sampling.probability。一个跨度代表的请求数是该值的倒数。在 /root/tp-metrics/05-adjusted.tsv 中写用制表符分隔的四列两行——第一列依次为 nth5、errbias,后面是 <보정한 오류 수><탭><보정한 전체 수><탭><보정한 비율>(占位符依次为校正后的错误数、制表符、校正后的总数、制表符、校正后的比率),校正后的错误数和总数保留到小数点后第四位,比率也保留到小数点后第四位。再在 /root/tp-metrics/05-limits.txt 中写两行——limit1= 和 limit2= 后面,各写一条即使校正也无法还原的东西(各至少 40 个字)。与第 4 步的 full 比率比较,确认校正能把结果拉回到什么程度。
  6. 创建 /root/tp-metrics/bridge.py(默认转储路径 /root/tp-metrics/06-bridge.jsonl)。像第 3 步那样埋点并留下转储文件之后,重新读取这个转储文件,为每个时间序列挑出最慢的一个根跨度,写成表格。表的路径是把转储路径中的 .jsonl 换成 .tsv 的位置(默认就是 /root/tp-metrics/06-bridge.tsv)。每行是用制表符分隔的四列 <http.route><탭><상태 코드><탭><duration_ms(소수 셋째 자리)><탭><trace_id>(占位符依次为 http.route、制表符、状态码、制表符、duration_ms 保留到小数点后第三位、制表符、trace_id),按 http.route 升序,相同则按状态码升序。再在 /root/tp-metrics/06-limits.txt 中用 limit1=、limit2= 两行,各写至少 40 个字,说明这座桥梁无法回答的东西。
  7. 在 /root/tp-metrics/07-contract.tsv 中写用制表符分隔的三列七行。第一列是跨度属性名称,依次为 http.route、http.response.status_code、service.name、http.target、request.id、user.id、sampling.probability。第二列是该属性在指标里使用的标签名称,不放在指标里的写 -。第三列是 both(在两个信号中用相同的值)或 trace-only(只放在跟踪里)。这张表必须与第 3 步的转储文件和 /opt/app/tracelab/tp_metrics/metrics/shop-api.prom 对照后实际相符——both 的属性必须全部出现在第 3 步转储文件的根跨度上,trace-only 的属性名称不得作为标签出现在指标文件里。
  8. 创建 /root/tp-metrics/pay.py(默认转储路径 /root/tp-metrics/08-pay.jsonl)。用服务名称 pay-api 给材料的 traffic.PAY 埋点,但原样留下第 7 步规则中的属性,根跨度名称为 POST <경로 틀>(占位符为路径模板),采样器是 samplers.keep_all()。然后创建 /root/tp-metrics/agree.py——用 python3 agree.py <스팬덤프> <노출형식파일>(占位符依次为跨度转储文件、暴露格式文件)运行时,逐个时间序列比较指标值和根跨度数,对每个不同的时间序列输出一行 mismatch<탭><handler><탭><code><탭><지표 값><탭><스팬 수>(占位符依次为制表符、handler、制表符、code、制表符、指标值、制表符、跨度数)并以退出码 1 结束;全部相同则输出一行 ok<탭><계열 수>(占位符依次为制表符、时间序列数)并以 0 结束。把检查器在 /opt/app/tracelab/tp_metrics/metrics/pay-api.prom 上运行的输出保存到 /root/tp-metrics/08-agree.txt。

参考

只用跨度转储文件数出请求数和延迟分布

创建 /root/tp-metrics/collect.py(默认转储路径 /root/tp-metrics/01-spans.jsonl)。依次处理材料 tracelab.tp_metrics.traffic 的 SHOP,为每个请求创建根跨度 GET <주소>(占位符为地址),并在开始跨度时传入五个属性——request.id、request.index、http.target(请求的 path)、http.response.status_code(请求的 status)、user.id(请求的 user)。在跨度内调用 traffic.work(tracer, req)。然后在 /root/tp-metrics/01-from-spans.tsv 中写用制表符分隔的两列四行——requests<탭><루트 스팬 수>、errors<탭><상태 코드 500 이상인 루트 수>、p50_ms<탭><값>、p95_ms<탭><값>(占位符依次为制表符、根跨度数、制表符、状态码 500 以上的根跨度数、制表符、值、制表符、值)。分位数是把根跨度的 duration_ms 按升序排列,从 1 开始数,取第 올림(비율 × 개수)(占位符依次为向上取整、比例、个数)个值,保留到小数点后第三位。

子跨度(db.query)也会进入转储文件,所以数请求时只能数没有 parent_id 的行。分位数是第 math.ceil(0.95 * n) - 1 个格子(从 0 开始数的 Python 索引)。耗时会因机器而略有不同,所以评分器会重新读取你的转储文件,用同样的方法计算后比较——不是要你对上绝对数值。重新生成转储文件之前要先删除该文件。

找出指标一侧的名称、值与跨度属性错位的地方

指标管道给出的文件在 /opt/app/tracelab/tp_metrics/metrics/shop-api.prom 中(Prometheus 暴露格式)。在 /root/tp-metrics/02-mapping.tsv 中写用制表符分隔的三列三行——第一列是指标标签,依次为 handler、code、svc,第二列是在第 1 步转储文件里打算与该标签对应的位置(http.target、http.response.status_code、service.name),第三列是这两者的值是否原样一致,写 yes 或 no。再在 /root/tp-metrics/02-gap.txt 中写两行——metric_series= 后面写该文件中 http_requests_total 的时间序列数,span_groups= 后面写把第 1 步转储文件的根跨度按 http.target 的值分组时得到的组数。

暴露格式的一行是 이름{라벨="값",...} 값(占位符依次为名称、标签、标签值、样本值),以 # 开头的行是说明。把两个数字并排放在一起,为什么无法配对一目了然——一边是能数得过来的数,另一边则随着每个地址而增长。service.name 不在跨度的 attributes 里,而在 resource 里。

用相同的名称、相同的值把两个信号连起来

创建 /root/tp-metrics/aligned.py(默认转储路径 /root/tp-metrics/03-aligned.jsonl)。处理与第 1 步相同的流量,但在根跨度上额外传入 http.route(请求的 route,即路径模板),跨度名称也定为 GET <경로 틀>(占位符为路径模板)。用于调查的 http.target 保持不变。采样器用 samplers.keep_all()。运行之后,在 /root/tp-metrics/03-joined.tsv 中,每个时间序列写一行用制表符分隔的四列——<handler><탭><code><탭><지표 값><탭><그 짝의 루트 스팬 수>(占位符依次为 handler、制表符、code、制表符、指标值、制表符、与其配对的根跨度数),按 handler 升序,相同则按 code 升序。两个数字在每个时间序列上必须相同。

指标用标签 handler 和 code 来区分时间序列,跨度则用属性 http.route 和 http.response.status_code 分组。状态码在指标里是字符串,在跨度里是整数,所以配对时要统一成其中一种。用了 keep_all(),所有根跨度上的 sampling.probability 都会写成 1.0,第 5 步会用到这个值。

换了样本,同一份数据会得出不同的错误率

创建 /root/tp-metrics/sampled.py。从命令行参数接收 nth5 或 errbias,分别用 samplers.every_nth(5) 和 samplers.errors_and_nth(5) 作为采样器,其余与第 3 步完全相同地埋点。默认转储路径是 /root/tp-metrics/04-<인자>.jsonl(占位符为命令行参数)。运行两次,做出 /root/tp-metrics/04-nth5.jsonl 和 /root/tp-metrics/04-errbias.jsonl 之后,连同第 3 步的转储文件一共三个,在 /root/tp-metrics/04-rates.tsv 中写用制表符分隔的四列三行——第一列依次为 full、nth5、errbias(full 是第 3 步的转储文件),后面是 <오류 루트 수><탭><전체 루트 수><탭><비율>(占位符依次为错误根跨度数、制表符、根跨度总数、制表符、比率),比率保留到小数点后第四位。

采样器是根据 request.index 和 http.response.status_code 来做决定的,所以如果不在开始跨度时传入这两个属性,采样器就什么也看不到。三个比率中,只有一个会突然变大——想一想是哪个、为什么。转储文件是追加的,运行之前要先删除。

用采样概率的倒数校正重新计数,并写下修不了的东西

两个样本转储文件的根跨度上写有 sampling.probability。一个跨度代表的请求数是该值的倒数。在 /root/tp-metrics/05-adjusted.tsv 中写用制表符分隔的四列两行——第一列依次为 nth5、errbias,后面是 <보정한 오류 수><탭><보정한 전체 수><탭><보정한 비율>(占位符依次为校正后的错误数、制表符、校正后的总数、制表符、校正后的比率),校正后的错误数和总数保留到小数点后第四位,比率也保留到小数点后第四位。再在 /root/tp-metrics/05-limits.txt 中写两行——limit1= 和 limit2= 后面,各写一条即使校正也无法还原的东西(各至少 40 个字)。与第 4 步的 full 比率比较,确认校正能把结果拉回到什么程度。

校正后的数是把每个根跨度的 1 / sampling.probability 相加得到的值。子跨度上没有写概率,所以自然只数根跨度。errbias 一侧校正之后会非常接近第 4 步的 full 比率,nth5 一侧本来也不会差得太远。在思考无法还原的东西时,想一想“样本里一个都没有进来的组合”和“分位数”。

为每个区间挑选代表性跟踪,做一座可以从指标跳过去的桥梁

创建 /root/tp-metrics/bridge.py(默认转储路径 /root/tp-metrics/06-bridge.jsonl)。像第 3 步那样埋点并留下转储文件之后,重新读取这个转储文件,为每个时间序列挑出最慢的一个根跨度,写成表格。表的路径是把转储路径中的 .jsonl 换成 .tsv 的位置(默认就是 /root/tp-metrics/06-bridge.tsv)。每行是用制表符分隔的四列 <http.route><탭><상태 코드><탭><duration_ms(소수 셋째 자리)><탭><trace_id>(占位符依次为 http.route、制表符、状态码、制表符、duration_ms 保留到小数点后第三位、制表符、trace_id),按 http.route 升序,相同则按状态码升序。再在 /root/tp-metrics/06-limits.txt 中用 limit1=、limit2= 两行,各写至少 40 个字,说明这座桥梁无法回答的东西。

之所以要从转储路径推出表的路径,是有原因的——评分器会在自己的临时目录里把你的程序再运行一次,如果把表写到固定路径,就会覆盖你提交的文件。挑选代表这件事本身,标准里称为 exemplar,但这个环境里没有 Prometheus,无法真正存储或查询。写限制时,想一想“普通的请求”和“被样本丢掉的请求”。

把哪些属性在两个信号里共用,固化成规则文件

在 /root/tp-metrics/07-contract.tsv 中写用制表符分隔的三列七行。第一列是跨度属性名称,依次为 http.route、http.response.status_code、service.name、http.target、request.id、user.id、sampling.probability。第二列是该属性在指标里使用的标签名称,不放在指标里的写 -。第三列是 both(在两个信号中用相同的值)或 trace-only(只放在跟踪里)。这张表必须与第 3 步的转储文件和 /opt/app/tracelab/tp_metrics/metrics/shop-api.prom 对照后实际相符——both 的属性必须全部出现在第 3 步转储文件的根跨度上,trace-only 的属性名称不得作为标签出现在指标文件里。

划分的标准是基数。指标的标签,值的种类数会直接乘到时间序列数上,所以放进地址或用户标识符,时间序列就会爆炸。相反,跟踪的目的是找出一个请求,所以这样的值必须放在那里。service.name 在跨度的 resource 里,但仍然是 both——因为它是把两个信号按服务聚合起来的键。

把规则应用到第二个服务,并用检查器对照

创建 /root/tp-metrics/pay.py(默认转储路径 /root/tp-metrics/08-pay.jsonl)。用服务名称 pay-api 给材料的 traffic.PAY 埋点,但原样留下第 7 步规则中的属性,根跨度名称为 POST <경로 틀>(占位符为路径模板),采样器是 samplers.keep_all()。然后创建 /root/tp-metrics/agree.py——用 python3 agree.py <스팬덤프> <노출형식파일>(占位符依次为跨度转储文件、暴露格式文件)运行时,逐个时间序列比较指标值和根跨度数,对每个不同的时间序列输出一行 mismatch<탭><handler><탭><code><탭><지표 값><탭><스팬 수>(占位符依次为制表符、handler、制表符、code、制表符、指标值、制表符、跨度数)并以退出码 1 结束;全部相同则输出一行 ok<탭><계열 수>(占位符依次为制表符、时间序列数)并以 0 结束。把检查器在 /opt/app/tracelab/tp_metrics/metrics/pay-api.prom 上运行的输出保存到 /root/tp-metrics/08-agree.txt。

检查器不需要 otel,所以写成能在系统 python3 上运行的样子。只在指标一侧有的时间序列和只在跨度一侧有的时间序列,都算错位,所以要遍历两个键集合的并集。评分器会把你的检查器也在故意写错的 /opt/app/tracelab/tp_metrics/metrics/pay-api-broken.prom 上运行,所以不能按文件名或特定值来判定。