traceId 传递-MQ

mq 发送消息

java 复制代码
    private <T extends BaseEvent> MessageBuilder<T> toMessageBuilder(final T event) {
        if (StringUtils.isBlank(event.keys())) {
            throw new RuntimeException("keys是必填项");
        }
        // 获取tag,默认使用类名
        String tags = StringUtils.defaultString(event.tags(), event.getClass().getSimpleName());
        // 构建消息
        MessageBuilder<T> messageBuilder = MessageBuilder.withPayload(event)
            .setHeader(RocketMQHeaders.TAGS, tags)
            .setHeader(RocketMQHeaders.KEYS, event.keys());
        String traceId = MDC.get(Constants.MDC_TRACE_ID);
        if (StringUtils.isNotBlank(traceId)) {
            messageBuilder.setHeader(RocketMQConsts.Header.TRACE_ID, traceId);
        }
        String env = RequestThread.getValue(Constants.ENV);
        if (StringUtils.isNotBlank(env)) {
            messageBuilder.setHeader(RocketMQConsts.Header.ENV, env);
        }
        String desc = event.desc();
        if (StringUtils.isNotBlank(desc)) {
            messageBuilder.setHeader(RocketMQConsts.Header.DESC, desc);
        }
        String producerApplicationName = environment.getProperty(Constants.SPRING_APPLICATION_NAME, DEFAULT_PRODUCER);
        messageBuilder.setHeader(RocketMQConsts.Header.PRODUCER, producerApplicationName);
        return messageBuilder;
    }

mq消费

java 复制代码
  public <T extends BaseEvent> void process(final String key, final Message<T> message, final Consumer<T> function) {
        String cacheKey = RedisKeyUtil.generate(REDIS_REPEAT_PREFIX_KEY, key);
        KeyInfo keyInfo = KeyInfo.builder()
            .prefix(REDIS_REPEAT_PREFIX_KEY)
            .keys(new String[]{key})
            .waitTime(3)
            .timeUnit(TimeUnit.SECONDS)
            .build();
        // 获取消息体
        T event = message.getPayload();
        try {
            // 获取链路追踪id
            String traceId = getHeaderValue(message, RocketMQConsts.Header.TRACE_ID);
            MDC.put(Constants.MDC_TRACE_ID, StringUtils.isBlank(traceId) ? key : traceId);
            // 设置调度tag环境
            String env = getHeaderValue(message, RocketMQConsts.Header.ENV);
            RequestThread.addParam(Constants.ENV, env);
            //对相同key的消息加锁,保证相同的消息是串行消费
            lockService.lock(keyInfo, () -> {
                //判断该消息是否已处理
                if (redisCache.exists(cacheKey)) {
                    log.info("[MQ消息-已处理]--[{}]:{}", key, ReflectUtils.processObject(event));
                    return null;
                }
                log.info("[MQ消息-开始处理]--[{}]:{}", key, ReflectUtils.processObject(event));
                function.accept(event);
                log.info("[MQ消息-处理完成]--{}", key);
                //缓存处理结果一小时,一个小时内不重复消费
                redisCache.setCacheObject(cacheKey, DateUtil.now(), 60, TimeUnit.MINUTES);
                return null;
            });
        } catch (LockException e) {
            //加锁失败,说明有线程正在消费
            log.warn("[MQ消息-正在处理]--[{}]:{}", key, ReflectUtils.processObject(event));
            throw e;
        } catch (Exception e) {
            log.error("[MQ消息-处理异常]--[{}]:{}", key, event, e);
            throw e;
        } finally {
            MDC.clear();
            RequestThread.clear();
        }
    }

问题

为什么mq消费可以从mac中获取traceId 传递给消息请求头

这个代码展示了 TraceId 如何从 HTTP 请求上下文传播到 MQ 消息 的完整链路。让我详细解释为什么这里可以从 MDC 获取到 traceId:

🔄 完整的传播链路

🎯 为什么可以从 MDC 获取 traceId?

关键:在同一个线程内!

java

复制代码
// 时间线分析(假设单线程同步处理):
1. HTTP请求进入 → HeaderFilter.doFilterInternal()
   ↓
2. HeaderFilter设置MDC:
   String traceId = request.getHeader("traceId");
   MDC.put("traceId", traceId);  // 存入当前线程的ThreadLocal
   ↓
3. 调用Controller → Service → MQ发送方法
   ↓
4. MQ发送方法执行:
   String traceId = MDC.get("traceId");  // 从同一个线程的ThreadLocal获取!
   ↓
5. HeaderFilter清理:
   finally { MDC.clear(); }  // 清理,但已经用过了

线程状态可视化

java

复制代码
public class ThreadStateDemo {
    
    public void processOrder() {
        // 时间点1:HeaderFilter执行前
        // 线程:http-nio-8080-exec-1
        // MDC状态:{}
        
        // HeaderFilter执行
        MDC.put("traceId", "TRACE-123");
        // MDC状态:{"traceId": "TRACE-123"}
        
        // 时间点2:Controller执行
        // 仍然是同一个线程:http-nio-8080-exec-1
        String traceId1 = MDC.get("traceId");  // "TRACE-123"
        
        // 时间点3:Service执行,调用MQ发送
        // 仍然是同一个线程:http-nio-8080-exec-1
        sendMQEvent();
        
        // sendMQEvent内部:
        String traceId2 = MDC.get("traceId");  // "TRACE-123" ✅
        
        // 时间点4:HeaderFilter finally清理
        MDC.clear();
        // MDC状态:{}
    }
    
    private void sendMQEvent() {
        // 关键:这里和调用者在同一个线程!
        System.out.println("当前线程: " + Thread.currentThread().getName());
        // 输出:http-nio-8080-exec-1
        
        String traceId = MDC.get("traceId");
        System.out.println("获取traceId: " + traceId);  // "TRACE-123"
    }
}

📊 完整的上下文传播表

阶段 组件 上下文存储位置 如何获取 是否同一线程
网关接收 TraceGlobalFilter 请求Header request.getHeader() N/A
网关传递 TraceGlobalFilter 请求Header header.set() 网关线程
业务服务接收 HeaderFilter 请求Header request.getHeader() 业务线程
业务服务存储 HeaderFilter MDC (ThreadLocal) MDC.put() 业务线程
Controller使用 Controller方法 MDC MDC.get() 同一业务线程
Service使用 Service方法 MDC MDC.get() 同一业务线程
MQ发送 MQ发送器 MDC MDC.get() 同一业务线程
MQ消息头 RocketMQ消息 Message Header setHeader() 同一业务线程

🔧 代码执行流程详解

1. 请求进入(设置MDC)

java

复制代码
@Order(Ordered.HIGHEST_PRECEDENCE)
public class HeaderFilter extends OncePerRequestFilter {
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, ...) {
        try {
            // 从HTTP header获取traceId
            String traceId = request.getHeader(Constants.MDC_TRACE_ID);
            
            // 设置到当前线程的MDC
            MDC.put(Constants.MDC_TRACE_ID, 
                StringUtils.defaultString(traceId, 
                    String.valueOf(System.currentTimeMillis())));
            
            // 继续处理请求
            chain.doFilter(request, response);  // → 进入Controller
            
        } finally {
            MDC.clear();  // 请求处理完后清理
        }
    }
}

2. 业务处理(使用MDC)

java

复制代码
@Service
public class OrderService {
    
    @Autowired
    private EventPublisher eventPublisher;
    
    public void createOrder(OrderRequest request) {
        // 这里可以直接使用MDC中的traceId
        String currentTraceId = MDC.get(Constants.MDC_TRACE_ID);
        log.info("创建订单,traceId: {}", currentTraceId);
        
        // 执行业务逻辑...
        Order order = saveOrder(request);
        
        // 发送MQ事件
        OrderCreatedEvent event = new OrderCreatedEvent(order);
        eventPublisher.publish(event);  // → 进入MQ发送代码
    }
}

3. MQ发送(从MDC获取)

java

复制代码
public class EventPublisher {
    
    private <T extends BaseEvent> MessageBuilder<T> toMessageBuilder(final T event) {
        // 关键:这里和Service方法在同一个线程
        // 所以可以获取到MDC中的traceId
        
        String traceId = MDC.get(Constants.MDC_TRACE_ID);  // ✅ 能获取到!
        if (StringUtils.isNotBlank(traceId)) {
            // 设置到MQ消息头
            messageBuilder.setHeader(RocketMQConsts.Header.TRACE_ID, traceId);
        }
        
        // 同样获取其他上下文
        String env = RequestThread.getValue(Constants.ENV);  // ThreadLocal
        if (StringUtils.isNotBlank(env)) {
            messageBuilder.setHeader(RocketMQConsts.Header.ENV, env);
        }
        
        return messageBuilder;
    }
}

📝 总结:为什么能从MDC获取traceId

核心原因:MQ发送代码与HTTP请求处理在同一个Tomcat线程内执行。

线程连续性:HTTP请求线程 → Controller → Service → MQ发送,都在同一个线程

MDC的线程绑定:MDC基于ThreadLocal,线程内共享

生命周期匹配:在HeaderFilter清理MDC之前,MQ发送已经完成

设计意图:这正是MDC的设计目的------在请求处理链路中共享上下文

关键时间线:

所以你的MQ发送代码能正确获取traceId,这正是分布式追踪系统设计精妙之处:通过线程上下文在同步处理链路上自动传递追踪信息。

相关推荐
思想在飞肢体在追21 分钟前
Springboot项目配置Nacos
java·spring boot·后端·nacos
cyforkk23 分钟前
09、Java 基础硬核复习:异常处理(容错机制)的核心逻辑与面试考点
java·数据库·面试
??(lxy)44 分钟前
java高性能无锁队列——MpscLinkedQueue
java·开发语言
数研小生1 小时前
Full Analysis of Taobao Item Detail API taobao.item.get
java·服务器·前端
Wang15301 小时前
Java编程基础与面向对象核心概念
java
毕设源码-郭学长1 小时前
【开题答辩全过程】以 康复管理系统为例,包含答辩的问题和答案
java
毅炼1 小时前
hot100打卡——day17
java·数据结构·算法·leetcode·深度优先
winfreedoms1 小时前
java-网络编程——黑马程序员学习笔记
java·网络·学习
开开心心就好2 小时前
键盘改键工具免安装,自定义键位屏蔽误触
java·网络·windows·随机森林·计算机外设·电脑·excel
IManiy2 小时前
总结之Temporal全局速率控制(二)第三方速率控制服务设计
java