Java 實現分佈式服務的調用鏈跟蹤

為什麼要實現調用鏈跟蹤?

隨著業務的發展,所有的系統最終都會走向服務化體系,微服務的目的一是提高系統的穩定性,二是提高持續交付的效率,為什麼能提高這兩項不是今天討論的內容。

當然這也不是絕對的,如果業務還在MVP驗證,團隊規模小個人覺得完全沒必要微服務化、單體應用是比較好的選擇。作者是有經歷過從單體應用到1000+應用的增長經歷,也是見證瞭公司從初創到上市的過程,對於系統階段和業務階段的匹配還是有比較深的感受的。

服務拆分後帶來的問題是什麼呢?服務的依賴關系復雜後,對於問題的排查也增加瞭復雜度,當然站在更高的角度來看拆分帶來的不隻是排錯復雜性的提升,工程效率、組織協作也都會帶來新的挑戰。

回到主題,如何快速查詢整個請求鏈路上的日志並呈現出來是解決排查問題復雜度的根本方法,這就是今天我們要講的內容,如何自己來實現一個全鏈路跟蹤。

如何實現?

第一步,看圖、看場景,用戶瀏覽器的一次請求行為所走的路徑是什麼樣的

如上圖、省略瞭4層和7層的LB,請求直接到gateway->A->B 那如何把個request關聯起來呢?從時序上來看我們隻要在gateway生成一個traceId然後層層透傳,那麼每一次的request的我們就能通過traceid關聯查詢出來瞭。
如何透傳、如何記錄呢?或者說如何透傳、如何記錄讓各應用的開發人員無需關註呢?

第二步,實現。不想看代碼可直接拉最後看結果和原理

如何傳遞,這裡我們使用定義統一的Request類,所有的api層需要使用這個規范,代碼如下:

public class Request<T> implements Serializable {
    //header:攜帶需要傳遞的信息
    private RequestHeader header;
    //業務參數
    private T bizModel;
    //...省略get set
}
public class RequestHeader implements Serializable {

    //調用鏈唯一ID
    private String traceId;
    //當前用戶Id
    private String userId;
    //上遊調用方appId
    private String callAppId;
    //...省略get set
}

有瞭這個Request之後,我們在網關層每次都生成traceId, 然後在各服務之間傳遞就能做到調用鏈的關聯瞭。我們繼續看個各應用應該如何定義服務和使用

    @ApiMethod
    @PostMapping("/test")
    @ApiOperation(value = "test", notes = "", response = String.class)
    public Response<ExampleRespDTO> test(@RequestBody Request<ExampleReqDTO> req) {
        ExampleRespDTO exampleRespDTO = new ExampleRespDTO();
        exampleRespDTO.setName(req.getBizModel().getName());

        //輸出當前應用的header信息
         System.out.println("上遊的traceId:"+RequestContext.getHeader().getTraceId());
        System.out.println("上遊的callAppId:"+RequestContext.getHeader().getCallAppId());
        System.out.println("上遊的userId:"+RequestContext.getHeader().getUserId());


        /***
         * 模擬調用其他應用服務
         * 通過RPCRequest 來構建request對象
         */
        Request<OtherAppServiceReqDTO>  otherAppServiceReqDTORequest =RPCRequest.createRequest(new OtherAppServiceReqDTO());

        //輸出下遊應用的header信息
        System.out.println("調用下遊的traceId:"+otherAppServiceReqDTORequest.getHeader().getTraceId());
        System.out.println("調用下遊的callAppId:"+otherAppServiceReqDTORequest.getHeader().getCallAppId());
        System.out.println("調用下遊的userId:"+otherAppServiceReqDTORequest.getHeader().getUserId());

        return Response.successResponse(exampleRespDTO);
    }

看完上面代碼的同學,應該看到瞭有一個模擬調用其他服務的地方,這裡主要解決的是服務和服務之間的調用header傳遞的問題,這裡封裝瞭一個createRequest的方法,其主要內容還是把當前應用的requestHeader 賦值給請求其他服務的request上。這也是一個測試接口,最後面有測試的結果

public class RPCRequest {
    public static <T> Request<T> createRequest(T requestData){
        Request<T> request = new Request();
        RequestHeader requestHeader=new RequestHeader();
        requestHeader.setTraceId(RequestContext.getHeader().getTraceId());
        requestHeader.setUserId(RequestContext.getHeader().getUserId());
        requestHeader.setCallAppId(AppConfig.CURRENT_APP_ID);
        request.setHeader(requestHeader);
        request.setBizModel(requestData);
        return request;
    }
}

當前request中的header存在什麼地方呢,我們看一下RequestContext的代碼

public class RequestContext {
  private static ThreadLocal<RequestHeader> threadLocal=new ThreadLocal<>();
   public static void setHeader(RequestHeader header){
       threadLocal.set(header);
   }
   public static RequestHeader getHeader(){
       return threadLocal.get();
   }
   public static void clear(){
       threadLocal.remove();
   }
}

header是什麼時候放進去的呢?這裡就是AOP該發揮作用的時候瞭,直接看代碼

public class ApiHandler {
    public ApiHandler() {
    }

    public Response handleApiMethod(ProceedingJoinPoint pjp, ApiMethod apiMethod) {
        //獲取上遊調用方的request header
        Object[] args = pjp.getArgs();
        Request request = (Request) args[0];
        RequestHeader header = request.getHeader();
        //將header加入到當前request 到ThreadLocal保存
        RequestContext.setHeader(header);
        Response response = null;
        try {
            //構建response header
            ResponseHeader responseHeader = new ResponseHeader();
            responseHeader.setTraceId(RequestContext.getHeader().getTraceId());
            //執行service方法
            response = (Response) pjp.proceed(args);
            response.setHeader(responseHeader);

        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }finally {
            //清除ThreadLocal中當前請求的header 對象
            RequestContext.clear();
        }
        return response;

    }
}

不想看代碼的,直接看下圖,原理比較簡單,淺黃色為AOP作用,接口執行前和執行後,其中reqeuest和header的定義在第1段代碼

這裡沒有介紹如何收集數據和查詢展示,比較簡單的辦法是使用logback打本地日志,然後通過agent抽到集中式日志進行查詢展示,例如ELK。

測試一下結果:

1、接口文檔

2、執行結果

以上就是Java 實現分佈式服務的調用鏈跟蹤的詳細內容,更多關於Java 分佈式服務的調用鏈跟蹤的資料請關註WalkonNet其它相關文章!