在整个链路中,网关、业务服务、调用其他服务,异步调用、或者发送mq是一个线程吗
📊 线程切换详解表
| 场景 | 线程是否变化 | 线程示例 | MDC/TraceId传递 |
|---|---|---|---|
| 网关→业务服务 | ✅ 变化 | http-nio-8080-exec-1→http-nio-8081-exec-3 | 通过HTTP Header自动传递 |
| 业务服务内同步调用 | ❌ 不变 | http-nio-8081-exec-3→http-nio-8081-exec-3 | MDC直接可用 |
| 同步HTTP调用下游 | ❌ 不变(阻塞) | http-nio-8081-exec-3(阻塞等待) | 需拦截器设置Header |
| 异步HTTP调用 | ✅ 变化 | http-nio-8081-exec-3→SimpleAsyncTaskExecutor-1 | 需要手动传递上下文 |
| @Async方法调用 | ✅ 变化 | http-nio-8081-exec-3→task-1 | 需要TaskDecorator |
| MQ发送(同步) | ❌ 不变 | http-nio-8081-exec-3 | MDC直接可用 |
| MQ发送(异步) | ✅ 变化 | http-nio-8081-exec-3→MQClient-Thread | 需要消息Header传递 |
| MQ消费 | ✅ 变化 | ConsumeMessageThread_1(全新线程) | 从消息Header获取 |
| 定时任务 | ✅ 变化 | scheduling-1(调度线程) | 需要手动生成设置 |
| 线程池任务 | ✅ 变化 | pool-1-thread-1(线程池线程) | 需要TTL或手动包装 |
🔄 完整的线程切换流程示例
关键结论:
HTTP请求跨服务一定线程切换(不同Tomcat实例)
同步业务处理通常线程不变(除非主动切换)
所有异步操作一定线程切换(@Async、CompletableFuture等)
MQ生产消费一定线程切换(不同线程模型)
线程切换处必须手动传递TraceId,否则链路断裂
📊 不同场景的线程分析
场景1:网关 → 业务服务(线程切换)
java
// 网关线程(Tomcat/Netty工作线程) public class TraceGlobalFilter { public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 当前线程:Tomcat-NIO-1 或 reactor-http-nio-2 String threadName = Thread.currentThread().getName(); // 如:reactor-http-nio-2(WebFlux) // 或:http-nio-8080-exec-1(Tomcat) // 网关处理完成后,请求转发到业务服务 // 业务服务由另一个Tomcat实例接收,使用不同的线程! } } // 业务服务线程(另一个Tomcat实例) public class HeaderFilter { protected void doFilterInternal(HttpServletRequest request, ...) { // 当前线程:http-nio-8081-exec-3(业务服务的Tomcat线程) // 与网关线程完全不同! // 但TraceId通过HTTP header传递,所以能保持链路 } }场景2:同步HTTP调用(可能线程不变)
java
@Service public class OrderService { @Autowired private RestTemplate restTemplate; public Order createOrder(OrderRequest request) { // 当前线程:http-nio-8081-exec-3(Tomcat线程) // 同步调用库存服务 // 注意:这里会阻塞当前线程,直到响应返回 ResponseEntity<StockResponse> response = restTemplate.postForEntity( "http://inventory-service/stock/check", stockRequest, StockResponse.class ); // 返回后,仍然是同一个线程:http-nio-8081-exec-3 // 但被调用的服务使用另一个线程 } }场景3:异步HTTP调用(线程切换)
java
@Service public class OrderService { @Autowired private AsyncRestTemplate asyncRestTemplate; public CompletableFuture<Order> createOrderAsync(OrderRequest request) { // 当前线程:http-nio-8081-exec-3 // 异步调用 ListenableFuture<ResponseEntity<StockResponse>> future = asyncRestTemplate.postForEntity( "http://inventory-service/stock/check", stockRequest, StockResponse.class ); // 立即返回,不会阻塞当前线程 // 实际HTTP请求会在另一个IO线程执行 return future.completable() .thenApply(response -> { // 这里会在回调线程执行,不是原来的Tomcat线程! // 线程可能是:SimpleAsyncTaskExecutor-1 return processResponse(response); }); } }场景4:MQ发送(通常线程不变)
java
@Service public class OrderService { @Autowired private RabbitTemplate rabbitTemplate; public void createOrderWithMQ(OrderRequest request) { // 当前线程:http-nio-8081-exec-3 // 发送MQ消息(通常是同步操作) rabbitTemplate.convertAndSend( "order.exchange", "order.create", orderMessage ); // 仍然是同一个线程,除非配置了异步发送 // 但MQ消费者是另一个线程! @RabbitListener(queues = "order.queue") public void processOrder(OrderMessage message) { // 这里由RabbitMQ监听器线程执行 // 如:SimpleAsyncTaskExecutor-2 } } }场景5:@Async方法(线程切换)
java
@Service public class OrderService { @Async // 开启异步执行 public CompletableFuture<String> asyncProcess() { // 这里会在Spring线程池的线程执行 // 线程名:task-1 或 SimpleAsyncTaskExecutor-1 // 不是原来的Tomcat线程! String threadName = Thread.currentThread().getName(); log.info("异步方法线程: {}", threadName); return CompletableFuture.completedFuture("done"); } public void callAsyncMethod() { // 当前线程:http-nio-8081-exec-3(Tomcat线程) // 调用异步方法 CompletableFuture<String> future = asyncProcess(); // 立即返回,不会阻塞 future.thenAccept(result -> { // 这里在异步方法的线程执行回调 }); } }⚠️ 线程切换带来的问题:MDC丢失
MDC的工作原理
java
// MDC基于ThreadLocal public class MDC { private static final ThreadLocal<Map<String, String>> context = new ThreadLocal<>(); public static void put(String key, String val) { // 存入当前线程的ThreadLocal getContext().put(key, val); } public static String get(String key) { // 从当前线程的ThreadLocal获取 Map<String, String> map = context.get(); return map == null ? null : map.get(key); } } // 关键结论:线程切换 = MDC丢失!问题示例
java
@Async public CompletableFuture<String> asyncTask() { // 新线程:MDC为空! log.info("TraceId: {}", MDC.get("traceId")); // null return CompletableFuture.completedFuture("done"); } // MQ消费者 @RabbitListener(queues = "order.queue") public void process(OrderMessage message) { // MQ监听线程:MDC为空! log.info("处理订单,TraceId: {}", MDC.get("traceId")); // null }🛠️ 解决方案:跨线程传递TraceId
方案1:手动传递(不推荐)
java
@Async public CompletableFuture<String> asyncTask() { // 手动传递参数 String traceId = MDC.get("traceId"); String userId = MDC.get("userId"); return CompletableFuture.supplyAsync(() -> { // 在新线程恢复 MDC.put("traceId", traceId); MDC.put("userId", userId); try { return doTask(); } finally { MDC.clear(); } }); }方案2:使用TaskDecorator(Spring异步)
java
@Configuration @EnableAsync public class AsyncConfig implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setTaskDecorator(new MdcTaskDecorator()); return executor; } class MdcTaskDecorator implements TaskDecorator { @Override public Runnable decorate(Runnable runnable) { // 保存当前线程的MDC Map<String, String> context = MDC.getCopyOfContextMap(); return () -> { try { // 在新线程恢复MDC if (context != null) { MDC.setContextMap(context); } runnable.run(); } finally { MDC.clear(); } }; } } }方案3:TransmittableThreadLocal(阿里开源)
xml
<!-- 依赖 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>transmittable-thread-local</artifactId> <version>2.14.2</version> </dependency>
java
// 使用TTL代替MDC public class TraceContext { private static final TransmittableThreadLocal<String> TRACE_ID = new TransmittableThreadLocal<>(); public static void setTraceId(String traceId) { TRACE_ID.set(traceId); } public static String getTraceId() { return TRACE_ID.get(); } } // 在线程池中使用 ExecutorService executor = TtlExecutors.getTtlExecutorService( Executors.newFixedThreadPool(10) ); executor.submit(() -> { // 自动获取父线程的TraceId log.info("TraceId: {}", TraceContext.getTraceId()); });方案4:CompletableFuture包装
java
public class TraceAwareCompletableFuture { public static <U> CompletableFuture<U> supplyAsync( Supplier<U> supplier) { // 捕获当前上下文 Map<String, String> context = MDC.getCopyOfContextMap(); return CompletableFuture.supplyAsync(() -> { // 恢复上下文 if (context != null) { MDC.setContextMap(context); } try { return supplier.get(); } finally { MDC.clear(); } }); } } // 使用 TraceAwareCompletableFuture.supplyAsync(() -> { log.info("异步任务,TraceId: {}", MDC.get("traceId")); // 能获取 return "result"; });方案5:MQ消息头传递
java
// 发送MQ时携带TraceId public void sendOrderMessage(Order order) { MessageProperties properties = new MessageProperties(); // 将TraceId放入消息头 properties.setHeader("traceId", MDC.get("traceId")); properties.setHeader("userId", MDC.get("userId")); Message message = new Message( JsonUtil.toJson(order).getBytes(), properties ); rabbitTemplate.send("order.exchange", "order.create", message); } // 接收MQ时恢复 @RabbitListener(queues = "order.queue") public void processOrder(Message message) { // 从消息头获取 String traceId = (String) message.getMessageProperties() .getHeaders().get("traceId"); String userId = (String) message.getMessageProperties() .getHeaders().get("userId"); // 恢复MDC MDC.put("traceId", traceId); MDC.put("userId", userId); try { // 处理消息 process(message); } finally { MDC.clear(); } }📊 各场景线程与TraceId传递总结
| 场景 | 是否同一线程 | TraceId传递方式 | 解决方案 |
|---|---|---|---|
| 网关→业务服务 | ❌ 不同 | HTTP Header | 自动传递 |
| 同步HTTP调用 | ✅ 同一线程阻塞 | HTTP Header(需配置拦截器) | RestTemplate拦截器 |
| 异步HTTP调用 | ❌ 不同 | HTTP Header + 上下文传递 | AsyncRestTemplate + TaskDecorator |
| @Async方法 | ❌ 不同 | 线程上下文传递 | TaskDecorator配置 |
| MQ发送/消费 | ❌ 不同 | 消息Header | 消息属性传递 |
| 线程池任务 | ❌ 不同 | 任务包装传递 | TTL或手动传递 |
📝 最佳实践建议
1. 统一TraceId传播机制
java
// 创建TraceContext统一管理 @Component public class TraceContext { // 获取当前上下文 public static Map<String, String> capture() { return MDC.getCopyOfContextMap(); } // 恢复上下文 public static void restore(Map<String, String> context) { if (context != null) { MDC.setContextMap(context); } } // 包装Runnable public static Runnable wrap(Runnable task) { Map<String, String> context = capture(); return () -> { restore(context); try { task.run(); } finally { MDC.clear(); } }; } // 包装Callable public static <T> Callable<T> wrap(Callable<T> task) { Map<String, String> context = capture(); return () -> { restore(context); try { return task.call(); } finally { MDC.clear(); } }; } }2. 配置完整的异步支持
java
@Configuration public class ThreadConfig { // 1. Spring异步支持 @Bean public TaskDecorator mdcTaskDecorator() { return new MdcTaskDecorator(); } // 2. 线程池配置 @Bean("traceAwareExecutor") public Executor traceAwareExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setTaskDecorator(mdcTaskDecorator()); return executor; } // 3. RestTemplate传播 @Bean public RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(); restTemplate.setInterceptors(Collections.singletonList( new TracePropagationInterceptor() )); return restTemplate; } }🎯 核心结论
网关和业务服务肯定不是同一个线程(不同Tomcat实例)
业务服务内部同步调用通常是同一线程(除非异步)
异步操作、MQ、线程池都会切换线程
线程切换会导致MDC中的TraceId丢失
必须手动传递TraceId(通过参数、消息头、线程包装)
最终建议:在你的系统中,对于所有异步场景,都需要实现TraceId的跨线程传递,否则分布式追踪会在异步处断裂。使用统一的TraceContext工具类来管理所有上下文传递。