想象一下,你点了一份外卖,骑手小张取餐后,绕了好几条街才送到。你打开App的配送轨迹页,看到一条清晰的路线——几点几分到店,几点几分取餐,走哪条路,中途停在哪。这份“配送轨迹图”让你一眼看出问题出在哪个环节:是商家出餐慢,还是骑手绕路,还是小区门卫拦了人。 在AI微服务架构里,一个用户请求就像一份跨店的外卖订单——它从入口网关出发,要经过鉴权服务、模型推理服务、特征服务、数据库等多个“店铺”。当请求变慢或失败时,最让人头疼的问题是:到底是哪家“店铺”拖了后腿?分布式追踪(Distributed Tracing)就是给每个请求贴上“外卖单号”,在它经过的每一站自动记账,最后拼出一张完整的配送轨迹图。这张图叫全链路可视化,而记账的单个条目叫Span,整条配送路线叫Trace。
掌握分布式追踪是AI系统架构师和运维工程师的必修课。它能让你在微服务的“车水马龙”中精准揪出拖后腿的环节,把排障时间从小时级压缩到分钟级,真正建立可观测性思维。
可视化演示
展示微服务调用链路追踪过程、Span数据模型与时间线、以及三种采样策略的对比效果。
本节配有交互动效(进入视口后自动加载,约 3KB)。
'+_captions.trace+'
微服务排障困境
外卖平台的客服电话被打爆了——用户说“订单卡在路上了”,但商家说早出餐了,骑手说早取餐了,配送站也说自己没问题。每个环节都觉得自己没错,可订单就是没送到。这正是微服务架构的日常:一个AI请求经过十多个服务,每个服务的日志都显示“正常”,但整体就是慢如蜗牛。出了故障,排障工程师只能像无头苍蝇一样,一会儿翻服务A的日志,一会儿查服务B的指标,在多个系统之间反复横跳。
为什么这么难?因为请求在服务间的调用关系是网状的,不是线性的。一个请求可能先调鉴权服务,再调特征服务,特征服务又要调数据库和缓存,而缓存又可能回调模型服务……每个服务只知道自己隔壁的邻居,没有人能看到完整的路线图。这就像一个城市里每个路口的交警只知道哪辆车从自己这儿经过,却不知道整条路线的全貌。没有全局视角,就没有人能为故障负责。
分布式追踪解决的就是这个问题:给每一次请求发一个“唯一的外卖单号”——Trace ID,让每个服务在收到请求时都记一笔账,然后把这些账本拼起来,还原整条请求路径。可观测性就是系统的“地图”,而追踪是地图上那条精确的配送轨迹。
有了这个全局视角,排障工程师再也不用“盲人摸象”。当用户说“接口很慢”时,他们不再逐台服务器翻日志,而是直接打开追踪系统,看到整条链路的配送轨迹——原来是“NLP推理服务”这一站耗时800毫秒,把平均耗时拉高了十倍。可观测性让排障从“侦探推理”变成了“看地图找路”。
- Trace 定义Trace代表一次完整的请求链路,从客户端发起到最后响应的全过程。
- Span 元素链路中的每一次服务调用都是一个Span,记录调用的开始时间、结束时间、状态。
- 集合关系Trace由若干Span组成,Span之间存在调用先后和嵌套关系。
Trace与Span模型
骑手小张的配送记录单上,每一行都写着“什么时候、在哪里、做了什么、花了多久”。整张记录单对应一次配送,叫做Trace;每一行的明细条目,就是一个Span。Span是分布式追踪的最小工作单元,它描述了“一次特定的操作”——比如“调用用户画像服务”或“把向量写入向量数据库”。
每个Span要有几个关键字段:Span名称描述做了什么,Span ID是这个条目的唯一编号,Parent Span ID记录它的上一级是谁,还有开始时间、结束时间和状态标签。当一个Span调用另一个Span时,被调用的Span就把调用者的Span ID记为“父亲”。
这种父子关系会形成一棵树:根节点是入口API调用,往下层层分支,构成一棵调用树(Call Tree)。这就像外卖订单的配送记录——“到店取餐”这个大条目下面,还嵌套着“核对订单”“打包”等更小的事件。深度嵌套的层级正是微服务链路的真实写照。
一个请求创建一个Trace,里面包含多少Span,取决于它调用了多少服务。AI场景下,一次推理请求可能包含特征提取、模型前向传播(可拆成多个层Span)、结果后处理等多个Span。通过观察Span的耗时分布,工程师能一目了然地看出哪次“取餐”环节最慢。追踪系统把一棵树画出来,树的每一片叶子都是一个服务调用——这就是“调用树”称谓的由来。
- Trace 标识所有Span必须共享同一个traceId,这是把它们串成一条链的“订单号”。
- Span 标识每个Span有自己唯一的spanId,相当于配送记录里每一条的“序列号”。
- 父级引用parentId指向父Span的spanId,父Span嵌套子Span,形成层级关系。
- 时间与标签时间戳记录开始和结束的精确时刻,标签记录业务信息,如用户ID、模型名称、状态码。
上下文传播机制
外卖单号是怎么跟着订单走的?商家打印小票、骑手扫码、物业登记——每一站都能通过小票上的编号关联到同一个订单。在微服务里,这个“小票编号”就是上下文(Context)。上下文包含traceId、当前spanId以及必要的选项信息,它必须随请求一起传播,沿途的每个服务才能“认账”。
传播的载体是什么?对HTTP服务来说,最常见的方式是HTTP Header注入。比如W3C标准定义的traceparent头,格式如下——“版本号-全局ID-父span ID-标志位”。服务收到请求后解析这个头,从中提取traceId和parentId,创建属于自己的Span,然后再把新的上下文传给下一个服务。上下文就像一个接力棒,每一棒都记录“从哪里来,到哪里去”。
最关键的挑战是跨进程传播:为了确保上下文在异步调用、消息队列、RPC框架等不同通信方式下不丢失,需要借助框架的拦截器或SDK的自动注入。如果传播断了,链路就会在某一环断裂,后面的请求变成“孤儿”——就像骑手中途把小票弄丢了,后面的站点就不知道这笔订单属于哪次配送。
想象快递包裹上的“面单”——它标注了收件人、寄件人和追踪编号。任何中转站看到面单都知道这个包裹从哪里来、要往哪里去。上下文传播也是如此:它靠header让每个服务“接得上头”,把整个调用链串联起来。没有上下文传播,你看到的只是一堆零散日志,而不是一条完整的配送轨迹。在AI微服务中,上下文甚至还会在消息队列(如Kafka)的消息头中传播,让异步的推理任务也能保持追踪连续。
- 版本号表示使用的追踪协议版本,目前标准版本为
00。 - 全局追踪ID128位十六进制数字,全局唯一,同一Trace内保持不变。
- 父Span ID64位十六进制数字,标识当前请求在调用方一侧的Span。
- 标志位8位二进制标记,最低位表示采样标记,决定该Trace是否被记录。
追踪采样策略
如果每一份外卖订单都事无巨细地记录全部配送细节,数据量会大到让外卖平台瘫痪。同理,在高QPS(每秒请求数)的AI服务中,如果每个请求都全量采集追踪数据,存储成本和性能损耗将不可承受。全量采集在流量低谷时尚可接受,但在流量高峰——比如大促时——系统可能因为追踪本身而宕机。
概率采样是经典的省钱策略:以一定概率决定是否记录某个Trace。比如按十分之一的概率采样,只记录约10%的请求链路。采样的本质是用不完整的数据换取可接受的成本。概率采样的问题在于:低概率时稀有故障可能漏掉,高概率时成本又上来了。低流量时应该全量采集,高流量时则降低采样率——这就是自适应采样。
采样策略与AI系统的预算约束高度相关。在GPU推理集群中,大量请求是相似的结构(如相同的模型版本、相同的输入管道),全量追踪的边际价值很低——你只是把同一张图存了几百遍。针对AI场景,一个流行的做法是尾部采样:一开始全部采集,但只保存那些“慢”或者“出错”的Trace(例如耗时超过P99阈值的请求),正常的请求在内存中缓冲后直接丢弃。这让排障有了“放大镜”,专门聚焦真正值得看的问题。
- 预算成本给定时间窗口内允许的追踪存储总开销。
- 预估请求数该时间窗口内的总请求数量,通常通过历史指标预估。
- 单位成本每条Trace包含的Span数量乘以每个Span的存储代价。
全链路可视化
追踪的数据最终要变成人看得懂的视图,否则一堆JSON日志毫无用处。最核心的视图有两种:调用瀑布图和依赖拓扑图。瀑布图按时间轴横向展开,每一行代表一个Span,长度代表耗时,行与行之间的缩进展示嵌套关系。你一眼就能看出哪个环节的“柱子”最长——它就是性能瓶颈。
依赖拓扑图则展示服务之间的调用关系:节点是服务,边是调用。用箭头和颜色表示调用方向、成功率和平均延迟。如果某个服务下游大面积飘红,拓扑图立刻暴露“热点”。这就像外卖平台的实时大屏——一看到某个商圈扎堆超时,调度中心立刻知道哪里出了状况。
可视化系统还承担着异常高亮告警的职责。当某个Span的状态码非200、抛异常或超过预估耗时阈值时,瀑布图上对应区块会标红,拓扑图中对应节点会闪烁。工程师可以直接点击红色区块,查看该Span内的完整metadata——包括堆栈信息、请求参数、模型版本等,不需要再翻原始日志。
瀑布图本质上就是把外卖配送轨迹按时间轴摊开:9:00接单,9:05到店,9:20取餐完成,9:40送达。哪一段耗时最长,用户比你还急。在AI微服务里,瀑布图还能嵌入阶段标记——比如把“预处理-推理-后处理”用不同颜色标出,让工程师一眼看出瓶颈在GPU计算还是在数据的I/O等待上。
- Span耗时该次服务调用的总耗时,由Span的开始和结束时间戳相减得到。
- Trace耗时整个请求链路的总耗时,即根Span的耗时。
- 占比计算两者相除,得到该Span在整条链路中的耗时占比。
主流工具与AI特性
目前最流行的开源工具有Jaeger、Zipkin和SkyWalking。Jaeger是CNCF项目,源自Uber,支持先进的采样策略和与Prometheus的集成;Zipkin是Twitter开源的老牌选手,文档齐全;SkyWalking使用无侵入探针采集数据,Java、Go、Python等多语言支持优秀,特别适合快速接入已有系统。它们都提供头部采集(在入口处插入SDK)、数据存储(ES/ClickHouse)和UI界面三大组件。
在AI微服务架构中,除了传统的RPC调用追踪,还有几个特殊性。首先是GPU占用的追踪:模型推理的耗时往往不等于GPU计算耗时,还包含显存拷贝、队列等待和内核启动开销。成熟的商业方案会在Span的Tags中注入gpu_id、cuda_utilization、vram_usage等自定义字段——相当于给配送单加上“骑手电动车电量”这一栏。
第二,异步流水线是AI系统的常态:一个请求触发任务写入消息队列,多个worker异步消费处理。追踪的上下文必须跨越“同步-异步”边界,利用消息头的传播机制。第三,模型版本与可观测性结合:Span中可以记录model_version,这样当某个模型版本效果退化时,可以立刻通过追踪系统回溯,判断是模型本身的问题还是上游数据的变化。
| 工具 | 实现语言 | 接入方式 | 核心优势 |
|------|---------|---------|---------|
| Jaeger | Go/React | SDK或Istio | 采样策略丰富、CNCF背书 |
| Zipkin | Java | SDK | 轻量、生态成熟 |
| SkyWalking | Java/Go/Python | Agent探针 | 无侵入脚本接入、自动服务拓扑 |
1. 接入方式:SDK需要修改代码,探针则通过运行时代理挂载,业务代码零改动。
2. 核心优势:不同工具在采样策略、社区活跃度、多语言支持上的差异决定了适用场景。
选工具就像外卖平台选配送系统:小作坊用微信群调度就够了,大型平台则必须上专业调度系统。AI企业应根据自身规模选择——初创公司用SkyWalking快速上线,大型平台用Jaeger定制采样。在AI推理服务中,最推荐的实践是“双轨制”:用通用追踪工具处理RPC调用,同时为GPU指标建立专门的监控看板,把两者通过traceId关联起来,实现真正意义上的“算力链路可观测”。
AI 应用场景
AI推理服务性能瓶颈诊断
阶段:模型服务化部署与运维
解决的问题:一次图片识别请求耗时2秒,产品经理质疑模型太慢。传统排查方式只能看服务平均耗时指标,无法定位瓶颈在预处理、GPU推理还是后处理。利用分布式追踪,每个阶段生成独立Span,追踪系统直接展示各阶段耗时占比,快速定位“瓶颈在图像预处理—CPU编解码耗时800ms”。
具体案例:某安防公司的AI人脸识别系统接入了Jaeger。某天监控发现P99延迟飙升,追踪系统瀑布图显示“特征提取服务”的数据库查询Span耗时异常,进一步展开发现慢查询是一个未建索引的向量表。修复索引后,P99从1.2s降回300ms。
RAG应用的链路追踪与效果调优
阶段:RAG应用开发与优化
解决的问题:RAG(检索增强生成)应用涉及“问题改写→向量检索→重排序→LLM生成”四个环节,用户反馈“回复太慢且不准确”,但无法判断是检索召回差还是LLM生成慢。通过分布式追踪,每个环节一个Span,并标注检索命中的文档ID、重排序分数等标签,从追踪数据中找到问题根源。
具体案例:某企业知识库问答机器人接入Zipkin后,发现“向量检索”Span在特定提问下耗时800ms,标签显示“候选集数量=1000”,而其他问题平均只需60ms。确认是相似度计算阈值过低导致的候选集过大,调整阈值后整体延迟下降60%。同时通过追踪系统查看每次生成的question_rewrite标签,发现30%的提问被改写系统误判为英文,验证了多语言模型参数问题。
异步推理流水线的可观测性建设
阶段:批处理推理系统的全链路监控
解决的问题:离线批量推理系统通过消息队列异步处理任务,单条消息经过“生产者→MQ→多个Worker→结果回写”,出现问题无法判断是投递失败、消费积压还是Worker崩溃。传统监控只看队列深度指标,无法关联“哪条消息由谁处理、处理多久”。引入分布式追踪后,每条消息的消费处理都形成独立Span,上下文通过消息头传播,完整还原处理链。
具体案例:某开源社区团队为大规模语音转写服务构建追踪系统。在一条消息的处理Trace中,发现消息在MQ中被重试了3次(每次创建新Span),且重试后落在了一个CPU打满的Worker节点上。由此定位到是消费者端缺少背压机制,在Worker集群调度优化后,消息重试率归零。
Python 演示
# -*- coding: utf-8 -*-"""Simplified Distributed Tracing Demo"""import jsonimport timeimport uuidfrom collections import defaultdict class Span: """A single unit of work in a trace.""" def __init__(self, name, trace_id, span_id, parent_id, tags=None): self.name = name self.trace_id = trace_id self.span_id = span_id self.parent_id = parent_id self.tags = tags or {} self.start_time = time.time() self.end_time = None def finish(self): self.end_time = time.time() def to_dict(self): return { "name": self.name, "trace_id": self.trace_id, "span_id": self.span_id, "parent_id": self.parent_id, "duration_ms": round((self.end_time - self.start_time) * 1000, 2), "tags": self.tags, } class Tracer: """Context manager that creates and records spans.""" def __init__(self, service_name): self.service_name = service_name self.reporter = Reporter() def start_span(self, operation, trace_id, parent_id): span_id = uuid.uuid4().hex[:16] return Span(operation, trace_id, span_id, parent_id, tags={"service": self.service_name}) def inject(self, span, headers=None): headers = headers or {} headers["trace_id"] = span.trace_id headers["parent_id"] = span.span_id return headers def extract(self, headers): headers = headers or {} return headers.get("trace_id"), headers.get("parent_id") class Reporter: """Collects finished spans from all services.""" def __init__(self): self.spans = [] self.lock = False def report(self, span): self.spans.append(span.to_dict()) def export(self, trace_id): return [s for s in self.spans if s["trace_id"] == trace_id] def service_a(tracer, headers=None): trace_id, parent_id = tracer.extract(headers) if trace_id is None: trace_id = uuid.uuid4().hex[:16] span = tracer.start_span("api-gateway", trace_id, parent_id) time.sleep(0.01) headers = tracer.inject(span) service_b(tracer, headers) span.finish() tracer.reporter.report(span) def service_b(tracer, headers): trace_id, parent_id = tracer.extract(headers) span = tracer.start_span("llm-inference", trace_id, parent_id) time.sleep(0.05) headers = tracer.inject(span) service_c(tracer, headers) span.finish() tracer.reporter.report(span) def service_c(tracer, headers): trace_id, parent_id = tracer.extract(headers) span = tracer.start_span("vector-db-query", trace_id, parent_id) time.sleep(0.02) span.tags["model_version"] = "gpt-4o-mini" if time.time() % 2 > 1: span.tags["cache_hit"] = "true" span.finish() tracer.reporter.report(span) def build_trace_tree(spans): """Group spans by parent_id to form a tree.""" children = defaultdict(list) for span in spans: children[span["parent_id"] or span["span_id"]].append(span) roots = [s for s in spans if s["parent_id"] is None] return roots, children def print_trace(spans, indent=0): roots, children = build_trace_tree(spans) for root in roots: print(" " * indent + f"[{root['name']}] {root['duration_ms']}ms") for child in children[root["span_id"]]: print(" " * (indent + 1) + f"[{child['name']}] {child['duration_ms']}ms") if __name__ == "__main__": tracer = Tracer("demo") service_a(tracer) trace_id = tracer.spans[0]["trace_id"] all_spans = tracer.reporter.export(trace_id) print("=== Trace JSON ===") print(json.dumps(all_spans, indent=2, ensure_ascii=False)) print("=== Call Tree ===") print_trace(all_spans)
class Span定义基本数据结构:名称、trace_id、span_id、parent_id、时长和标签。class Tracer追踪器的核心,提供启动Span和上下文的注入/提取方法。def inject(self, span, headers)将trace_id和parent_id写入HTTP头字典,模拟跨服务传递上下文。def extract(self, headers)从HTTP头字典中读回当前的trace_id和parent_id,完成上下文恢复。def service_b(tracer, headers)通过参数接收headers,模拟下游服务如何恢复链路。span.tags["cache_hit"] = "true"演示给Span打业务标签(如缓存命中情况),排查时查看。def build_trace_tree(spans)用parent_id构建多叉树,把平铺的Span还原为调用层级。def print_trace(spans, indent=0)用缩进打印调用树,无损还原“配送轨迹”。
常见误区
误区分布式追踪就是在线上故障时用来排查的工具,服务一切正常时根本不需要看它,上不上都无所谓。
正解追踪的价值远不止排查故障,它更是容量规划和性能优化的依据。平时通过追踪数据可以发现“某服务调用量暴涨但平均延迟稳定”的异常趋势,提前扩容;也可以通过分析Span耗时分布,找到优化空间。就像外卖平台平时就在看每个区域骑手的平均配送时长,并及时优化规划,不只是等出投诉后才看轨迹。
为何常见:故障排障是紧迫需求,给人的印象最深;而日常性能优化是渐进改善,容易被忽略。
误区为了省存储成本,把采样率设得越低越好,比如千万分之一,反正能看个大概。
正解过低的采样率会让稀有的故障事件在统计上鲜有出现。假如一个错误只影响1%的请求,千分之一的采样率意味着你几乎永远看不到它。合理的做法是按流量动态调整:低流量时全量采样,高流量时降到1%或更低,同时用尾部采样保留那些慢请求和错误请求的完整追踪。追踪的价值不在“多”,而在“精”。
为何常见:管理者只看到存储成本,忽略了追踪数据的业务价值,以为省钱就是最优。
误区部署一套Zipkin或SkyWalking,把所有服务加进来,就能在UI上看到完整链路和拓扑图,什么都不用管。
正解工具只是载体,关键在于业务语义的正确传播。如果你的服务用到了消息队列、异步线程池、或HTTP客户端库不被SDK支持,上下文就可能“断裂”,产生大量根Span缺失的孤儿Trace。接入后必须验证全链路是否打通,并对异步场景做手动埋点。就像外卖系统不是靠装个GPS就能覆盖所有商家——还得确保每个骑手都带着手机并打开了定位。
为何常见:厂商文档宣传“无侵入”,让用户误以为零配置、零维护就能解决所有问题。
误区有了追踪数据,切了传统日志系统,出现什么问题直接查追踪系统就好了。
正解追踪和日志是互补关系,不是替代关系。Span是结构化的、面向调用链的数据,而日志是丰富的、面向上下文的文本信息。最佳实践是在Span的Tag中记录日志关键字或关联日志ID,排障时根据TraceId关联到具体的业务日志,看到更完整的上下文。类比外卖配送单记录了路线和时长,但用户反馈“餐洒了”这种细节,还是要看后台留言和拍照凭证。
为何常见:看到追踪系统界面强大,容易高估它的定位,低估传统日志在细节排查中的价值。
误区AI推理服务调用链里,GPU计算就是一个“黑盒”Span,只需要看模型服务内部各层的HTTP调用。
正解AI系统耗时瓶颈往往不在网络,而在GPU内核计算和数据传输。要在Span中额外打上cuda_utilization、vram_used、kernel_time等标签;更精细的追踪还要记录数据加载、GPU拷贝、CPU-GPU同步等子Span,才能真正发现瓶颈。外卖配送不能只看“商家→用户”的路线,还要看商家内部“备菜→炒菜→打包”各花了多久。
为何常见:传统分布式追踪工具往往只关心网络调用,开发者容易忽略计算资源的追踪。
面试题
Q1. 以下哪个概念表示一次完整的请求链路?
- A.Span
- B.Trace← 正确答案
- C.Metrics
- D.Log
答案:B。Trace代表一次请求从入口到出口的完整链路;Span是链路中的一次单独调用。Metrics是指标,Log是日志,都不描述链路本身。类比:Trace是整张配送单,Span是单子上的一个条目。
Q2. 一个Span最核心的五个字段中,用于区分“谁调用了我”的是?
- A.traceId
- B.spanId
- C.parentId← 正确答案
- D.timestamp
答案:C。parentId指向调用方的父Span ID,用于还原调用树的父子关系。traceId标识整条链路,spanId唯一标识当前Span,timestamp是时间记录。类比:parentId就是配送记录里“上一站是哪”。
Q3. W3C标准中traceparent头的字段顺序是?
- A.version-trace_id-parent_id-flags← 正确答案
- B.trace_id-parent_id-version-flags
- C.version-parent_id-trace_id-flags
- D.trace_id-version-flags-parent_id
答案:A。W3C的traceparent标准格式固定为version-trace_id-parent_id-flags四个字段用短横线拼接。任何服务解析时都必须按此顺序。类比:快递面单的字段位置也是固定的,乱序会让读单员困惑。
Q4. 关于概率采样,下列说法正确的是?
- A.采样率越高,追踪系统性能越好
- B.采样率越低,越容易漏掉稀有故障← 正确答案
- C.概率采样适用于所有流量场景
- D.采样不影响存储成本
答案:B。采样率低时稀有故障在统计上容易“隐身”。采样率高会增加存储成本,还可能拖慢系统。概率采样虽通用,但高QPS下仍需配合自适应或尾部采样。类比:抽查配送单时,抽查比例越低,越难发现那1%的异常订单。
Q5. 以下哪种工具属于无侵入式追踪方案?
- A.Jaeger SDK
- B.Zipkin Brave
- C.SkyWalking Agent← 正确答案
- D.OpenTelemetry API
答案:C。SkyWalking通过Agent探针的字节码注入技术自动拦截RPC调用,业务代码零修改。Jaeger、Zipkin和OpenTelemetry都需要在代码中引入SDK或API进行埋点,属于侵入式接入。
Q6. 在AI推理链路中,以下哪项最不适合作为Span标签(Tag)记录?
- A.model_version
- B.gpu_utilization
- C.完整的用户敏感聊天内容← 正确答案
- D.cache_hit
答案:C。Span标签会进入追踪系统存储,可能被展示或归档。记录完整敏感内容有数据泄露风险,应只记录脱敏后的摘要或ID。模型版本、GPU利用率和缓存命中情况都是有价值的业务标签。
Q7. 分布式追踪中“上下文传播”的作用是?
- A.只在本服务内存中记录信息
- B.跨服务传递traceId和parentId← 正确答案
- C.代替数据库存储
- D.提高网络传输速度
答案:B。上下文传播把traceId和parentId通过HTTP头、消息头等载体从上游传到下游,是链路“不断裂”的关键。它不改变业务数据,也不替代存储,更与网络速度无关。类比:是配送单上的面单跟着包裹从一个站点传到下一个站。
Q8. 关于Trace和Span的关系,以下哪些描述正确?
- A.一个Trace可以包含多个Span← 正确答案
- B.Span之间通过parentId形成层级关系← 正确答案
- C.不同Trace的Span可以共享同一个traceId
- D.根Span的parentId为空← 正确答案
答案:ABD。一个Trace由多个Span组成,Span通过parentId形成树形结构,根Span没有父级。不同Trace共享traceId是错误认知——traceId全局唯一,是区分不同请求的“订单号”。
Q9. 在高QPS的AI服务中,哪些做法能有效降低追踪系统的存储压力?
- A.低流量时全量采样,高流量时降低采样率← 正确答案
- B.对慢请求和错误请求优先保留(尾部采样)← 正确答案
- C.所有请求全量采集并永久存储
- D.只保留P99延迟以内的正常请求
答案:AB。自适应采样和尾部采样都兼顾了成本与可观测性。全量采集并永久存储显然不可持续。错误选项D把逻辑搞反了——真正需要重点分析的是慢请求,而正常请求(P99以内的部分)恰恰可以大胆丢弃。
Q10. 在AI异步推理流水线中,以下哪些方案能保证跨异步边界的链路完整性?
- A.在消息头中注入traceparent字符串← 正确答案
- B.使用消息队列中间件自带的追踪插件← 正确答案
- C.每个worker重新生成一个traceId并忽略上游
- D.手动在消息体中加入traceId和parentId字段← 正确答案
答案:ABD。让上下文跨异步传播的三种可行方式是:标准traceparent头注入、中间件插件自动透传、消息体中手动带字段。每个worker新生成traceId会让链路分裂成多个独立Trace,排障时就无法还原完整调用链,是错误方案。