• 微服务TraceId设计(SpringCloud OpenFeign)


    一、背景

         在微服务框架下,需要通过traceId来定位到整个链路中每个细节的运行情况,这里不引用外部组件,自己来进行实现。当然还保证在多线程环境下的一致性。

    二、需求

    1)接口入口侧如果有traceId,则获取;如果没有则生成;

    2)定义生成traceId的规则;

    3)traceId可以默认打印到log中,不需要自己显示定义;

    4)通过feign传输到后端服务中

    三、实现

    3.1)TraceId生成算法

        参考:https://help.aliyun.com/document_detail/151840.html 

    1. public static String genTraceId() {
    2. try {
    3. StringBuilder sb = new StringBuilder();
    4. InetAddress addr = InetAddress.getLocalHost();
    5. if (null == addr) {
    6. return TRACE_ID_VALUE_DEFAULT;
    7. }
    8. String addrStr = addr.getHostAddress();
    9. if (null == addrStr) {
    10. return TRACE_ID_VALUE_DEFAULT;
    11. }
    12. String[] addrArr = addrStr.split("\\.");
    13. if (addrArr.length != 4) {
    14. return TRACE_ID_VALUE_DEFAULT;
    15. }
    16. sb.append(StringUtils.leftPad(Integer.toHexString(Integer.valueOf(addrArr[0])) , 2, "0"))
    17. .append(StringUtils.leftPad(Integer.toHexString(Integer.valueOf(addrArr[1])) , 2, "0"))
    18. .append(StringUtils.leftPad(Integer.toHexString(Integer.valueOf(addrArr[2])) , 2, "0"))
    19. .append(StringUtils.leftPad(Integer.toHexString(Integer.valueOf(addrArr[3])) , 2, "0"))
    20. .append(System.currentTimeMillis())
    21. .append(RandomUtil.randomNumbers(4))
    22. .append(ProcessHandle.current().pid());
    23. return sb.toString();
    24. } catch (Exception e) {
    25. return TRACE_ID_VALUE_DEFAULT;
    26. }
    27. }

    3.2)服务入口日志拦截

    1. @EnableAspectJAutoProxy
    2. @Component
    3. @Aspect
    4. @Slf4j
    5. public class InterfaceLogAspect {
    6. @Around("execution(* com.springboot.k8s.demo.interfaces.*.*(..))")
    7. public Object interfaceAround(ProceedingJoinPoint joinPoint) {
    8. String className = joinPoint.getTarget().getClass().getSimpleName();
    9. Object[] args = joinPoint.getArgs();
    10. MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    11. Parameter[] argNames = signature.getMethod().getParameters();
    12. StringBuilder sb = new StringBuilder(className + "." + joinPoint.getSignature().getName() + " -- ");
    13. //获取RequestAttributes
    14. RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
    15. //从获取RequestAttributes中获取HttpServletRequest的信息
    16. HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
    17. TraceIdUtil.initTraceId(request);
    18. StopWatch clock = new StopWatch();
    19. clock.start();
    20. Object retVal = null;
    21. try {
    22. Map paramMap = new HashMap<>();
    23. for (int i = 0; i < argNames.length; i++) {
    24. paramMap.put(argNames[i].getName(), args[i]);
    25. }
    26. LoggerUtils.auditLogInfo(LogLabelEnum.REQUEST_IN.getLogLabel(), paramMap);
    27. retVal = joinPoint.proceed(joinPoint.getArgs());
    28. } catch (Throwable e) {
    29. //..
    30. } finally {
    31. //..
    32. }
    33. return retVal;
    34. }
    35. }

    3.3)FeignClient服务之间传递TraceId:

    1. @Service
    2. public class MyRequestInterceptor implements RequestInterceptor {
    3. @Override
    4. public void apply(RequestTemplate template) {
    5. template.header(TraceIdUtil.TRACE_ID_KEY, TraceIdUtil.getTraceId());
    6. }
    7. }

    3.4)logback.xml

    1. <appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
    2. <layout class="ch.qos.logback.classic.PatternLayout">
    3. <pattern>[%-5level][%date{yyyy-MM-dd HH:mm:ss:SSS}][%logger:%line] --%mdc{client} %msg||traceId=%X{Trace-Id}%npattern>
    4. layout>
    5. appender>

    备注:"%X{Trace-Id}"即将MDC中的以Trace-Id为key的值同步到日志中,也就是TraceIdUtil.TRACE_ID_KEY。

    完整的TraceIdUtill为:

    1. public class TraceIdUtil {
    2. public static final String TRACE_ID_KEY = "Trace-Id";
    3. public static final String TRACE_ID_VALUE_DEFAULT = "000000";
    4. public static void setTraceId(String traceId) {
    5. //如果参数为空,则设置默认traceId
    6. //将traceId放到MDC中
    7. MDC.put(TRACE_ID_KEY, traceId);
    8. }
    9. public static String getTraceId() {
    10. String traceId = MDC.get(TRACE_ID_KEY);
    11. return StringUtils.isNotBlank(traceId) ? traceId : TRACE_ID_VALUE_DEFAULT;
    12. }
    13. /**
    14. * generate traceId: https://help.aliyun.com/document_detail/151840.html
    15. */
    16. public static String genTraceId() {
    17. try {
    18. StringBuilder sb = new StringBuilder();
    19. InetAddress addr = InetAddress.getLocalHost();
    20. if (null == addr) {
    21. return TRACE_ID_VALUE_DEFAULT;
    22. }
    23. String addrStr = addr.getHostAddress();
    24. if (null == addrStr) {
    25. return TRACE_ID_VALUE_DEFAULT;
    26. }
    27. String[] addrArr = addrStr.split("\\.");
    28. if (addrArr.length != 4) {
    29. return TRACE_ID_VALUE_DEFAULT;
    30. }
    31. sb.append(StringUtils.leftPad(Integer.toHexString(Integer.valueOf(addrArr[0])) , 2, "0"))
    32. .append(StringUtils.leftPad(Integer.toHexString(Integer.valueOf(addrArr[1])) , 2, "0"))
    33. .append(StringUtils.leftPad(Integer.toHexString(Integer.valueOf(addrArr[2])) , 2, "0"))
    34. .append(StringUtils.leftPad(Integer.toHexString(Integer.valueOf(addrArr[3])) , 2, "0"))
    35. .append(System.currentTimeMillis())
    36. .append(RandomUtil.randomNumbers(4))
    37. .append(ProcessHandle.current().pid());
    38. return sb.toString();
    39. } catch (Exception e) {
    40. return TRACE_ID_VALUE_DEFAULT;
    41. }
    42. }
    43. public static void initTraceId(HttpServletRequest request) {
    44. String traceId = "";
    45. if (request != null) {
    46. //get traceId from request
    47. traceId = request.getHeader(TraceIdUtil.TRACE_ID_KEY);
    48. }
    49. //generate new traceId
    50. if (StringUtils.isBlank(traceId) || TRACE_ID_VALUE_DEFAULT.equals(traceId)){
    51. traceId = TraceIdUtil.genTraceId();
    52. }
    53. TraceIdUtil.setTraceId(traceId);
    54. }
    55. public static void main(String[] args) {
    56. System.out.println(TraceIdUtil.genTraceId());
    57. }
    58. }

    四、多线程TraceId复制

    4.1)线程配置

    1. @Configuration
    2. public class ThreadPoolConfig {
    3. public static final String ASYNC_EXECUTOR_NAME = "threadPoolTaskExecutor";
    4. @Bean(name=ASYNC_EXECUTOR_NAME)
    5. public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
    6. ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    7. // for passing in request scope context
    8. executor.setTaskDecorator(new ContextCopyingDecorator());
    9. executor.setCorePoolSize(10);
    10. executor.setMaxPoolSize(20);
    11. executor.setQueueCapacity(100);
    12. executor.setWaitForTasksToCompleteOnShutdown(true);
    13. executor.setThreadNamePrefix("SeelAsyncThread-");
    14. executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
    15. executor.initialize();
    16. return executor;
    17. }
    18. }

    4.2)定义TaskDecorator

    1. public class ContextCopyingDecorator implements TaskDecorator {
    2. @Override
    3. public Runnable decorate(Runnable runnable) {
    4. RequestAttributes context = RequestContextHolder.currentRequestAttributes();
    5. Map contextMap = MDC.getCopyOfContextMap();
    6. return () -> {
    7. try {
    8. RequestContextHolder.setRequestAttributes(context);
    9. MDC.setContextMap(contextMap);
    10. runnable.run();
    11. } finally {
    12. RequestContextHolder.resetRequestAttributes();
    13. }
    14. };
    15. }
    16. }

    Author:忆之独秀

    Email:leaguenew@qq.com

    转载注明出处:

  • 相关阅读:
    数据结构详细笔记——线性表
    为mysql添加TCMalloc库,以提升性能!
    SRE 排障利器,接口请求超时试试 httpstat
    Day51:动态规划 LeedCode 300.最长递增子序列 674. 最长连续递增序列 718. 最长重复子数组
    【Pytorch】张量的维度/轴/dim的理解
    专业135总400+合工大合肥工业大学833信号分析与处理信息通信上岸经验分享
    主从Reactor模式 任务池提高请求处理效率分析
    服务器的cpu如何通过脚本让其使用率变高
    PyQt5_股票K线形态查看工具
    前端架构师之01_ES6_基础
  • 原文地址:https://blog.csdn.net/lavorange/article/details/126087077