ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

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

2022-08-22 18:04:09  阅读:186  来源: 互联网

标签:初体验 java springboot MDC 线程 org import public clr


  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,MDC,线程,org,import,public,clr
来源: https://www.cnblogs.com/zwjvzwj/p/16613085.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有