springboot3.2+jdk21 虚拟线程 使用MDC traceId追踪日志

springboot3.2发布了,配合jdk21使用虚拟线程,使用MDC + traceId追踪日志方法

关于虚拟线程和MDC traceId这里就不多说了,如果不清楚请自行查询资料

第一步,创建MdcVirtualThreadTaskExecutor 


/**
 * @author xxley
 * @date 2022/7/25 15:54
 */
public class MdcVirtualThreadTaskExecutor extends TaskExecutorAdapter {
    
    public MdcVirtualThreadTaskExecutor(Executor concurrentExecutor) {
        super(concurrentExecutor);
    }

    @Override
    public <T> Future<T> submit(Callable<T> callable) {
        Map<String, String> context = MDC.getCopyOfContextMap();
        return super.submit(() -> {
            if (context != null) {
                //将父线程的MDC内容传给子线程
                MDC.setContextMap(context);
            } else {
                //直接给子线程设置MDC
                // 这个方法是我自己封装的,实际是MDC.put("traceId", traceId);
                TraceIdContext.setMCData(null);
            }
            try {
                //执行任务
                return callable.call();
 
`@Trace` 的代理机制通常依赖于 **动态代理** 或 **字节码增强** 技术,具体实现取决于所使用的分布式追踪框架(如 SkyWalking、Sleuth、Zipkin 等)。其核心目标是在方法调用前后插入追踪逻辑(如创建 Span、记录耗时、传递上下文),而无需手动修改业务代码。以下是常见实现方式及原理分析: --- ### 1. **动态代理(Dynamic Proxy)** #### **适用场景**:Spring AOP、JDK 动态代理、CGLIB 代理 - **原理**: 通过生成代理对象,在方法调用前后插入追踪逻辑。适用于接口方法或类方法(需基于子类代理,如 CGLIB)。 - **框架示例**: - **Spring Cloud Sleuth**:默认通过 Spring AOP 实现 `@NewSpan` 或 `@ContinueSpan` 的追踪。 - **自定义 `@Trace`**:若基于 Spring AOP 实现,会使用 `ProxyFactory` 或 `@Aspect` 切面。 #### **代码示例(Spring AOP 实现 `@Trace`)**: ```java @Aspect @Component public class TraceAspect { @Around("@annotation(trace)") public Object trace(ProceedingJoinPoint joinPoint, Trace trace) throws Throwable { // 1. 创建 Span(追踪节点) String spanName = trace.operationName().isEmpty() ? joinPoint.getSignature().getName() : trace.operationName(); Span span = Tracer.startSpan(spanName); try { // 2. 执行目标方法 Object result = joinPoint.proceed(); // 3. 记录成功状态 span.setTag("status", "success"); return result; } catch (Exception e) { // 4. 记录错误状态 span.setTag("status", "error"); span.log("Exception: " + e.getMessage()); throw e; } finally { // 5. 结束 Span span.finish(); } } } ``` #### **特点**: - **优点**:实现简单,与 Spring 生态无缝集成。 - **缺点**: - 仅能拦截 Spring 管理的 Bean 方法(无法拦截静态方法、私有方法)。 - 通过接口代理时(JDK 动态代理),性能略低于 CGLIB。 --- ### 2. **字节码增强(Bytecode Instrumentation)** #### **适用场景**:SkyWalking、Pinpoint、Jaeger 等 - **原理**: 在编译时或类加载时修改字节码,直接在方法体内插入追踪代码。无需依赖代理对象,性能更高。 - **框架示例**: - **SkyWalking**:使用 Java Agent 在类加载时增强字节码,通过 `@Trace` 标记方法。 - **Arthas**:类似技术,用于动态调试。 #### **代码示例(SkyWalking 的 `@Trace` 注解)**: ```java import org.apache.skywalking.apm.toolkit.trace.Trace; @Service public class OrderService { @Trace(operationName = "validateOrder") public boolean validate(Order order) { // 业务逻辑(字节码增强后会自动插入追踪逻辑) return true; } } ``` #### **底层实现**: 1. **Java Agent**: SkyWalking 通过 `-javaagent` 参数注入 Agent,在类加载时修改字节码。 ```java public class SkyWalkingAgent { public static void premain(String args, Instrumentation inst) { inst.addTransformer(new TraceTransformer()); } } ``` 2. **字节码修改**: 在方法开头插入 `Span` 创建逻辑,在方法结尾插入 `Span` 结束逻辑。 #### **特点**: - **优点**: - 性能高(无代理对象开销)。 - 支持静态方法、私有方法等 Spring AOP 无法拦截的场景。 - **缺点**: - 实现复杂,需处理字节码操作。 - 需在启动时添加 `-javaagent` 参数。 --- ### 3. **编译时增强(Annotation Processing)** #### **适用场景**:Lombok、MapStruct 等 - **原理**: 在编译阶段通过注解处理器生成额外代码(如追踪逻辑)。较少用于 `@Trace`,但某些框架可能结合使用。 - **示例**: 若 `@Trace` 注解处理器在编译时生成 `AOP` 代理类,效果类似动态代理。 #### **特点**: - **优点**:无运行时开销。 - **缺点**:灵活性低,需重新编译代码。 --- ### 4. **不同框架的代理机制对比** | 框架 | 代理方式 | 适用场景 | 性能开销 | |---------------|-------------------|------------------------------|----------| | Spring Sleuth | Spring AOP | Spring Boot 微服务 | 中 | | SkyWalking | Java Agent + 字节码 | 高性能分布式追踪 | 低 | | Zipkin + Brave| 手动 `Tracer` API | 需要精细控制 Span 的场景 | 高(手动)| --- ### 5. **关键问题与解决方案** #### **问题 1:代理链顺序混乱** - **场景**:`@Trace` 和自定义 AOP 注解同时存在,导致追踪上下文未初始化时自定义逻辑已执行。 - **解决**:通过 `@Order` 注解控制切面优先级: ```java @Aspect @Order(1) // 先执行 public class TraceAspect { ... } @Aspect @Order(2) // 后执行 public class CustomAspect { ... } ``` #### **问题 2:线程上下文传递** - **场景**:异步调用(如 `@Async`)时,`TraceId` 丢失。 - **解决**: - SkyWalking 自动通过 `ThreadLocal` 传递上下文。 - 手动实现时需在代理逻辑中显式传递: ```java @Around("...") public Object trace(ProceedingJoinPoint joinPoint) { String traceId = generateTraceId(); // 存入 ThreadLocal 或 MDC try { return joinPoint.proceed(); } finally { // 清理 ThreadLocal } } ``` #### **问题 3:代理失效** - **场景**:方法被 `final` 修饰或类被 `final` 修饰,导致 CGLIB 无法代理。 - **解决**: - 使用字节码增强框架(如 SkyWalking)。 - 避免对 `final` 方法使用 `@Trace`。 --- ### 6. **如何选择代理机制?** | 需求场景 | 推荐机制 | |------------------------------|------------------------| | Spring 生态内快速集成 | Spring AOP | | 高性能、无侵入追踪 | 字节码增强(如 SkyWalking) | | 需要支持静态/私有方法追踪 | 字节码增强 | | 简单日志追踪 | 手动 `Tracer` API | --- ### 总结: - **`@Trace` 的代理机制**主要分为 **动态代理**(Spring AOP)和 **字节码增强**(SkyWalking)。 - **动态代理**:简单易用,但有局限性(如无法拦截 `final` 方法)。 - **字节码增强**:高性能、无侵入,但需配置 `-javaagent`。 - **关键点**:确保代理顺序正确、上下文传递无误,并处理代理失效场景。
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值