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

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

通用部分

封装TraceIdUtil工具类

java 复制代码
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日志文件的修改

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

需注意:

biff 模块

创建过滤器

java 复制代码
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.......");
        System.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 = ((HttpServletRequest) 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.......");
    }
}

配置过滤器生效

java 复制代码
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了。

java 复制代码
import org.springframework.context.annotation.Configuration;
import com.karry.admin.bff.common.util.TraceIdUtil;
import feign.RequestInterceptor;
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,,这种统一返回的对象无法直接修改的情况下使用的,如果可以直接修改:

java 复制代码
    /**
     * 链路追踪TraceId
     */
    public String traceId = TraceIdUtil.getTraceId();

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

java 复制代码
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模块

创建过滤器

java 复制代码
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.......");
    }
}

配置过滤器生效

java 复制代码
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;
    }
}

线程池

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

java 复制代码
import org.slf4j.MDC;
import org.springframework.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;
    }
}
相关推荐
天天扭码2 小时前
五天SpringCloud计划——DAY1之mybatis-plus的使用
java·spring cloud·mybatis
Doker 多克3 小时前
Spring AI 框架使用的核心概念
人工智能·spring·chatgpt
天冬忘忧3 小时前
Kafka 工作流程解析:从 Broker 工作原理、节点的服役、退役、副本的生成到数据存储与读写优化
大数据·分布式·kafka
请叫我青哥6 小时前
第五十二条:谨慎使用重载
java·spring
孟秋与你7 小时前
【spring】spring单例模式与锁对象作用域的分析
java·spring·单例模式
IT枫斗者8 小时前
如何解决Java EasyExcel 导出报内存溢出
java·服务器·开发语言·网络·分布式·物联网
求积分不加C8 小时前
Kafka怎么发送JAVA对象并在消费者端解析出JAVA对象--示例
java·分布式·kafka·linq
luckywuxn8 小时前
Spring Cloud Alibaba、Spring Cloud 与 Spring Boot各版本的对应关系
spring boot·spring·spring cloud
是程序喵呀8 小时前
SpringMVC详解
java·spring·spring-mvc
冷心笑看丽美人9 小时前
Spring 框架七大模块(Java EE 学习笔记03)
学习·spring·架构·java-ee