WebFlux 里的 ThreadLocal(MDC、用户信息)为什么会丢?Reactor Context 怎么用、方向是什么?为什么手动 subscribe() 会丢上下文?怎样让 ThreadLocal 在响应式链里自动恢复?这篇用 Reactor 3.8.7 和 context-propagation 1.2.1 逐个验证。
主线程直接读 = user-42 Mono.just(1).map(读 TL).block()(没有切线程,在 main 上执行)= user-42 Mono.just(1).publishOn(parallel).map(读 TL)= parallel-1 读到 null Mono.delay(10ms).map(读 TL)= parallel-2 读到 null Mono.fromCallable(读 TL).subscribeOn(boundedElastic)= boundedElastic-1 读到 null
响应式链会在多个线程上执行,ThreadLocal 只在当前线程有效。
contextWrite 放在链的末尾,上游的 deferContextual 读到 = alice contextWrite 放在 deferContextual 之前(上游)= (无) 两个 contextWrite 写同一个 key,离订阅者近的先写、离源近的后写 —— 最终上游看到 = inner Context 经过 publishOn 切线程 = boundedElastic-1 读到 alice 在 delay 之后的 map 里通过 transformDeferredContextual 读 = parallel-4 读到 alice Context 是不可变的:ctx.put 返回新对象,原来的不变 = 2 / 1
从订阅者往源头传,contextWrite 只影响上游。
flatMap 内层读外层的 Context = alice 内层 flatMap 里手动 subscribe() 一个新链(读不到外层 Context)= (无) 订阅者各自的 Context 互不影响:同一个 Mono 用两个 Context 订阅 = 读到 A / 读到 B cache() 之后:第一个订阅者的 Context 被缓存的结果带给了第二个 = 读到 A / 读到 A
手动 subscribe() 不继承;cache() 会把第一个订阅者的结果带给后面的订阅者。
1) 什么都没配置:parallel-5 读到 null
2) 只注册了 ThreadLocalAccessor,没开 Hook,也没 contextCapture:parallel-6 读到 null
3) 注册了 + 链尾加 contextCapture(),但没开 Hook:parallel-7 读到 null
4) 注册了 + 开了 Hook,没有 contextCapture:parallel-8 读到 user-42
5) 注册了 + 开了 Hook + 链尾 contextCapture():parallel-9 读到 user-42
6) 同上,再用 Mono.delay 和 subscribeOn(boundedElastic):parallel-10 读到 user-42 | boundedElastic-1 读到 user-42
7) 订阅线程上没有设置 TL 时,下游读到 = parallel-1 读到 null
8) 线程池里的任务:普通 Runnable / ContextSnapshot.wrap:
普通提交 = my-pool 读到 null;ContextSnapshot.captureAll().wrap(...) 提交 = my-pool 读到 user-42注册 accessor 加开 Hook,线程池任务要 wrap。