dubbo项目traceId链路传递(MDC方案及重复traceId处理)

1.traceId用途

        主要用于项目dubbo接口调用链日志追踪使用,可以获取完整的链路日志,协助排查问题。

2.traceId传递及代码实现

        本方案是基于 org.slf4j.MDC 进行实现,会出现线程池中线程复用导致traceId重复问题,后面会说解决方案。

  • web项目(CONSUMER)

    • com.alibaba.dubbo.rpc.Filter 文件,路径在src\main\resources\META-INF\dubbo目录下面,文件内容就是对应项目中指定的过滤器类路径
      • globalTraceFilter=com.xxx.filter.GlobalTraceFilter

        dubbo项目traceId链路传递(MDC方案及重复traceId处理)_第1张图片

  • GlobalTraceFilter类里面代码如下,实现dubbo的Filter接口:

    package com.xxx.filter;
    
    import lombok.extern.slf4j.Slf4j;
    import org.apache.dubbo.common.constants.CommonConstants;
    import org.apache.dubbo.common.extension.Activate;
    import org.apache.dubbo.rpc.*;
    import org.slf4j.MDC;
    
    import java.util.UUID;
    
    /**
     * @Description 过滤器传递tradeId(消费者)
     * @Version v1.0
     */
    @Activate(group = {CommonConstants.CONSUMER})
    @Slf4j
    public class GlobalTraceFilter implements Filter {
        private static final String TRACE_ID = "TraceId";
    
        @Override
        public Result invoke(Invoker invoker, Invocation invocation) throws RpcException {
            RpcContext rpcContext = RpcContext.getContext();
            String traceId;
            if (rpcContext.isConsumerSide()) {
                traceId = MDC.get(TRACE_ID);
                if (traceId == null) {
                    traceId = UUID.randomUUID().toString().replace("-", "");
                }
                MDC.put(TRACE_ID, traceId);
                rpcContext.setAttachment(TRACE_ID, traceId);
            }
            return invoker.invoke(invocation);
        }
    }
    
  • logback-spring.xml配置
     

    
    
        
    
        
        
        
        
    
    
        
            
                
                    %d{yyyy-MM-dd HH:mm:ss.SSS},[%X{TraceId}] [%thread] %highlight(%-5level) %cyan(%logger{15}) - %highlight(%msg) %n%exception
                
            
        
    
    
           
        
            ${default_log}
            
                ${default_log}.%d{yyyy-MM-dd}
                60
                50GB
            
            
                
                    %d{yyyy-MM-dd HH:mm:ss.SSS},[%X{TraceId}] [%thread] %-5level %logger{36} %method:%L - %msg %n
                
            
        
    
    
        
        
            ${error_log}
            
                ${error_log}.%d{yyyy-MM-dd}
                60
                10GB
            
            
                
                    %d{yyyy-MM-dd HH:mm:ss.SSS},[%X{TraceId}] [%thread] %-5level %logger{36} %method:%L - %msg%n%exception
                
            
            
                ERROR
                ACCEPT
                DENY
            
        
    	
        
        
            
            
            
        
    
    
    • api项目(CONSUMER&PROVIDER)

      • com.alibaba.dubbo.rpc.Filter 文件,路径在src\main\resources\META-INF\dubbo目录下面,文件内容就是对应项目中指定的过滤器类路径
        • globalTraceFilter=com.xxx.filter.GlobalTraceFilter

          dubbo项目traceId链路传递(MDC方案及重复traceId处理)_第2张图片

  • GlobalTraceFilter类里面代码如下,实现dubbo的Filter接口:
     

    package com.xxx.filter;
    
    import org.apache.dubbo.common.constants.CommonConstants;
    import org.apache.dubbo.common.extension.Activate;
    import org.apache.dubbo.rpc.*;
    import org.slf4j.MDC;
    
    import java.util.UUID;
    
    /**
     * @Description 过滤器传递tradeId(消费者和生产者)
     * @Version v1.0
     */
    @Activate(group = {CommonConstants.CONSUMER, CommonConstants.PROVIDER})
    public class GlobalTraceFilter implements Filter {
        private static final String TRACE_ID = "TraceId";
    
        @Override
        public Result invoke(Invoker invoker, Invocation invocation) throws RpcException {
            RpcContext rpcContext = RpcContext.getContext();
            String traceId;
            if (rpcContext.isConsumerSide()) {
                traceId = MDC.get(TRACE_ID);
                if (traceId == null) {
                    traceId = UUID.randomUUID().toString();
                }
                rpcContext.setAttachment(TRACE_ID, traceId);
            }else if (rpcContext.isProviderSide()) {
                traceId = rpcContext.getAttachment(TRACE_ID);
                if (traceId == null) {
                    traceId = UUID.randomUUID().toString();
                }
                MDC.put(TRACE_ID, traceId);
            }
            return invoker.invoke(invocation);
        }
    }

  • logback-spring.xml配置和上面一样

3.traceId重复的处理

通过AOP进行处理,controller方法执行完后清除MDC,避免日志线程池中的线程复用导致MDC中traceId还存在则不会生成新的traceId;

web项目中RequestTraceIdAspect类代码如下:

package com.xxx.aspectj;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;

/**
 * @Description
 * @Create 2023/8/31
 */
@Aspect
@Component
public class RequestTraceIdAspect {
    @After("execution(public * com.xxx.controller..*.*(..)) && !execution(* com.xxx.controller.BaseController.*(..))")
    public void afterRequest() {
        // 在请求结束时执行的逻辑,清空MDC中的TraceId,避免线程池中因线程复用导致上次请求的TraceId在后续请求中重复使用
        MDC.clear();
    }
}

特别说明一下 !execution(* com.xxx.controller.BaseController.*(..))排查BaseController中的方法,因为这里项目中Controller有继承BaseController做通用处理,会先调用里面的方法,如果在里面就清空了MDC,再到对应Controller执行真正业务逻辑的时候就没有traceId了;如果没有这种继承关系就不需要这段了

4.mybatis日志未记录到日志中的处理

现象如下图,api项目中sql日志并未显示traceId

mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis日志标准输出改为如下通过slf4j的日志实现输出即可
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.slf4j.Slf4jImpl

希望能帮到各位小伙伴哦~

你可能感兴趣的:(java,springboot,dubbo,dubbo,mybatis,traceId,链路追踪,MDC)