Spring WebFlux Kotlin协程调用Mono.awaitSingle后MDCContext为空的问题
Reactor(ReactiveMongo基于该框架)的上下文系统与Kotlin协程的上下文是完全独立的两个体系。当你直接调用awaitSingle()时,协程中的MDCContext不会自动同步到Reactor的Context中;而Reactor操作执行时会使用自身的空上下文,执行完毕切换回协程上下文时,原MDC内容已丢失,导致后续获取MDCContext为空。
更简便的解决方案
方案1:给Mono添加自动传递MDC的扩展函数
写一个通用的Mono扩展函数,自动将当前协程的MDC上下文注入到Reactor流中,同时同步MDC内容:
import org.slf4j.MDC import reactor.core.publisher.Mono fun <T> Mono<T>.attachCoroutineMDC(): Mono<T> { val currentMdc = MDC.getCopyOfContextMap() ?: emptyMap() return this // 将协程MDC注入Reactor上下文 .contextWrite { reactorCtx -> reactorCtx.putAll(currentMdc) } // 订阅时同步MDC到当前线程 .doOnSubscribe { currentMdc.forEach { MDC.put(it.key, it.value.toString()) } } // 流结束后清理当前线程的MDC(避免污染其他线程) .doFinally { currentMdc.keys.forEach { MDC.remove(it) } } }
之后所有调用awaitSingle()的地方,只需先调用这个扩展函数即可:
val count = templateRepository.findByName(request.templateName) .attachCoroutineMDC() .awaitSingle()
这种方式不需要修改协程上下文,仅需给Mono添加一行调用,相比逐个包裹withContext更高效。
方案2:全局配置协程Dispatcher自动同步MDC
如果不想修改任何业务代码,可以自定义一个全局协程Dispatcher,自动将协程的MDC上下文同步到Reactor操作中:
import kotlinx.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.MDCContext import org.slf4j.MDC import reactor.core.publisher.Hooks import reactor.core.publisher.Operators val MDC_AWARE_DISPATCHER = Dispatchers.IO.limitedParallelism(64).let { baseDispatcher -> object : CoroutineDispatcher() { override fun dispatch(context: CoroutineContext, block: Runnable) { val mdcMap = context[MDCContext]?.context ?: emptyMap() baseDispatcher.dispatch(context) { // 注册Reactor操作符,自动同步MDC Hooks.onEachOperator { operator -> Operators.lift { subscriber -> reactor.core.publisher.CoreSubscriber { signal -> mdcMap.forEach { MDC.put(it.key, it.value.toString()) } subscriber.onNext(signal) mdcMap.keys.forEach { MDC.remove(it) } } }.apply { operator.subscribe(this) } } try { block.run() } finally { Hooks.resetOnEachOperator() } } } } }
之后在所有执行Reactive操作的协程中使用这个Dispatcher:
suspend fun getTemplates(request: GetPaginatedTemplateServiceRequest): PaginationTemplates = withContext(MDC_AWARE_DISPATCHER) { var context = MDCContext() val count = templateRepository.findByName(request.templateName).awaitSingle() context = MDCContext() // ... 后续逻辑 }
如果项目中大部分协程都使用IO Dispatcher,也可以考虑将这个Dispatcher设置为全局默认(需谨慎测试,避免影响其他逻辑)。
关于你现有解决方案的说明
你用withContext(Dispatchers.IO.plus(MDCContext()))的方式有效,是因为在子协程中重新附加了MDCContext,kotlinx-coroutines-reactor桥接工具会将协程上下文的部分内容同步到Reactor的上下文中,从而保留了MDC内容。但这种方式需要逐个修改调用点,对于大量现有代码来说成本较高。
内容的提问来源于stack exchange,提问作者Wildan Dicky Alnatara

