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

 更新时间:2024年11月03日 09:34:46   作者:YB_account  
在DDD领域驱动设计中,我们使用SpringCloud来去实现,但排查错误的时候,通常会想到Skywalking,但是引入一个新的服务,增加了系统消耗和管理学习成本,对于大型项目比较适合,但是小的项目显得太过臃肿了,所以本文介绍了SpringCloud通过MDC实现分布式链路追踪

引言

在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>

需注意:

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.......");
        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.......");
    }
}

配置过滤器生效

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.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,,这种统一返回的对象无法直接修改的情况下使用的,如果可以直接修改:

    /**
     * 链路追踪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(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 来解决这个问题。

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;
    }
}

以上就是SpringCloud通过MDC实现分布式链路追踪的详细内容,更多关于SpringCloud MDC链路追踪的资料请关注脚本之家其它相关文章!

相关文章

  • Mybatis两种不同批量插入方式的区别

    Mybatis两种不同批量插入方式的区别

    随着业务需要,有时我们需要将数据批量添加到数据库,mybatis提供了将list集合循环添加到数据库的方法,这篇文章主要给大家介绍了关于Mybatis两种不同批量插入方式的区别,需要的朋友可以参考下
    2021-09-09
  • MyBatis映射文件resultMap元素中使用多个association的方法

    MyBatis映射文件resultMap元素中使用多个association的方法

    这篇文章主要介绍了MyBatis映射文件resultMap元素中使用多个association的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03
  • json-lib将json格式的字符串,转化为java对象的实例

    json-lib将json格式的字符串,转化为java对象的实例

    下面小编就为大家带来一篇json-lib将json格式的字符串,转化为java对象的实例。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-03-03
  • SpringBoot Security权限控制自定义failureHandler实例

    SpringBoot Security权限控制自定义failureHandler实例

    这篇文章主要为大家介绍了SpringBoot Security权限控制自定义failureHandler实例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-11-11
  • SpringCloud网关组件zuul实例解析

    SpringCloud网关组件zuul实例解析

    这篇文章主要介绍了SpringCloud网关组件zuul实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-03-03
  • 浅谈Java方法调用的优先级问题

    浅谈Java方法调用的优先级问题

    这篇文章主要介绍了浅谈Java方法调用的优先级问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-10-10
  • mybatis-plus 使用Condition拼接Sql语句各方法的用法

    mybatis-plus 使用Condition拼接Sql语句各方法的用法

    这篇文章主要介绍了mybatis-plus 使用Condition拼接Sql语句各方法的用法,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • Spring Boot整合Spring Data JPA过程解析

    Spring Boot整合Spring Data JPA过程解析

    这篇文章主要介绍了Spring Boot整合Spring Data JPA过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-10-10
  • Java ES(Elasticsearch) 中的and 和 or 查询

    Java ES(Elasticsearch) 中的and 和 or 查

    Elasticsearch 是一个分布式、高扩展、高实时的搜索与数据分析引擎,es中match查询中,查询字符串分词后,默认是or或者的关系,这篇文章主要介绍了ES 中的and 和 or 查询,需要的朋友可以参考下
    2022-11-11
  • 使用ANT与YUI压缩js的实现方法

    使用ANT与YUI压缩js的实现方法

    本篇文章是对使用ANT与YUI压缩js的实现方法进行了详细的分析介绍,需要的朋友参考下
    2013-05-05

最新评论