使用requestId在分布式系统追踪请求

0.234字数 553阅读 7481

背景

现在大多数企业开发的系统都是分布式系统了,随着系统的复杂如何有效地追踪定位线上问题也变得更加困难。我们可以使用requestId(traceId)来解决这一问题。

实现思路

  1. 使用过滤器或切面在每个HTTP请求进到Controller前生成一个唯一标识请求的requestId,可以使用UUID,放在ThreadLocal里,方便上下文调用。
    public static final String REQUEST_ID_KEY = "requestId";
    public static ThreadLocal<String> requestIdThreadLocal = new ThreadLocal<String>();
 
    private static final Logger logger = LoggerFactory.getLogger(RequestIdUtil.class);
 
    public static String getRequestId(HttpServletRequest request) {
        String requestId = null;
        String parameterRequestId = request.getParameter(REQUEST_ID_KEY);
        String headerRequestId = request.getHeader(REQUEST_ID_KEY);
 
        if (parameterRequestId == null &amp;&amp; headerRequestId == null) {
            logger.info("request parameter 和header 都没有requestId入参");
            requestId = UUID.randomUUID().toString();
        } else {
            requestId = parameterRequestId != null ? parameterRequestId : headerRequestId;
        }
 
        requestIdThreadLocal.set(requestId);
 
        return requestId;
    }
  1. requestId结合日志打印,这里使用slf4j标准里的MDC实现,例子使用logback打印日志。
    代码使用:
        MDC.put("requestId", requestId);

logback配置示例:

<configuration> 
     <appender name="logfile" class="ch.qos.logback.core.rolling.RollingFileAppender">
         <Encoding>UTF-8</Encoding>
         <File>${log_base}/java-base-web.log</File>
         <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
             <FileNamePattern>${log_base}/java-base-web-%d{yyyy-MM-dd}-%i.log</FileNamePattern>
             <MaxHistory>10</MaxHistory>
             <TimeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <MaxFileSize>200MB</MaxFileSize>
             </TimeBasedFileNamingAndTriggeringPolicy> 
         </rollingPolicy>
         <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%d^|^%X{requestId}^|^%-5level^|^%logger{36}%M^|^%msg%n</pattern>
         </layout>
     </appender>
     <root level="info"> 
        <appender-ref ref="logfile" /> 
     </root> 
</configuration>

主要是在pattern那里指定,使用MDC里面的变量用的是%X{requestId},requestId为放进MDC里面的key,

达到的效果就是打印出来的日志自动带上了requestId。效果如下:

[a0b3589f-fe87-4d3a-9151-8925afd0a6c6]  at com.test.demo.TestMain2.test2(TestMain2.java:26)  

相当于切日志,这样我们线上就可以使用requestId在ELK上搜索属于这个请求的日志了。

  1. 使用HTTP Client或者OKHTTP等在后台请求其他系统的服务的时候把放在上下文的requestId放在请求参数或者头里,推荐放在header,上面的切面一开始会判断requestId是否在请求里面,有的话就继续沿用调用方的requestId,这样子在被调用的系统也能追踪到属于同一个调用链的日志了。
    String requestId = RequestIdUtil.requestIdThreadLocal.get();
    headerMap.put(RequestIdUtil.REQUEST_ID_KEY, requestId);
    Map<String, String> paramMap = new HashMap<String, String>();
    String resultString = JsonHttpClientUtil.post(testHttpClientUrl, headerMap, paramMap, "UTF-8");
    logger.info(resultString);
  1. requestId可以放到一个封装响应类里面,这样子我们就可以在控制台里面看到响应参数里面的requestId,方便在ELK里面查找日志了。也可以结合异常体系使用,抛的异常打印的日志自然也带上了requestId,同时可以使用钉钉机器人来通知开发区定位问题,同时带上requestId和异常堆栈信息就行了。这样就能方便的知道异常发生的上下文日志了。

总结

上面总结了一些工作中使用起来比较便利的定位问题的方法体系。希望可以给读者带来一些启示。

推荐阅读更多精彩内容