一次网购下单一盒咖啡豆,包裹从仓库出发,经过分拨中心、干线运输、城市网点,最后快递员送到你手上。你在物流 App 上看到的每一个节点——“已下单”“已打包”“运输中”“派送中”“已签收”——对应着仓库、货车、网点和快递员一次次接力。假如咖啡豆迟迟没送到,你打电话给客服,客服能一键调出包裹经过的所有中转站,立刻判断是卡在哪个仓库没出,还是快递员路线不对。这种能力,微服务系统里也有,叫链路追踪。它为每一个请求生成一个全局唯一的“物流单号”,请求每经过一个微服务,就留下一条“中转记录”。当请求变慢或出错时,工程师顺着单号把所有中转记录拼成一张完整的“运输地图”,一眼看出瓶颈在哪个环节。本文就是教你搭建这套“物流追踪系统”。

岗位关联

学完链路追踪,你将掌握微服务排障的“总控台”:慢请求定位从小时级压缩到分钟级,成为团队可观测性建设的中坚力量。

目录
  1. 可视化演示
  2. 1. 追踪基础:从物流单号到 Trace 与 Span
  3. 2. 上下文传播:让物流面单一路随行
  4. 3. 采样策略:不为每个包裹都全程录像
  5. 4. OpenTelemetry:行业统一面单标准
  6. 5. Baggage 透传:面单上的备注栏
  7. 6. 关联排障:轨迹、通话与监控录像齐上阵
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示Trace/Span瀑布图、上下文跨进程传递机制及采样策略与性能开销权衡。

本节配有交互动效(进入视口后自动加载,约 3KB)。

Trace与Span模型 请求经过网关/API/DB/缓存四服务 每个Span记录服务耗时与父子关系 瀑布图展示完整调用链路时序

1. 追踪基础:从物流单号到 Trace 与 Span

你在网上买了一盒咖啡豆,打开物流 App,能看到“已下单、仓库已打包、干线运输中、已到本地网点、派送中、已签收”一整条时间线。这条时间线的背后,是包裹每经过一个中转站就被扫描枪记录一次。把所有这些“中转记录”串起来,你就知道这盒咖啡豆此刻在哪、经过了哪些地方。微服务里的链路追踪,做的就是同样的事——只不过“包裹”是用户的请求,“中转站”是后台一个个微服务。

一个用户请求进入系统后,通常会经过 API 网关、用户服务、订单服务、库存服务等多个节点。追踪系统把这整段旅程定义为一个 Trace(链路),把其中每一个服务处理的一小段定义为 Span(片段)。Span 之间不是孤立的:服务 A 调用了服务 B,那么 B 的 Span 就是 A 的 Span 的子节点,这种父子关系把所有 Span 组织成一棵“调用树”。

以最常见的追踪语义为例,一个 Span 至少包含六类信息:全局链路 ID(traceId)、当前片段 ID(spanId)、父片段 ID(parentSpanId)、开始与结束时间戳、以及随附的属性标签。写成一个结构就是:

每个 Span 都像物流系统里的一次“扫描记录”:单号是谁、上一站是哪、几时几分进的站、到了哪个站点。当客服想查一单快递为什么延误,只要按物流单号把所有扫描记录按时间排开,堵在哪一目了然——链路追踪就是给工程师的那张“物流全程地图”。

span={traceId, spanId, parentSpanId, tstart, tend, attributes}\text{span} = \{\text{traceId},\ \text{spanId},\ \text{parentSpanId},\ t_{\text{start}},\ t_{\text{end}},\ \text{attributes}\}
  1. traceId(链路标识)一次完整请求的全局唯一编号,入口服务生成一次,整条链路的所有 Span 共用。
    traceId\text{traceId}
  2. spanId(片段标识)当前 Span 自己的唯一编号,用于区分同一链路内的不同片段。
    spanId\text{spanId}
  3. parentSpanId(父片段标识)记录调用方 Span 的编号,为空表示这是链路根节点。通过它把所有 Span 串成树。
    parentSpanId\text{parentSpanId}
  4. t_start 与 t_end(时间戳)记录片段开始与结束的时刻,两者相减即该服务处理耗时。
    tstart, tendt_{\text{start}},\ t_{\text{end}}
  5. attributes(标签属性)附带的元数据,如服务名、HTTP 方法、状态码,帮助排障时快速筛选。
    attributes\text{attributes}

2. 上下文传播:让物流面单一路随行

物流包裹能一单到底,靠的是贴在包裹上的面单。包裹每到一个中转站,分拣员不需要重新贴一张新单,只要扫面单上的条码,就知道它从哪来、要往哪去。面单必须跟着包裹走,这是物流系统能串起来的前提。在微服务链路里,这份“面单”就是追踪上下文(Trace Context)。

如果每个服务各自生成一套编号,链路就断了。所以 Trace ID 必须在请求入口(如 API 网关)统一生成,然后通过 HTTP 请求头等方式传递给下游。目前业界最通用的是 W3C 的 traceparent 标准,它规定了上下文在跨服务调用时的传递格式。

一个标准的 traceparent 头包含四个字段,以短横线分隔。不同语言的框架(如 Spring Cloud 的 Sleuth、Go 的 otel 中间件)都会自动完成“从请求头读取上下文、调用下游时注入上下文”这两个动作,业务代码基本无感。在进程内部,上下文常存放在 ThreadLocal 中,线程池场景需要显式传参或用装饰器传递。

面单上那一串条码,就是 Trace ID 的化身。只要面单不丢、每一站都扫码,快件的轨迹就连得起来。链路追踪也一样——只要每个服务都诚实地把 traceparent 往下传,整条调用链就能从入口连到末端。

traceparent=ver−trace-id−parent-id−flags\text{traceparent} = \text{ver}-\text{trace-id}-\text{parent-id}-\text{flags}
  1. ver(版本号)当前标准的版本号,绝大多数实现固定为 00,保证向前兼容。
    ver\text{ver}
  2. trace-id(全链路 ID)32 位十六进制字符串,整条链路唯一,是聚合所有 Span 的“物流单号”。
    trace-id\text{trace-id}
  3. parent-id(父 Span ID)调用方 Span 的 16 位十六进制 ID,用于还原父子层级。
    parent-id\text{parent-id}
  4. flags(标记位)最低一位表示该请求是否已被采样,采样决策会跟着请求传给下游。
    flags\text{flags}

3. 采样策略:不为每个包裹都全程录像

快递公司不会给每件包裹都配一个全程跟拍的摄像师,那样成本会高到破产。现实做法基本是:普通包裹抽查录像,贵重或已出问题的包裹才申请全程重点跟拍。微服务的高峰流量同样如此——每秒几千上万个请求,如果每个请求的 Trace 都完整落库,存储和传输开销会迅速爆炸。

采样(Sampling)就是为了在“数据完整度”和“成本”之间做取舍。常见策略有三种:全量采样适合低流量或关键链路;固定比例采样按设定概率随机抽取,实现简单;动态采样则根据系统状态自动调整采样率。

头部采样(Head-based)在请求入口就决定“这单要不要全程录像”,采样信号通过 flags 位传给下游,各服务不再单独决策,避免“上下游各采一半”导致链路断裂。更聪明的做法是动态调整采样率:当一个窗口内请求量暴增时,自动降低采样率以保护存储;当错误率升高时,自动提高采样率以便收集足够多的故障样本。

采样就像快递公司的那套“抽查+重点跟拍”策略:平时 10% 的包裹有录像就够了,一旦配送异常率飙升,立刻把抽查比例提到 100%,把每个异常包裹的全程轨迹都留下。这套机制让追踪系统既省钱,又不至于在关键时刻“瞎眼”。

padaptive=min⁡(1, NtargetQtotal)p_{\text{adaptive}} = \min\left(1,\ \frac{N_{\text{target}}}{Q_{\text{total}}}\right)
  1. N_target(目标样本数)在当前时间窗口内希望保留多少条 Trace 的配额,由运维预算决定。
    NtargetN_{\text{target}}
  2. Q_total(总请求数)当前时间窗口内的请求总量,来自入口服务的实时计数器。
    QtotalQ_{\text{total}}
  3. min(1, ·)(上限约束)采样率最高为 100%,当请求量小于配额时自然回落到全量采集。
    min⁡(1, ⋅)\min(1,\ \cdot)
  4. p_adaptive(动态采样率)随请求量波动自适应调整的概率值,是“成本与完整度”的动态平衡点。
    padaptivep_{\text{adaptive}}

4. OpenTelemetry:行业统一面单标准

以前不同快递公司各自印刷自己的面单,A 家的单号拿到 B 家去查,B 家系统根本不认识。后来行业统一了面单标准(如条码格式),不同公司之间才能互通互认。微服务世界也走过同样的弯路:早期有 Zipkin、Jaeger、SkyWalking 等多种追踪实现,各用各的数据格式,接入方被厂商绑定。

OpenTelemetry(简称 OTel)是 CNCF 主推的可观测性标准,已成为事实上的“统一面单”。它提供一套中立的 API、SDK 和数据模型,让应用只需面向 OTel 埋点,就能把 Trace 数据导出到任何兼容的后端(如 Jaeger、Datadog、自建 Collector)。

OTel 的设计核心是 API 与 SDK 分离:API 定义“往面单上写哪些格子”,SDK 负责“把面单送到转运中心”(通过 Exporter 导出到后端)。接入方式有两种——自动埋点通过语言 Agent 或中间件拦截库自动生成 Span,适合快速覆盖;手动埋点则在关键业务代码中显式创建 Span,适合标记业务语义。

有了 OTel 这张“行业标准面单”,你不再关心后端是哪个快递公司——面单格式统一了,想换数据平台,只改 Exporter 配置即可,业务代码一行都不用动。

span=tracer.startSpan(name)\text{span} = \text{tracer.startSpan}(\text{name})
propagation.inject(ctx, carrier)\text{propagation.inject}(\text{ctx},\ \text{carrier})
  1. tracer.startSpan(name)从全局 Tracer 创建并启动一个 Span,传入操作名如“查询订单”。
    span=tracer.startSpan(name)\text{span} = \text{tracer.startSpan}(\text{name})
  2. propagation.inject(ctx, carrier)将上下文中的 traceId 等写入请求载体(如 HTTP 头),实现跨进程传递。
    propagation.inject(ctx, carrier)\text{propagation.inject}(\text{ctx},\ \text{carrier})
  3. span.end()业务处理结束后调用,记录结束时间并上报到导出器。
    span.end()\text{span.end}()

5. Baggage 透传:面单上的备注栏

快递面单上除了条码,还有一小块“备注栏”:客户会写“易碎品,请轻放”“到货放驿站”“货到付款”。这些备注不参与分拣路由,但每一站的工作人员都能看到,并据此调整处理方式。链路追踪里也有这么一块“备注栏”,叫作 Baggage。

Baggage 是 W3C 标准中与 traceparent 并列的另一个头(baggage),它允许调用链上传递一组键值对,比如 userId、灰度版本号、请求来源渠道。与 Span 属性不同,Span 的标签只属于单个 Span,而 Baggage 会附着在 Trace 上下文上,一路透传给所有下游服务。

一个典型 baggage 头长这样:userId=9527; grayVersion=v2; channel=mobile。下游服务可以直接读取这些字段做业务逻辑,例如灰度发布时代理层据此把请求路由到灰度机器。使用 Baggage 要克制:它会被复制到每个下游 Span 上,如果塞入大字段(如完整请求体),会让每条 Trace 都膨胀数倍。

备注栏写什么、写多少,直接决定各站点工作人员要额外读多少信息。聪明的做法是只写“影响处理动作的关键备注”,Baggage 也同理:只透传必要的业务标识,别把整页合同贴面单上。

baggage=k1=v1; k2=v2\text{baggage} = k_1 = v_1;\ k_2 = v_2
  1. k_i(键)业务字段名,如 userId、grayVersion,需遵循键名规范。
    kik_i
  2. v_i(值)字段值,需 URL 编码以兼容 HTTP 头字符集。
    viv_i
  3. 分号分隔多个键值对之间用分号加空格隔开,一个头传递整份备注。
    ;\
  4. 随上下文传递Baggage 作为上下文的一部分,与 traceparent 一起被注入到每个下游请求。
    baggage∈Context\text{baggage} \in \text{Context}

6. 关联排障:轨迹、通话与监控录像齐上阵

客服查一个“已签收但用户没收到”的投诉时,会同时看三样东西:物流轨迹、快递员通话记录、签收底单照片。只看其中任何一样都很难定责,但三者拼在一起,基本就能还原真相。可观测性世界的“三样东西”,就是指标(Metric)、日志(Log)和链路(Trace)。

Trace 擅长回答“是哪一跳慢”,但它不擅长回答“这个服务整体是不是在变慢”——这需要指标;而“那一跳上到底发生了什么错误”——这需要日志。把三者关联起来的粘合剂,正是 Trace ID:日志中打印 trace_id 和 span_id,查询单条 Trace 时即可关联出该 Span 覆盖的时间段内的所有日志。

通用的排障工作流是:先看 RED 指标确认异常是否真实存在——Rate(请求速率)、Errors(错误率)、Duration(平均耗时);接着进 Trace 瀑布图找到耗时最宽的 Span;最后点开这个 Span 关联的日志,看到具体堆栈。整个过程如同“指着一份物流轨迹上最久的一站,再调出那个仓库的监控录像”。

这套“指标发现面、Trace 定位点、日志看清根因”的组合拳,就是现代可观测性工程的核心打法。别再一个人翻十几个服务的日志了——先让 RED 指出方向,再用 Trace 下钻到那一站,最后让日志说清真相。

Rate=RΔt,Errors=ER,Duration=∑TiR\text{Rate} = \frac{R}{\Delta t},\quad \text{Errors} = \frac{E}{R},\quad \text{Duration} = \frac{\sum T_i}{R}
  1. Rate(请求速率)统计窗口内总请求数 R 除以时间 Δt,反映系统当前流量高低。
    Rate=RΔt\text{Rate} = \frac{R}{\Delta t}
  2. Errors(错误率)错误请求数 E 除以总请求数 R,反映失败占比是否恶化。
    Errors=ER\text{Errors} = \frac{E}{R}
  3. Duration(平均耗时)所有请求耗时总和除以请求数,代表平均响应时间,是慢请求的第一信号。
    Duration=∑TiR\text{Duration} = \frac{\sum T_i}{R}
  4. RED 与 Trace 协同RED 从整体面上发现异常,Trace 下钻到具体 Span 定位异常点。
    RED→Trace\text{RED} \rightarrow \text{Trace}

AI 应用场景

慢请求瓶颈定位

阶段:可观测性建设 / 线上排障

解决的问题:传统排障中只能逐台服务器 grep 日志,请求的调用链分散在多个服务的日志文件里,无法快速知道“整体慢在哪一环”。链路追踪用一条 Trace 把所有 Span 拼成瀑布图,一眼定位高耗时服务。

具体案例:某电商大促期间,订单提交接口 P99 从 200ms 涨到 1.2s。工程师从追踪面板看到调用链为“网关 → 用户 → 订单 → 库存”,其中“库存扣减”服务对应 Span 占了 800ms;点开该 Span 关联日志,发现 Redis 热点 key 竞争,半小时内通过集群拆分和限流将 P99 拉回 300ms 以内。

跨团队故障定责

阶段:故障复盘 / 责任界定

解决的问题:一个转账请求跨账务、风控、消息推送三个团队的服务,线上偶发失败,三个团队互相推诿。传统排查靠各自日志截图对时间线,效率极低。链路追踪用统一 Trace ID 串起全链路,让故障点无处遁形。

具体案例:通过 Trace 发现“风控服务”的 Span 状态码为 500,其日志记录到第三方风控接口超时。数据摆在面前,问题团队再无争议,直接推动第三方限流整改。

灰度发布验证

阶段:发布变更 / 变更验证

解决的问题:灰度发布时,如何确认流量真的打到了灰度机器?传统做法看网关日志和机器访问日志对账,繁琐易漏。用 Baggage 透传 grayVersion 字段,直接按该字段筛选 Trace,一屏看清。

具体案例:运维发布时让 5% 流量带 grayVersion=v2,在追踪面板按 baggage 字段过滤,确认请求确实路由到灰度机器;验证通过后逐步放大比例,回滚时再确认 Baggage 透传消失。

Python 演示

python83 行
import time span_seq = 0  def next_id():    global span_seq    span_seq += 1    return f"span{span_seq}"  class Span:    def __init__(self, name, parent_id=None, trace_id=None):        self.name = name        self.span_id = next_id()        self.trace_id = trace_id or self.span_id        self.parent_id = parent_id        self.start = time.time()        self.end = None        self.children = []        self.attributes = {}     def finish(self):        self.end = time.time()     def duration_ms(self):        if not self.end:            return 0.0        return (self.end - self.start) * 1000     def report(self, depth=0):        indent = "  " * depth        print(f"{indent}span={self.name} id={self.span_id} trace={self.trace_id} "              f"cost={self.duration_ms():.2f}ms")        for child in self.children:            child.report(depth + 1)  class Tracer:    def __init__(self, sample_rate=0.8):        self.sample_rate = sample_rate        self.sampled_count = 0     def should_sample(self, trace_id):        return int(trace_id, 16) % 100 < self.sample_rate * 100     def start_root(self, name, trace_id):        if not self.should_sample(trace_id):            return None        root = Span(name, trace_id=trace_id)        self.sampled_count += 1        return root  def call_service(parent, name, seconds):    span = Span(name, parent_id=parent.span_id, trace_id=parent.trace_id)    time.sleep(seconds)    span.finish()    parent.children.append(span)    return span  def main():    total = 5    tracer = Tracer(sample_rate=0.8)    for i in range(1, total + 1):        trace_id = f"f{i}000"        root = tracer.start_root(f"req-{i}", trace_id)        if root is None:            print(f"req-{i} trace={trace_id} not sampled")            continue        gateway = call_service(root, "gateway", 0.005)        user = call_service(gateway, "user-service", 0.008)        order = call_service(gateway, "order-service", 0.006)        root.finish()        root.report()        print("---")    print(f"sampled {tracer.sampled_count}/{total} "          f"rate={tracer.sampled_count / total:.0%}")  if __name__ == "__main__":    main()
目的:用约 80 行代码实现一个“极简链路追踪模拟器”,演示 Trace 树构建、父子 Span 挂接和头部采样的核心逻辑。
  1. def should_sample(self, trace_id):头部采样判决:对 trace_id 的十六进制值取余 100,决定这条链路是否留存。
  2. root = Span(name, trace_id=trace_id)入口创建根 Span,trace_id 全链路唯一。
  3. span = Span(name, parent_id=parent.span_id, trace_id=parent.trace_id)下游 Span 必须继承父 Span 的 trace_id,并记录 parent_id。
  4. parent.children.append(span)把子 Span 挂到父 Span 的 children 下,形成调用树。
  5. call_service(gateway, "user-service", 0.008)模拟一次服务调用,用 sleep 模拟耗时。
  6. root.report()递归打印整棵调用树,让工程师“一眼看清”整条 Trace。
  7. tracer = Tracer(sample_rate=0.8)设定 80% 采样率,样本越多存储成本越高,需要权衡。

常见误区

误区我一直以为要完整观测所有请求,必须 100% 采集 Trace,否则漏掉的那一个可能刚好是故障请求。所以我把采样率开到 100%,结果存储成本爆炸,查询也变慢了。

正解全量采集是典型的成本陷阱。Trace 的目的是定位问题模式,10%-30% 采样率配合动态提升(错误率升高时自动提高采样),足以覆盖绝大多数故障。链路追踪的价值在于“有代表性样本”而非“每一笔都留底”,就像快递公司抽查录像而不是给每个包裹都配全程跟拍。

为何常见:因为“怕漏”的心理,加上对采样统计意义不熟悉,以为数据越全越安全。

误区我在项目里给每个服务都单独接了追踪 SDK,每个服务自己生成 traceId,日志里也有 trace_id,应该算接好链路追踪了吧?

正解如果每个服务各自生成 traceId,那它们只是“各自为政”的日志,无法串成一条完整链路。链路追踪的前提是 traceId 在入口统一生成,并通过 HTTP 头或消息头传递到所有下游节点。日志中打印 trace_id 只是“关联”的一步,前面还缺了上下文传递与 Span 上报。

为何常见:分不清“日志里带个 ID”和“完整链路追踪”的区别,忽略了上下文传播机制才是 Trace 真正串起来的关键。

误区Span 只记录哪个服务调用了哪个服务就够了,耗时、参数、状态码这些乱七八糟的别记,免得数据量太大。

正解Span 不仅包含调用关系,还包含开始/结束时间戳、服务名、操作名、状态码等关键属性。没有时间戳就无法计算每个环节的耗时,也就无法在瀑布图中定位瓶颈——那跟看一张没有时间的物流轨迹有什么区别?

为何常见:以为链路追踪只是画调用图,忽视了时间戳和标签信息在排障中的核心价值。

误区链路追踪主要用于线上故障排查,开发阶段和测试环境没必要接入,省得麻烦。

正解链路追踪在开发/测试阶段的收益同样巨大:联调时能一眼看出请求在哪个服务之间断掉、测试环境的慢查询问题也能提前暴露。而且“上线前接入”比“上线后补接”成本低得多——等出问题再想追溯,很多历史请求早就被采样丢掉了。

为何常见:把可观测性当成“运维的事”,没有意识到它应该贯穿研发流程的每个阶段。

面试题

Q1. 关于 Trace 与 Span 的关系,下列说法正确的是?

单选题 · easy

  • A.Trace只能包含一个Span
  • B.Trace由多个Span构成树状结构← 正确答案
  • C.Span之间完全独立互不关联
  • D.每个服务可自行生成TraceID

答案:B。Trace 是一次完整请求的链路,Span 是其中的单个服务片段,多个 Span 通过父子关系组成树状结构;每个服务自行生成 TraceID 会导致链路无法串联。就像一张物流轨迹由多个中转站记录拼接而成,而不是每站各开一个单号。

Q2. 跨服务传递追踪上下文,最常用的方式是?

单选题 · easy

  • A.URL参数中写死传递
  • B.HTTP请求头注入上下文← 正确答案
  • C.日志中复制trace_id
  • D.共享数据库存储链路

答案:B。业界标准做法是在 HTTP 请求头中注入 traceparent 等上下文头,让下游自动读取。URL 参数污染语义,日志复制和数据库共享都不实时也不可靠,相当于把面单写进小纸条而不是贴在包裹上。

Q3. 以下哪些信息通常包含在一个 Span 中?

多选题 · mid

  • A.完整的用户数据库快照
  • B.spanId与traceId标识← 正确答案
  • C.开始与结束的时间戳← 正确答案
  • D.服务名与操作名标签← 正确答案

答案:BCD。Span 包含链路标识(traceId/spanId)、时间戳、服务名与操作名等元数据,用于还原调用关系和计算耗时;它不存完整业务数据,就像物流扫描记录只记时间和站点,不会复制包裹里的商品。

Q4. 为什么微服务链路追踪需要采样?

单选题 · easy

  • A.因为Span数据量太少
  • B.让链路数据更加完整
  • C.降低存储传输与排查成本← 正确答案
  • D.提高业务代码运行速度

答案:C。高并发下全量 Trace 会产生海量数据,存储和传输成本极高;采样用可控成本保留代表性样本。数据太少不是问题,完整度和成本才是权衡核心,就像快递公司不会给每个包裹都配全程录像。

Q5. 尾部采样相比头部采样的最大优势是?

单选题 · hard

  • A.生成结果的速度更快
  • B.理论上不需要任何缓存
  • C.内存占用一定更小
  • D.可根据最终结果决定是否保存← 正确答案

答案:D。头部采样在入口决定,容易漏掉“最终失败”的请求;尾部采样等待处理结束后再决定是否保留,能保证故障样本不丢。代价是需要缓存中间数据,相当于等快递到了再决定要不要保留全程录像,更精准但更费缓存。

Q6. OpenTelemetry 的核心组件包括哪些?

多选题 · mid

  • A.标准化的API接口定义← 正确答案
  • B.具体实现的SDK逻辑← 正确答案
  • C.数据导出组件Exporter← 正确答案
  • D.用户业务数据仓库系统

答案:ABC。OTel 由 API(埋点接口)、SDK(实现与处理)、Exporter(导出到后端)组成,业务数据仓库不在其范围内。这就像统一的快递面单:API 规定格子怎么填,SDK 负责打印面单,Exporter 把面单送到转运中心。

Q7. Baggage 在链路追踪中的作用是?

单选题 · mid

  • A.存储全量业务日志记录
  • B.在服务间透传业务键值对← 正确答案
  • C.替代traceId的传递作用
  • D.对请求数据做加密压缩

答案:B。Baggage 是一组随 Trace 上下文透传给所有下游的键值对,典型用途是透传用户 ID、灰度标识等业务字段。它不替代 traceId,也不是存储或加密工具,就像面单备注栏不替代条码,只是补充说明。

Q8. 关于日志与链路的关联,正确的做法是?

单选题 · easy

  • A.将日志只保存在服务进程内存
  • B.日志中不包含任何线程标识
  • C.不同服务分别独立记录日志
  • D.在日志中打印traceId与spanId← 正确答案

答案:D。在日志中打印 traceId 和 spanId,才能按链路维度聚合日志,实现“点开 Span 看对应日志”。只存内存、无标识、各记各的都无法关联,相当于物流轨迹没有时间戳,无法和通话记录对上。

Q9. 关于链路追踪排障流程,正确的做法有哪些?

多选题 · hard

  • A.从瀑布图定位耗时最长Span← 正确答案
  • B.凭经验直接重启下游服务
  • C.结合该Span日志与指标定位← 正确答案
  • D.只看全局大盘不做下钻分析

答案:AC。正确流程是先看指标发现异常,再到 Trace 瀑布图找最宽 Span,最后结合日志定位根因。盲目重启是碰运气,只看大盘无法落到具体服务,就像客服只看配送总时长却不查具体卡在哪个仓库。

Q10. W3C traceparent 标准头包含哪四个字段?

单选题 · mid

  • A.version、user-name和password
  • B.version、trace-id、parent-id和flags← 正确答案
  • C.service-name、ip和port字段
  • D.trace-id与span-id和flags

答案:B。traceparent 固定格式为 version-trace-id-parent-id-flags,包含版本号、全链路 ID、父 Span ID 和采样标记。用户名密码、服务 IP 端口都不属于该标准头,链路 ID 和父 ID 才是拼接整条轨迹的关键要素。