分佈式調用跟蹤實戰

背景

分佈式環境下,跨服務之間的調用錯綜複雜,若是忽然爆出一個錯誤,雖然有日誌記錄,但究竟是哪一個服務出了問題呢?是移動端傳的參數有錯誤,仍是系統X或者系統Y提供的接口致使?在這種狀況下,錯誤排查起來就很是費勁。前端

爲了追蹤一個請求完整的流轉過程,我能夠給請求分配一個惟一的traceId,當請求調用其餘服務時,咱們傳遞這個traceId。在輸出日誌時,將這個traceId打印到日誌文件中,這樣,從日誌文件中,根據traceId就能夠分析一個請求完整的調用過程,若更進一步,還能夠作性能分析。segmentfault

TraceID在Http服務中的實現

在一個服務的內部,咱們不但願在調用每一個方法時,都帶上traceId這個參數(這樣實在太蠢了- . -)。app

在Java中,咱們通常將traceId放到ThreadLocal中,這樣在打印日誌時,日誌框架從ThreadLocal取出traceId,和其餘須要打印的信息一塊兒打印出來。這樣對框架的使用者來講,traceId就是透明的,並不須要去關注它。框架

咱們來看代碼實現:dom

/**
 * 創建日誌MDC上下文屬性的攔截器
 */
public class WebLogMdcHandlerInterceptor extends HandlerInterceptorAdapter {

    /**
     * traceId通常由前端的負載生成,好比Nignx
     */
    private boolean generateTraceId = false;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String ctxTraceId = null;
        String ctxOpId = null;

        // 判斷Http header中是否有traceId字段,若是沒有,則經過隨機數生成
        if (StringUtils.isNotBlank(request.getHeader(Conventions.TRACE_ID_HEADER))) {
            ctxTraceId = request.getHeader(Conventions.TRACE_ID_HEADER);
        } else if (generateTraceId) {
            ctxTraceId = getTraceId();
        }

        ctxOpId = UUID.randomUUID().toString();
        MDC.put(Conventions.CTX_TRACE_ID_MDC, ctxTraceId + "," + ctxOpId);

        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        MDC.clear();
    }

    // 經過隨機數生成traceId,也能夠經過其餘方式實現,只要保證惟一便可
    private static String getTraceId() {
        Random random = new Random();
        String rs1 = String.valueOf(random.nextInt(10000));
        String rs2 = String.valueOf(random.nextInt(10000));
        return rs1 + rs2;
    }

    public void setGenerateTraceId(boolean generateTraceId) {
        this.generateTraceId = generateTraceId;
    }
}

實現其實比較簡單,使用MDC(Mapped Diagnostic Contexts)來實現,logbacklog4j支持MDCMDC的底層實現其實很容易理解,就是經過ThreadLocal來維護key-value,源碼以下:分佈式

public final class LogbackMDCAdapter implements MDCAdapter {

    final InheritableThreadLocal<Map<String, String>> copyOnInheritThreadLocal = new InheritableThreadLocal<Map<String, String>>();
    ...
    ...
}

WebLogMdcHandlerInterceptor繼承了HandlerInterceptorAdapterHandlerInterceptorAdapter是一個攔截器適配器,咱們實現了它其中的2個方法:ide

  • preHandle: 實現處理器的預處理
  • afterCompletion: 整個請求處理完畢回調方法,能夠進行一些資源清理

咱們在afterCompletion方法中對MDC進行了clear操做,底層調用了ThreadLocalremove方法,清除當前線程中的線程局部變量。其做用有兩個,一是防止ThreadLocal致使的內存溢出,二是Tomcat容器線程複用時,新請求會依舊使用原來的MDC中的traceId,會致使traceId的"串碼"現象。性能

咱們再來說一下preHandle方法中的ctxOpId,即咱們向MDC中不單單寫入http header中的traceId,還經過UUID生成了一個ctxOpIdthis

alt text

如上圖,A服務的某個方法連續調用了B服務的某個接口3次(多是重試機制致使,也有可能確實是業務邏輯),如何區分這3次調用呢?只經過traceId沒法區分,由於這三次的traceId都相同,因此每次調用時UUID生成ctxOpId,來區分這三次調用。spa

而後在logback.xml文件中配置pattern,以下:

<pattern>%d %-5level [%X{ctxTraceId}][%thread] %logger{5} - %msg%n</pattern>

具體打印日誌時,會根據pattern格式打印,各字段的含義可自行百度。

最後,當咱們在調用其餘Http服務時,先獲取當前線程的ThreadLocal上下文,將traceId寫入http clientheader中,從而達到跨服務傳遞traceId

這是一個簡單的實現分佈式調用追蹤的實踐,以上。

原文連接

https://segmentfault.com/a/11...

相關文章
相關標籤/搜索