开放问答
Kafka 消费侧 + 线程池 trace 断链:P99 排查时同步段有 span、异步段全黑,怎么系统性把上下文传下去
该问题的正文、回答与评论。
提问者 程知远 · 状态 open
可观测性链路追踪OpenTelemetryKafka线程池P99
【背景】 Spring Boot 3 + JDK 17 + Kafka 3.5(4 分区)+ OpenTelemetry 1.32(Java Agent 接入)+ 自研线程池。业务链路:HTTP 入口 → 同步处理 → 发 Kafka → 消费端落库 + 线程池并发更新 3 张关联表。整体 P50 在 40ms 左右,但每周都有 1–2 次 P99 飙到 600ms+,且告警里只能看到入口 span 耗时,消费端和线程池里的子任务完全没有 span,等于排查时只能盲猜。
【现象】
- 入口 span 正常,duration 准确;
- 消费端日志里 MDC 有 traceId(说明 Kafka header 透传成功),但 Jaeger 里这条 trace 只有入口那一段,消费端的 handler、3 个线程池子任务全部缺失;
- 更诡异的是:偶尔能看到消费端 span 的 traceId 是上一条请求的(串号),怀疑是线程池复用时 MDC/Context 没清干净;
- 业务上无法把「一次下单」的同步段和异步落库段串起来,P99 抖动到底是消费端慢还是线程池排队,根本判断不了。
【已尝试】
- 用
MDC.put手动透传 +ThreadLocal包装线程池的TaskDecorator:解决了 MDC 日志,但 OTel 的 Context 还是丢; - 把 OTel Agent 的
-Dotel.instrumentation.kafka.enabled=true打开:入口→Kafka 这段有 span,但消费端内部线程池里依然没有; - 在消费端
@KafkaListener方法开头手动Context.current().withSpan(...)再runWith:能挂上 span,但 3 个并发子任务的 span 父子关系还是错的(挂到了同一个父下,看不出并行耗时)。
【期望得到的答案形态】
- 一个可复现的最小代码片段:从
@KafkaListener入口 → 自研线程池 → 子任务,OTel Context 怎么正确传播(包括ContextStorage、Context.run、TaskDecorator的正确写法),最好带「串号」问题的修复点; - 一份踩坑清单:哪些地方容易把 MDC 和 OTel Context 混为一谈、线程池复用时的清理顺序、Kafka header 与 OTel 自动注入的边界;
- 如果你们团队有现成的「异步链路 trace 治理 SOP」,欢迎贴出来,我对照着改。
评论
暂无评论。