java springboot 初体验 (七)对接链路追踪

  1. 上一篇
    1. java springboot 初体验 (六)添加统一的入参出参打印日志(使用切面)
    2. https://www.cnblogs.com/zwjvzwj/p/16612094.html
  2. MDC介绍
    1. MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j 、logback及log4j2 提供的一种方便在多线程条件下记录日志的功能。MDC 可以看成是一个与当前线程绑定的哈希表,可以往其中添加键值对。MDC 中包含的内容可以被同一线程中执行的代码所访问。当前线程的子线程会继承其父线程中的 MDC 的内容。当需要记录日志时,只需要从 MDC 中获取所需的信息即可。
      • clear() => 移除所有MDC
      • get (String key) => 获取当前线程MDC中指定key的值
      • getContext() => 获取当前线程MDC的MDC
      • put(String key, Object o) => 往当前线程的MDC中存入指定的键值对
      • remove(String key) => 删除当前线程MDC中指定的键值对
  3. 添加拦截器
    1.   拦截所有的请求,并在请求中添加traceId
    2. package com.zwj.zwjproject.interceptor;
      
      import com.ctrip.framework.apollo.core.utils.StringUtils;
      import com.sun.istack.internal.NotNull;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.web.servlet.HandlerInterceptor;
      import org.slf4j.MDC;
      import org.springframework.web.servlet.ModelAndView;
      
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      import java.util.UUID;
      
      /**
       * @ClassName: LogInterceptor
       * @Author zhangwujie
       * @Date 2022/8/22 3:13 下午
       * @Description: 日志拦截。统一在日志中添加链路追踪id
       */
      @Slf4j
      public class TraceInterceptor implements HandlerInterceptor {
      
          // 链路中统一的id
          public static final String TRACE_ID = "requestId";
          // 链路中每个服务的id
          public static final String SPAN_ID = "spanId";
      
          public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) {
              try {
                  //如果有上层调用就用上层的ID
                  String traceId = request.getHeader(TRACE_ID);
                  if (StringUtils.isEmpty(traceId)) {
                      MDC.put(TRACE_ID, UUID.randomUUID().toString());
                  } else {
                      MDC.put(TRACE_ID, traceId);
                  }
                  MDC.put(SPAN_ID, UUID.randomUUID().toString());
              } catch (Exception e) {
                  log.error("LogInterceptor preHandle catch error msg={}", e.getMessage(), e);
              }
      
              return true;
          }
      
          public void postHandle(@NotNull HttpServletRequest httpServletRequest, @NotNull HttpServletResponse httpServletResponse, @NotNull Object o, ModelAndView modelAndView) {
          }
      
          public void afterCompletion(@NotNull HttpServletRequest httpServletRequest, @NotNull HttpServletResponse httpServletResponse, @NotNull Object o, Exception e) {
              //调用结束后删除
              MDC.clear();
          }
      }

       

    3.  
  4.    注册拦截器
    1. package com.zwj.zwjproject.configuration;
      
      import com.sun.istack.internal.NotNull;
      import com.zwj.zwjproject.interceptor.TraceInterceptor;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
      import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
      
      /**
       * @ClassName: TraceIdInterceptorConfiguration
       * @Author zhangwujie
       * @Date 2022/8/22 3:38 下午
       * @Description: TraceID的拦截器\生成請求的唯一值
       */
      @Configuration
      public class TraceIdInterceptorConfiguration implements WebMvcConfigurer {
      
          /**
           * 添加拦截器链路的请求路径
           */
          private String[] addPathPatterns = new String[]{"/**"};
      
          /**
           * 不添加拦截器链路的请求路径
           */
          private String[] excludePathPatterns = new String[]{};
      
          @Override
          public void addInterceptors(@NotNull InterceptorRegistry interceptorRegistry) {
              // 链路追踪日志拦截器
              TraceInterceptor traceInterceptor = new TraceInterceptor();
              interceptorRegistry
                      .addInterceptor(traceInterceptor)
                      .addPathPatterns(addPathPatterns)
                      .excludePathPatterns(excludePathPatterns);
          }
      
      }
  5.   添加日志格式配置

    1.   application.yml添加配置日志(与apollo添加配置的效果是一样的,二者配置一个就行)

      logging:
          pattern:
              console: "%date{yyyy-MM-dd HH:mm:ss.SSS} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }) %highlight([requestId: %X{requestId}]) %highlight([spanId: %X{spanId}]) {magenta}%clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}"

       

    2. apollo添加配置(与application.yml添加配置的效果是一样的,二者配置一个就行)

      1. logging.pattern.console = %date{yyyy-MM-dd HH:mm:ss.SSS} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }) %highlight([requestId: %X{requestId}]) %highlight([spanId: %X{spanId}]) {magenta}%clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}

         

  6. 添加一个任务适配器,用于主线程和子线程的traceId传递
    1. package com.zwj.zwjproject.decorator;
      
      import org.slf4j.MDC;
      import org.springframework.core.task.TaskDecorator;
      
      import java.util.Map;
      
      /**
       * @ClassName: MdcTaskDecorator
       * @Author zhangwujie
       * @Date 2022/8/22 4:55 下午
       * @Description: 任务适配器
       */
      public class MdcTaskDecorator implements TaskDecorator {
      
          /**
           * @apiNote 使异步线程池获得主线程的上下文
           * @param runnable runnable
           * @return Runnable
           */
          @Override
          public Runnable decorate(Runnable runnable) {
              /**
               * 为了线程池中的线程在复用的时候也能获得父线程的MDC中的信息,
               * 子线程第一次初始化的时候没事,因为通过InheritableThreadLocal
               * 已经可以获得MDC中的内容了
               */
              Map<String, String> map = MDC.getCopyOfContextMap();
              return () -> {
                  try {
                      // 线程重用的时候,把父线程中的context map内容带入当前线程的context map中,
                      // 因为线程已经初始化过了,不会像初始化时那样通过拷贝父线程inheritableThreadLocals到子线程
                      // 的inheritableThreadLocals操作来完成线程间context map的传递。
                      // 真正执行到这个run方法的时候,已经到了子线程中了,所以要在初始化的时候用
                      // MDC.getCopyOfContextMap()来获得父线程contest map,那时候还在父线程域中
                      MDC.setContextMap(map);
                      runnable.run();
                  } finally {
                      MDC.clear();
                  }
              };
          }
      }
  7.   配置线程池,用于链路追踪MDC的线程池\在多线程情况下会将主线程的上下文传递给子线程

    1. package com.zwj.zwjproject.configuration;
      
      import com.zwj.zwjproject.decorator.MdcTaskDecorator;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.scheduling.annotation.EnableAsync;
      import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
      
      import java.util.concurrent.ThreadPoolExecutor;
      
      /**
       * @ClassName: MdcThreadPoolConfiguration
       * @Author zhangwujie
       * @Date 2022/8/22 4:52 下午
       * @Description: 用于链路追踪MDC的线程池\在多线程情况下会将主线程的上下文传递给子线程
       */
      @EnableAsync
      @Configuration
      public class MdcThreadPoolConfiguration {
          // 核心线程池数
          private final int corePoolSize = 50;
          // 最大线程池数
          private final int maxPoolSize = 200;
          // 任务队列的容量
          private final int queueCapacity = 1000;
          // 非核心线程的存活时间
          private final int keepAliveSeconds = 300;
      
          @Bean(name = "threadPoolTaskExecutor")
          public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
              ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
              // 核心线程池数
              executor.setMaxPoolSize(maxPoolSize);
              // 最大线程池数
              executor.setCorePoolSize(corePoolSize);
              // 任务队列的容量
              executor.setQueueCapacity(queueCapacity);
              // 非核心线程的存活时间
              executor.setKeepAliveSeconds(keepAliveSeconds);
              // 传递主线程的信息到子线程
              executor.setTaskDecorator(new MdcTaskDecorator());
              // 线程池对拒绝任务(无线程可用)的处理策略
              // - AbortPolicy
              //   用于被拒绝任务的处理程序,它将抛出RejectedExecutionException。- CallerRunsPolicy
              //   用于被拒绝任务的处理程序,它直接在execute方法的调用线程中运行被拒绝的任务。- DiscardOldestPolicy
              //   用于被拒绝任务的处理程序,它放弃最旧的未处理请求,然后重试execute。- DiscardPolicy
              //   用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务。
              executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
              return executor;
          }
      }
  8.   启动服务

  9. 调用接口

  10.   下一篇

    1.   java springboot 初体验 (八)对接链路追踪

    2.  

       https://www.cnblogs.com/zwjvzwj/p/16614128.html

       

       

       

       

       
posted @ 2022-08-22 17:49  zwjvzwj  阅读(409)  评论(0编辑  收藏  举报