题干与适用场景
一个 worker 每次从队列取出 100 个事件,聚合后只调用一次下游 API。每个事件可能来自不同 Trace,worker 也可能被定时任务或人工重放触发。请设计追踪模型,既能回溯每个来源,又不把 100 条不相关请求伪装成一个父子树。
OpenTelemetry 将 Span 描述为可形成树的操作,并允许一个 Span 关联零个或多个 Link。官方概览明确把“批处理由多个入站 Span 发起”列为 Links 的典型场景。
面试官考察点
考察候选人是否理解 parent 表示单一当前上下文,Link 表示因果相关但非父子关系;能否控制链接数量、采样和高基数属性,避免 Trace 爆炸,同时让指标和日志仍可关联。
回答前需要澄清的问题
- 批次是否包含不同租户、不同安全级别或不同业务类型?
- 下游调用是一次合并请求,还是仍可拆成每个事件的操作?
- 需要按单个事件追责、延迟分析还是只看批次吞吐?
- 采样由入口决定,还是 worker 可以保留部分来源上下文?
- 是否允许在 Link 属性中放事件 ID、租户 ID 或敏感字段?
30 秒回答框架
“批次处理 Span 以 worker 或批次触发上下文为 parent;100 个入站 Span 作为 Links,因为它们共同促成一次批处理却不构成单一父子链。每个 Link 只保留必要的 SpanContext 和低基数属性,设置链接上限并统计截断数。批次 Span 记录大小、等待、处理、下游耗时和失败数,日志用批次 ID 与事件哈希关联。敏感租户字段脱敏,采样要保证失败批次和重放批次可定位。”
分步骤深入解答
第一步:区分 parent 与 Link
Parent 表达当前操作从哪个单一 Span 继续,形成 Trace 树并继承 TraceId。Link 只表达一个相关 SpanContext,可来自同一或不同 Trace。批处理有多个同等来源时,不能任选一个事件作为父节点再把其他事件挂成子节点。
批次处理 Span
parent: worker / scheduler context
links: event-1 SpanContext ... event-100 SpanContext第二步:保留跨边界的 SpanContext
从消息头提取 TraceContext,验证格式和采样标记后构造 Link;不要把完整消息、用户输入或原始令牌写入 Link 属性。若消息没有合法上下文,记录“无来源上下文”的计数,而不是生成伪造 TraceId。
第三步:控制链接规模与成本
100 个链接只是题设上限,生产批次可能更大。设置 SDK 的 link count limit 或应用层上限,优先保留错误、重试和关键租户的代表性来源,并记录 dropped-link 数量。链接被截断时要在批次指标和日志中可见。
第四步:设计批次 Span 生命周期
Span 覆盖等待批次、反序列化、聚合、下游调用和提交结果;按阶段记录事件或指标。任何创建的 Span 都必须结束,即使处理失败、取消或部分提交。不要让单个事件的长处理把整个批次 Span 的结束时间掩盖。
第五步:处理采样与失败可见性
入口采样决定是否保留来源 Trace,但 worker 应为失败、重试、死信和人工重放设置保底策略。Link 在 Span 创建时可能影响采样,创建后添加的 Link 不一定能被采样器使用,因此需要明确采样顺序和降级行为。
第六步:把追踪、指标和日志分工
Trace 解释单个批次的因果关系;指标承载批次大小、队列等待、处理时延、成功/失败和截断计数;日志用批次 ID、事件哈希和重放 ID定位具体样本。不要把事件 ID 作为无限基数的指标标签。
第七步:隔离租户与隐私
跨租户批次需要在 Link 和日志中使用不可逆哈希或内部引用,并在导出前做属性过滤。若不同安全级别不能共存,应按租户或权限拆批,避免一个 Trace 让低权限操作者看到其他租户的上下文。
第八步:验证查询与故障场景
测试单来源、混合 Trace、缺失上下文、链接超限、采样丢失、下游重试、部分失败、死信和重放。验证从批次 Span 能跳回保留的来源 Trace,也能从指标定位被截断或未采样的批次。
高质量示范回答
“批次 Span 以 worker 或调度器为 parent,消息中的每个来源 SpanContext 作为 Link。这样保留了 100 个事件共同促成一次下游调用的事实,避免伪造父子树。我要限制链接数量并记录丢弃计数,过滤租户和敏感属性;用指标记录批次大小、队列等待、下游时延、失败与重试,日志用批次和重放 ID 关联。采样策略为失败批次保底,测试缺失上下文、混合 Trace、超限和重放。”
常见错误
- 任选第一条消息作为 parent → 伪造因果树 → 批次用共同 parent,其余来源用 Links。
- 把整个消息放进 Link 属性 → 泄露敏感数据并增大成本 → 只保留必要、脱敏的低基数字段。
- 无限制添加 Links → Span 体积和导出成本失控 → 设置上限并记录截断。
- Span 只在成功路径结束 → 错误和取消留下悬挂 Span → 用 finally 或作用域自动结束。
- 用事件 ID 做指标标签 → 高基数爆炸 → 把细节留给日志或 Trace。
- 认为创建后 Link 一定影响采样 → 重要来源被采样丢失 → 在创建 Span 前准备采样所需上下文。
追问及应对
追问一:批次只有一个消息时还需要 Link 吗?
可以直接以该消息上下文作 parent,若 worker 有独立生命周期也可用 worker parent 加一个 Link;选择取决于是否要把批次视为该请求的直接子操作。
追问二:Link 会把不同 Trace 合并成一个 Trace 吗?
不会。Link 表达相关性,保留各自 TraceId;查询系统需要提供从 Link 跳转来源 Trace 的能力。
追问三:如何选择被截断的来源?
按错误、重试、重放、关键租户或确定性采样保留,并记录总数和 dropped 数;不要静默保留前 N 条导致偏差。
追问四:批处理失败如何保证可诊断?
让失败批次和死信重放带独立批次/重放 ID,保留错误事件链接或摘要;指标记录失败类型,日志提供受控的事件引用。
追问五:Link 属性能否放 tenant.id?
只有在权限、基数和隐私评估通过时才可放内部稳定标识;跨租户导出通常应哈希或删除,并避免把它当高频指标标签。