事情本身不复杂,线索都在日志里。下单接口用 trace_id 做幂等键,同步链路正常,改成线程池异步写库后重试产生了三条订单,日志里异步线程拿到的是新生成的 trace_id。

第一反应是「MQ 消息头没有带幂等键,消费端只能自己生成」,验证下来不成立。

排查方向其实有限,先把可能性排一排。trace 上下文存在 ThreadLocal,线程池不会自动复制。还有一种是异步任务里重新生成了 trace_id 而没有沿用父上下文。

少数情况下是重试框架在 catch 分支新建上下文重新发请求。

第一件事是在异步任务里打印 trace_id 与父线程对比

python -c "import threading;print(threading.current_thread().name)"

再往下用显式参数把幂等键传到异步方法

python order.py --pass-idem-key --async-write

线程池包装器里做上下文复制

python -c "from contextvars import copy_context;ctx=copy_context();print(ctx)"

等前面都确认过,再在 MQ 消息头写入幂等键并校验消费端读取

python consume.py --header X-Idem-Key --print-header

改完先别撤,确认这几项。异步链路三次重试只有一条订单,日志里父子线程的幂等键一致,MQ 消息头能读到同一个值。

上下文传递不能依赖隐式变量,跨线程、跨进程、跨消息都要显式带上幂等键,任何一处重新生成都会破坏幂等。拖得越久,排查成本越高,因为现场状态会被后续操作覆盖。

作者 9823189