当前位置: 首页 > news >正文

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 {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {log.info("Init Trace filter   init.......");System.out.println("Init Trace filter  init.......");}@Overridepublic 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();}}@Overridepublic 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 {@Overridepublic 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*/@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}@SneakyThrows//异常抛出,相当于方法上throw一个异常@Overridepublic 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 {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {log.info("Init Trace filter   init.......");}@Overridepublic 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();}}@Overridepublic 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 {@Beanpublic ThreadPoolExecutor threadPoolExecutor() {// 自定义 ThreadFactoryThreadFactory threadFactory = new ThreadFactory() {private final ThreadFactory defaultFactory = Executors.defaultThreadFactory();private final String namePrefix = "Async---";@Overridepublic 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;}
}
http://www.lryc.cn/news/474473.html

相关文章:

  • logback日志级别动态切换四种方案
  • AI视频管理平台中使用目标检测模型中的NMS参数原理及设置原则
  • 从零开始点亮一个LED灯 —— keil下载、新建工程、版本烧录、面包板使用、实例代码
  • [pdf,epub]105页《分析模式》漫谈合集01
  • 计算机网络5层模型
  • Python毕业设计选题:基于Python的无人超市管理系统-flask+vue
  • WindowsDocker安装到D盘,C盘太占用空间了。
  • Java面试经典 150 题.P80. 删除有序数组中的重复项 II(004)
  • 【Three.js】SpriteMaterial 加载图片泛白,和原图片不一致
  • 了解神经网络中的激活函数
  • 配置BGP与IGP交互和路由自动聚合示例
  • 代码随想录算法训练营第三十三天 | 62.不同路径 63.不同路径
  • 使用Flask构建RESTful API
  • 基于springboot的Java学习论坛平台
  • Python离线环境搭建
  • windows下kafka使用出现的问题
  • ctfshow文件包含web78~81
  • 鸿蒙生态认识
  • Hadoop-004-Big Data Tools插件的使用
  • linux8在线扩容/home目录
  • 【C/C++】模拟实现strcpy
  • 网络编程番外——IO多路复用的应用说明
  • 【Java爬虫的淘宝寻宝记】—— 淘宝商品类目的“藏宝图”
  • 探索Python文档自动化的奥秘:揭开docxtpl库的神秘面纱
  • RabbitMQ的解耦、异步、削峰是什么?
  • 4:arm汇编语言4:bits/byte的介绍(ASCII码)与二进制补位
  • C++实现仿安卓线程Handler、Message、Looper的功能
  • 构建安全的用户登录API:从请求验证到JWT令牌生成
  • 状态模式:封装对象状态并改变行为的设计模式
  • 备战“双11”丨AI+物流:你的快递会有什么变化?