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;
    }
}
相关推荐
cyforkk9 分钟前
警惕生产环境中的“日志炸弹”:Spring MVC 异常处理最佳实践
spring·mvc·状态模式
姓蔡小朋友11 分钟前
RabbitMQ
分布式·rabbitmq
华科易迅23 分钟前
Spring 代理
java·后端·spring
波波七28 分钟前
maven导入spring框架
数据库·spring·maven
深蓝轨迹29 分钟前
Redis 分布式锁实现流程
数据库·redis·分布式
摇滚侠1 小时前
Spring Data Redis 主从集群 哨兵集群 分片集群 yml 配置
redis·python·spring
2301_767902641 小时前
ceph分布式存储(三)
分布式·ceph
二进制person1 小时前
JavaEE进阶 --Spring Framework、Spring Boot和Spring MVC(1)
spring boot·spring·java-ee
小胖java1 小时前
基于LDA主题模型与情感分析的航空客户满意度分析
java·spring boot·spring
华科易迅1 小时前
Spring AOP(XML后置+异常通知)
xml·java·spring