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

TTL+日志的MDC实现简易链路追踪

通过MDC实现简单链路追踪

需要用到的依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency><!-- Hutool工具包 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency><!-- SLF4J日志 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency><!-- Lombok 依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency><!-- SLF4J API 依赖 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.9</version>
</dependency><!-- SLF4J 实现(例如 Logback)依赖 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.11</version>
</dependency>

Spring配置类配置日志输出格式

配置输出格式,让我们自己加的TraceId可以成功输出

logging:pattern:console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %X{traceId} %logger{36} - %msg%n"server:servlet:encoding:charset: UTF-8enabled: trueforce: true

AOP配置类

生成我们的TraceId放到我们的slf4j的MDC里面

注意为什么我们的只在Controller下往我们的MDC里面加东西呢

因为我们的MDC是共享上下文的,它会往下传递,我们不需要再匹配Service方法再生成一个ID

package com.example.threadpool.Config;import cn.hutool.core.util.IdUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;@Aspect
@Component
public class ControllerLogAspect {public static final String TRACE_ID = "traceId";private static final Logger logger = LoggerFactory.getLogger(ControllerLogAspect.class);// 定义切入点,匹配带有 @Controller、@RestController 或 @Service 注解的类中的所有方法@Pointcut("@within(org.springframework.stereotype.Controller) || @within(org.springframework.web.bind.annotation.RestController) || @within(org.springframework.stereotype.Service)")public void controllerAndServiceMethods() {}@Around("controllerAndServiceMethods()")public Object around(ProceedingJoinPoint point) throws Throwable {try {String traceId = IdUtil.objectId();String fullTraceId = "追踪ID:" + traceId;MDC.put(TRACE_ID, fullTraceId);logger.info("Generated traceId: {}", fullTraceId); // 调试日志return point.proceed();} finally {MDC.remove(TRACE_ID);}}
}

Controller测试类

package com.example.threadpool.Config;import cn.hutool.core.util.IdUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;@Aspect
@Component
public class ControllerLogAspect {public static final String TRACE_ID = "traceId";private static final Logger logger = LoggerFactory.getLogger(ControllerLogAspect.class);// 定义切入点,只匹配带有 @RestController 注解的类中的所有方法@Pointcut("@within(org.springframework.web.bind.annotation.RestController)")public void controllerMethods() {}@Around("controllerMethods()")public Object aroundController(ProceedingJoinPoint point) throws Throwable {try {String traceId = IdUtil.objectId();String fullTraceId = "追踪ID:" + traceId;MDC.put(TRACE_ID, fullTraceId);logger.info("Generated traceId: {}", fullTraceId); // 调试日志return point.proceed();} finally {MDC.remove(TRACE_ID);}}
}

Service测试类

package com.example.threadpool.Config;public interface TestService {public String test();
}
package com.example.threadpool.Config;import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;@Controller
@Slf4j
public class TestServiceImpl implements TestService {@Overridepublic String test() {log.info("这是Service的日志");return null;}
}

输出结果


通过拦截器和请求头实现不同服务之间TraceID的传递

package com.achobeta.intercepter;import cn.hutool.core.util.IdUtil;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;@Component
public class LogInterceptor implements HandlerInterceptor {public static final String TRACE_ID = "traceId";@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//如果有上层调用就用上层的IDString traceId = request.getHeader(TRACE_ID);if (traceId == null) {traceId = IdUtil.objectId();}MDC.put(TRACE_ID, traceId);response.addHeader(TRACE_ID, traceId);return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception {MDC.remove(TRACE_ID);}}

异步情况下MDC的上下文丢失问题

MDC的底层是通过ThreadLocal来绑定到当前线程的

所以在做异步的时候要是不进行特殊处理就会造成MDC的上下文丢失

可以引入TTL来处理MDC的上下文丢失问题


TTL改进MDC实现链路追踪

引入TTL依赖

<dependency><groupId>com.alibaba</groupId><artifactId>transmittable-thread-local</artifactId><version>2.14.2</version>
</dependency>

重写TTL构造线程池方法-线程池适配器

package com.kira.scaffoldmvc.ShutDownHook;import com.alibaba.ttl.TtlCallable;
import com.alibaba.ttl.TtlRunnable;
import org.slf4j.MDC;
import java.util.Map;
import java.util.concurrent.*;public class TtlThreadPoolExecutor extends ThreadPoolExecutor {public TtlThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);}public TtlThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);}public TtlThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);}public TtlThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);}@Overridepublic void execute(Runnable command) {// 获取当前线程的MDC上下文Map<String, String> contextMap = MDC.getCopyOfContextMap();// 使用TtlRunnable包装,确保上下文传递super.execute(TtlRunnable.get(() -> {// 恢复MDC上下文if (contextMap != null) {MDC.setContextMap(contextMap);}try {command.run();} finally {// 不要在这里清除MDC,可能会影响后续任务// 由任务本身决定何时清理}}));}@Overridepublic Future<?> submit(Runnable task) {return super.submit(wrapTask(task));}@Overridepublic <T> Future<T> submit(Callable<T> task) {return super.submit(wrapTask(task));}private Runnable wrapTask(Runnable task) {Map<String, String> contextMap = MDC.getCopyOfContextMap();return TtlRunnable.get(() -> {if (contextMap != null) {MDC.setContextMap(contextMap);}try {task.run();} finally {MDC.clear();}});}private <T> Callable<T> wrapTask(Callable<T> task) {Map<String, String> contextMap = MDC.getCopyOfContextMap();return TtlCallable.get(() -> {if (contextMap != null) {MDC.setContextMap(contextMap);}try {return task.call();} finally {MDC.clear();}});}
}

@Bean注册线程池

package com.kira.scaffoldmvc.ShutDownHook;import com.alibaba.ttl.threadpool.TtlExecutors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;@Configuration
public class ThreadPoolConfig {public static final int CORE_POOL_SIZE = 5;public static final int MAX_POOL_SIZE = 10;public static final int QUEUE_CAPACITY = 100;public static final Long KEEP_ALIVE_TIME = 1L;@Beanpublic ThreadPoolExecutor kiraExecutor1() {return new TtlThreadPoolExecutor(CORE_POOL_SIZE,MAX_POOL_SIZE,KEEP_ALIVE_TIME,TimeUnit.SECONDS,new ArrayBlockingQueue<>(QUEUE_CAPACITY),new ThreadPoolExecutor.AbortPolicy());}@Beanpublic ThreadPoolExecutor kiraExecutor2() {return new TtlThreadPoolExecutor(CORE_POOL_SIZE,MAX_POOL_SIZE,KEEP_ALIVE_TIME,TimeUnit.SECONDS,new ArrayBlockingQueue<>(QUEUE_CAPACITY),new ThreadPoolExecutor.AbortPolicy());}@Beanpublic ThreadPoolExecutor kiraExecutor3() {return new TtlThreadPoolExecutor(CORE_POOL_SIZE,MAX_POOL_SIZE,KEEP_ALIVE_TIME,TimeUnit.SECONDS,new ArrayBlockingQueue<>(QUEUE_CAPACITY),new ThreadPoolExecutor.AbortPolicy());}}

AOP类,调用Controller时放入TraceId

package com.kira.scaffoldmvc.Trace;import cn.hutool.core.util.IdUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;@Aspect
@Component
public class ControllerLogAspect {public static final String TRACE_ID = "traceId";private static final Logger logger = LoggerFactory.getLogger(ControllerLogAspect.class);// 定义切入点,匹配带有 @Controller、@RestController 或 @Service 注解的类中的所有方法@Pointcut("@within(org.springframework.stereotype.Controller) || @within(org.springframework.web.bind.annotation.RestController) || @within(org.springframework.stereotype.Service)")public void controllerAndServiceMethods() {}@Around("controllerAndServiceMethods()")public Object around(ProceedingJoinPoint point) throws Throwable {try {String traceId = IdUtil.objectId();String fullTraceId = "追踪ID:" + traceId;MDC.put(TRACE_ID, fullTraceId);logger.info("Generated traceId: {}", fullTraceId); // 调试日志return point.proceed();} finally {MDC.remove(TRACE_ID);}}
}

Controller接口测试

package com.kira.scaffoldmvc.Trace;import com.kira.scaffoldmvc.POJO.Result;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.concurrent.*;@RestController
@RequestMapping("/trace")
@Slf4j
public class TestTraceController {@ResourceThreadPoolExecutor kiraExecutor1;@GetMapping("/test")Result test(){log.info("测试MDC1");kiraExecutor1.execute(()->{log.info("测试MDC2");});return Result.success(true );}}

测试结果

traceId没有丢失


网关生成TraceId-拦截器放入TraceId

拦截器

拦截器放入TraceId或者生成TraceId

package com.kira.scaffoldmvc.Config;import com.kira.scaffoldmvc.CommonPool.RtaProxyContextInterceptor;
import com.kira.scaffoldmvc.Interceptor.LoginInterceptor;
import com.kira.scaffoldmvc.Trace.TraceInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;//配置我们的拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {//这个是在配置类里面来注册我们的拦截器@Autowiredprivate TraceInterceptor traceInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(traceInterceptor).order(1).excludePathPatterns("/xxxx/xxxx","/xxxx/xxxx")//配置放行路径.addPathPatterns("/**");//拦截路径,/**拦截所有}
}


重写TTL构造线程池方法-线程池适配器

package com.kira.scaffoldmvc.ShutDownHook;import com.alibaba.ttl.TtlCallable;
import com.alibaba.ttl.TtlRunnable;
import org.slf4j.MDC;
import java.util.Map;
import java.util.concurrent.*;public class TtlThreadPoolExecutor extends ThreadPoolExecutor {public TtlThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);}public TtlThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);}public TtlThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);}public TtlThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);}@Overridepublic void execute(Runnable command) {// 获取当前线程的MDC上下文Map<String, String> contextMap = MDC.getCopyOfContextMap();// 使用TtlRunnable包装,确保上下文传递super.execute(TtlRunnable.get(() -> {// 恢复MDC上下文if (contextMap != null) {MDC.setContextMap(contextMap);}try {command.run();} finally {// 不要在这里清除MDC,可能会影响后续任务// 由任务本身决定何时清理}}));}@Overridepublic Future<?> submit(Runnable task) {return super.submit(wrapTask(task));}@Overridepublic <T> Future<T> submit(Callable<T> task) {return super.submit(wrapTask(task));}private Runnable wrapTask(Runnable task) {Map<String, String> contextMap = MDC.getCopyOfContextMap();return TtlRunnable.get(() -> {if (contextMap != null) {MDC.setContextMap(contextMap);}try {task.run();} finally {MDC.clear();}});}private <T> Callable<T> wrapTask(Callable<T> task) {Map<String, String> contextMap = MDC.getCopyOfContextMap();return TtlCallable.get(() -> {if (contextMap != null) {MDC.setContextMap(contextMap);}try {return task.call();} finally {MDC.clear();}});}
}

@Bean注册线程池

package com.kira.scaffoldmvc.ShutDownHook;import com.alibaba.ttl.threadpool.TtlExecutors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;@Configuration
public class ThreadPoolConfig {public static final int CORE_POOL_SIZE = 5;public static final int MAX_POOL_SIZE = 10;public static final int QUEUE_CAPACITY = 100;public static final Long KEEP_ALIVE_TIME = 1L;@Beanpublic ThreadPoolExecutor kiraExecutor1() {return new TtlThreadPoolExecutor(CORE_POOL_SIZE,MAX_POOL_SIZE,KEEP_ALIVE_TIME,TimeUnit.SECONDS,new ArrayBlockingQueue<>(QUEUE_CAPACITY),new ThreadPoolExecutor.AbortPolicy());}@Beanpublic ThreadPoolExecutor kiraExecutor2() {return new TtlThreadPoolExecutor(CORE_POOL_SIZE,MAX_POOL_SIZE,KEEP_ALIVE_TIME,TimeUnit.SECONDS,new ArrayBlockingQueue<>(QUEUE_CAPACITY),new ThreadPoolExecutor.AbortPolicy());}@Beanpublic ThreadPoolExecutor kiraExecutor3() {return new TtlThreadPoolExecutor(CORE_POOL_SIZE,MAX_POOL_SIZE,KEEP_ALIVE_TIME,TimeUnit.SECONDS,new ArrayBlockingQueue<>(QUEUE_CAPACITY),new ThreadPoolExecutor.AbortPolicy());}}

Controller接口测试

package com.kira.scaffoldmvc.Trace;import com.kira.scaffoldmvc.POJO.Result;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.concurrent.*;@RestController
@RequestMapping("/trace")
@Slf4j
public class TestTraceController {@ResourceThreadPoolExecutor kiraExecutor1;@GetMapping("/test")Result test(){log.info("测试MDC1");kiraExecutor1.execute(()->{log.info("测试MDC2");});return Result.success(true );}}

测试结果

http://www.lryc.cn/news/595979.html

相关文章:

  • 【从0-1的JavaScript】第2篇:JS对象的创建、使用已经内置对象
  • 操作系统 —— A / 概述
  • API网关原理与使用场景详解
  • Android AppCompat:实现Material Design向后兼容的终极指南
  • Apache Ignite扫描查询
  • 快手视觉算法面试30问全景精解
  • 2025 年非关系型数据库全面指南:类型、优势
  • Apache Ignite缓存基本操作
  • [Dify] -进阶10- Dify 的用户输入结构:变量、参数、文件上传全解析
  • 如何撤销Git提交误操作
  • 【音视频协议篇】RTMP协议
  • haproxy的负载均衡集群搭建
  • 构建智能视频中枢--多路RTSP转RTMP推送模块在轨道交通与工业应用中的技术方案探究
  • 最新AI与Python在地球科学多源数据交叉融合中的前沿技术应用
  • linux用户态各定时器抖动测试
  • 「Linux命令基础」用户组管理
  • MongoDB频繁掉线频繁断开服务的核心原因以及解决方案-卓伊凡|贝贝|莉莉|糖果
  • stream流入门
  • 企业知识库软件选型与实践指南
  • LINUX 722 逻辑卷快照
  • useState
  • 3.4 安全-分布式-数据库-挖掘
  • Java并发编程:JUC核心组件全解析
  • IMU(LSM6DSMTR+LIS2MDLTR)
  • 隧道代理与普通代理:一场网络隐身术的“智能革命”
  • 开发者的AI认知指南:用大模型重新理解人工智能(上)
  • 基于AutoJawSegment项目的CBCT图像分割实践指南
  • Qt开发环境搭建全攻略(Windows+Linux+macOS)
  • Navicat 远程连接SQLlite数据库
  • 【NLP舆情分析】基于python微博舆情分析可视化系统(flask+pandas+echarts) 视频教程 - 主页-微博基本信息实现