开发者

SpringCloud通过MDC实现分布式链路追踪

目录
  • 引言
  • 通用部分
    • 封装TraceIdUtil工具类
    • logback.XML日志文件的修改
  • biff 模块
    • 创建过滤器
    • 配置过滤器生效
    • figen接口发送的head修改
    • 统一返回处理
  • 非biff模块
    • 创建过滤器
    • 配置过滤器生效
  • 线程池

    引言

    在DDD领域驱动设计中,我们使用SpringCloud来去实现,但排查错误的时候,通常会想到Skywalking,但是引入一个新的服务,增加了系统消耗和管理学习成本,对于大型项目比较适合,但是小的项目显得太过臃肿了,我们此时就可以使用TraceId,将其存放到MDC中,返回的时候参数带上它,访问的时候日志打印出来,每次访问生成的TraceId不同,这样可以实现分布式链路追踪的问题。

    通用部分

    封装TraceIdUtil工具类

    import org.apache.commons.lang3.StringUtils;
    import org.slf4j.MDC;
    import cn.hutool.core.util.IdUtil;
    
    public class TraceIdUtil {
    
        public static final String TRACE_ID_KEY = "TraceId";
    
        /**
         * 生成TraceId
         * @return
         */
        public static String generateTraceId(){
            String traceId = IdUtil.fastSimpleUUID().toUpperCase();
            MDC.put(TRACE_ID_KEY,traceId);
            return traceId;
        }
    
        /**
         * 生成TraceId
         * @return
         */
        public static String generateTraceId(String traceId){
            if(StringUtils.isBlank(traceId)){
                return generateTraceId();
            }
            MDC.put(TRACE_ID_KEY,traceId);
            return traceId;
        }
    
        /**
         * 获取TraceId
         * @return
         */
        public static String getTraceId(){
            return MDC.get(TRACE_ID_KEY);
        }
    
        /**
         * 移除TraceId
         * @return
         */
        public static void removeTraceId(){
            MDC.remove(TRACE_ID_KEY);
        }
    }
    

    logback.xml日志文件的修改

    <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [TRACEID:%X{TraceId}]  [%thread] %-5level %logger{36} -%msg%n</Pattern>
    

    需注意:

    SpringCloud通过MDC实现分布式链路追踪

    biff 模块

    创建过滤器

    import Java.io.IOException;
    import javax.servlet.Filter;
    import javax.servlet.FilterChain;
    import javax.servlet.FilterConfig;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.annotation.WebFilter;
    import javax.servlet.http.HttpServletRequest;
    import org.apache.commons.lang3.StringUtils;
    import com.karry.admin.bff.common.util.TraceIdUtil;
    import cn.hutool.core.util.IdUtil;
    import lombok.extern.slf4j.Slf4j;
    
    @Slf4j
    @WebFilter
    public class TraceFilter implements Filter {
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            log.info("Init Trace filter   init.......");
            pythonSystem.out.println("Init Trace filter  init.......");
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
            try {
                HttpServletRequest servletRequest = (HttpServletRequest) request;
                String gateWayTraceId = ((HttpSdxAGoSwUServletRequest) request).getHeader(TraceIdUtil.TRACE_ID_KEY);
                String traceId = TraceIdUtil.generateTraceId(StringUtils.isEmpty(gateWayTraceId)
                        ? IdUtil.fastSimpleUUID().toUpperCase()
                        : gateWayTraceId
                );
                // 创建新的请求包装器
                log.info("TraceIdUtil.getTraceId():"+TraceIdUtil.getTraceId());
                //将请求和应答交给下一个处理器处理
                filterChain.doFilter(request,response);
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                //最后移除,不然有可能造成内存泄漏
                TraceIdUtil.removeTraceId();
            }
        }
    
        @Override
        public void destroy() {
            log.info("Init Trace filter  destroy.......");
        }
    }
    
    

    配置过滤器生效

    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.boot.web.servlet.FilterRegistrationBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.Ordered;
    import org.springframework.core.annotation.Order;
    import com.karry.admin.bff.common.filter.TraceFilter;
    import lombok.extern.slf4j.Slf4j;
    
    @Slf4j
    @Configuration
    public class WebConfiguration {
    
        @Bean
        @ConditionalOnMissingBean({TraceFilter.class})
        @Order(Ordered.HIGHEST_PRECEDENCE + 100)
        public FilterRegistrationBean<TraceFilter> traceFilterBean(){
            FilterRegistrationBean<TraceFilter> bean = new FilterRegistrationBean<>();
            bean.setFilter(new TraceFilter());
            bean.addUrlPatterns("/*");
            return bean;
        }
    }
    

    figen接口发送的head修改

    此处修改了发送的请求的header,在其他模块就可以获取从biff层生成的traceId了。

    import org.springframework.context.annotation.Configuration;
    import com.karry.admin.bff.common.util.TraceIdUtil;
    import feign.RequestInter编程ceptor;
    import feign.RequestTemplate;
    
    @Configuration
    public class FeignRequestInterceptor implements RequestInterceptor {
        @Override
        public void apply(RequestTemplate template){
            String traceId = TraceIdUtil.getTraceId();
            //当前线程调用中有traceId,则将该traceId进行透传
            if (traceId != null) {
                template.header(TraceIdUtil.TRACE_ID_KEY,TraceIdUtil.getTraceId());
            }
        }
    }
    

    统一返回处理

    此种情况时针对BaseResult,,这种统一返回的对象无法直接修改的情况下使用的,如果可以直接修改:

        /**
         * 链路追踪TraceId
         */
        public String traceId = TraceIdUtil.getTraceId();
    

    不可以直接修改就用响应拦截器进行处理:

    import org.springframework.core.MethodParameter;
    import org.springframework.http.MediaType;
    import org.springframework.http.server.ServerHttpRequest;
    import org.springframework.http.server.ServerHttpResponse;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
    import com.karry.app.common.utils.TraceIdUtil;
    import com.karry.order.sdk.utils.BeanCopyUtils;
    import com.souche.platform.common.model.base.BaseResult;
    import lombok.SneakyThrows;
    
    
    @ControllerAdvice
    public class ResponseAdvice implements ResponseBodyAdvice {
        /**
         * 开关,如果是true才会调用beforeBodyWrite
         */
        @Override
        public boolean supports(MethodParameter returnType, Class converterType) {
            return true;
        }
    
    
        @SneakyThrows//异常抛出,相当于方法上throw一个异常
        @Override
        public Object beforeBodyWrite(Object object, MethodParameter methodParameter, MediaType mediaType, Class aClass,
                ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
            BaseResult result = BeanCopyUtils.copy(object, BaseResult.class);
            result.setTraceId(TraceIdUtil.getTraceId());
            return result;
        }
    
    }
    
    

    非biff模块

    创建过滤器

    import java.io.IOException;
    import javax.servlet.Filter;
    import javax.servlet.FilterChain;
    import javax.servlet.FilterConfig;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.annotation.WebFilter;
    import javax.servlet.http.HttpServletRequest;
    import org.apache.commons.lang3.StringUtils;
    import com.karry.app.common.utils.TraceIdUtil;
    import cn.hutool.core.util.IdUtil;
    import lombok.extern.slf4j.Slf4j;
    
    @Slf4j
    @WebFilter
    public class TraceFilter implements Filter {
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            log.info("Init Trace filter   init.......");
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
            try {
                HttpServletRequest servletRequest = (HttpServletRequest) request;
                String gateWayTraceId = ((HttpServletRequest) request).getHeader(TraceIdUtil.TRACE_ID_KEY);
                String traceId = TraceIdUtil.generateTraceId(StringUtils.isEmpty(gateWayTraceId)
                        ? IdUtil.fastSimpleUUID().toUpperCase()
                        : gateWayTraceId
                );
                //将请求和应答交给下一个处理器处理
                filterChain.doFilter(request,response);
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                //最后移除,不然有可能造成内存泄漏
                TraceIdUtil.removeTraceId();
            }
        }
    
        @Override
        public void destroy() {
            log.info("Init Trace filter  destroy.......");
        }
    }
    

    配置过滤器生效

    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.boot.web.servlet.FilterRegistrationBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.Ordered;
    import org.springframework.core.annotation.Order;
    import com.karry.admin.bff.common.filter.TraceFilter;
    import lombok.extern.slf4j.Slf4j;
    
    @Slf4j
    @Configuration
    public class WebConfiguration {
    
        @Bean
        @ConditionalOnMissingBean({TraceFilter.class})
        @Order(Ordeandroidred.HIGHEST_PRECEDENCE + 100)
        public FilterRegistrationBean<TraceFilter> traceFilterBean(){
            FilterRegistrationBean<TraceFilter> bean = new FilterRegistrationBean<>();
            bean.setFilter(new TraceFilter());
            bean.addUrlPatterns("/*");
            return bean;
        }
    }
    

    线程池

    上面对于单线程的情况可以进行解决,traceId和Threadlocal很像,是键值对模式,会有内存溢出问题,还是线程私有的。 所以在多线程的情况下就不能获取主线程的traceId了。我们就需要设置线程工厂包装 Runnable 来解决这个问题。

    import org.slf4j.MDC;
    import org.springframewpythonork.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import java.util.Map;
    import java.util.concurrent.ArrayblockingQueue;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ThreadFactory;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    @Configuration
    public class MyThreadPoolConfig {
        @Bean
        public ThreadPoolExecutor threadPoolExecutor() {
            // 自定义 ThreadFactory
            ThreadFactory threadFactory = new ThreadFactory() {
                private final ThreadFactory defaultFactory = Executors.defaultThreadFactory();
                private final String namePrefix = "Async---";
    
                @Override
                public Thread newThread(Runnable r) {
                    // 获取主线程的 MDC 上下文
                    Map<String, String> contextMap = MDC.getCopyOfContextMap();
    
                    // 包装 Runnable 以设置 MDC 上下文
                    Runnable wrappedRunnable = () -> {
                        try {
                            // 设置 MDC 上下文
                            MDC.setContextMap(contextMap);
                            // 执行任务
                            r.run();
                        } finally {
                            // 清除 MDC 上下文
                            MDC.clear();
                        }
                    };
    
                    Thread thread = defaultFactory.newThread(wrappedRunnable);
                    thread.setName(namePrefix + thread.getName());
                    return thread;
                }
            };
    
            ThreadPoolExecutor executor = new ThreadPoolExecutor(
                    5,
                    10,
                    30L,
                    TimeUnit.SECONDS,
                    new ArrayBlockingQueue<>(500),
                    threadFactory,
                    new ThreadPoolExecutor.CallerRunsPolicy());
            return executor;
        }
    }
    

    以上就是SpringCloud通过MDC实现分布式链路追踪的详细内容,更多关于SpringCloud MDC链路追踪的资料请关注编程客栈(www.devze.com)其它相关文章!

    0

    上一篇:

    下一篇:

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    最新开发

    开发排行榜