Java 實現(xiàn)分布式服務(wù)的調(diào)用鏈跟蹤
為什么要實現(xiàn)調(diào)用鏈跟蹤?
隨著業(yè)務(wù)的發(fā)展,所有的系統(tǒng)最終都會走向服務(wù)化體系,微服務(wù)的目的一是提高系統(tǒng)的穩(wěn)定性,二是提高持續(xù)交付的效率,為什么能提高這兩項不是今天討論的內(nèi)容。
當然這也不是絕對的,如果業(yè)務(wù)還在MVP驗證,團隊規(guī)模小個人覺得完全沒必要微服務(wù)化、單體應(yīng)用是比較好的選擇。作者是有經(jīng)歷過從單體應(yīng)用到1000+應(yīng)用的增長經(jīng)歷,也是見證了公司從初創(chuàng)到上市的過程,對于系統(tǒng)階段和業(yè)務(wù)階段的匹配還是有比較深的感受的。
服務(wù)拆分后帶來的問題是什么呢?服務(wù)的依賴關(guān)系復(fù)雜后,對于問題的排查也增加了復(fù)雜度,當然站在更高的角度來看拆分帶來的不只是排錯復(fù)雜性的提升,工程效率、組織協(xié)作也都會帶來新的挑戰(zhàn)。
回到主題,如何快速查詢整個請求鏈路上的日志并呈現(xiàn)出來是解決排查問題復(fù)雜度的根本方法,這就是今天我們要講的內(nèi)容,如何自己來實現(xiàn)一個全鏈路跟蹤。
如何實現(xiàn)?
第一步,看圖、看場景,用戶瀏覽器的一次請求行為所走的路徑是什么樣的
如上圖、省略了4層和7層的LB,請求直接到gateway->A->B 那如何把個request關(guān)聯(lián)起來呢?從時序上來看我們只要在gateway生成一個traceId然后層層透傳,那么每一次的request的我們就能通過traceid關(guān)聯(lián)查詢出來了。
如何透傳、如何記錄呢?或者說如何透傳、如何記錄讓各應(yīng)用的開發(fā)人員無需關(guān)注呢?
第二步,實現(xiàn)。不想看代碼可直接拉最后看結(jié)果和原理
如何傳遞,這里我們使用定義統(tǒng)一的Request類,所有的api層需要使用這個規(guī)范,代碼如下:
public class Request<T> implements Serializable { //header:攜帶需要傳遞的信息 private RequestHeader header; //業(yè)務(wù)參數(shù) private T bizModel; //...省略get set } public class RequestHeader implements Serializable { //調(diào)用鏈唯一ID private String traceId; //當前用戶Id private String userId; //上游調(diào)用方appId private String callAppId; //...省略get set }
有了這個Request之后,我們在網(wǎng)關(guān)層每次都生成traceId, 然后在各服務(wù)之間傳遞就能做到調(diào)用鏈的關(guān)聯(lián)了。我們繼續(xù)看個各應(yīng)用應(yīng)該如何定義服務(wù)和使用
@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()); //輸出當前應(yīng)用的header信息 System.out.println("上游的traceId:"+RequestContext.getHeader().getTraceId()); System.out.println("上游的callAppId:"+RequestContext.getHeader().getCallAppId()); System.out.println("上游的userId:"+RequestContext.getHeader().getUserId()); /*** * 模擬調(diào)用其他應(yīng)用服務(wù) * 通過RPCRequest 來構(gòu)建request對象 */ Request<OtherAppServiceReqDTO> otherAppServiceReqDTORequest =RPCRequest.createRequest(new OtherAppServiceReqDTO()); //輸出下游應(yīng)用的header信息 System.out.println("調(diào)用下游的traceId:"+otherAppServiceReqDTORequest.getHeader().getTraceId()); System.out.println("調(diào)用下游的callAppId:"+otherAppServiceReqDTORequest.getHeader().getCallAppId()); System.out.println("調(diào)用下游的userId:"+otherAppServiceReqDTORequest.getHeader().getUserId()); return Response.successResponse(exampleRespDTO); }
看完上面代碼的同學,應(yīng)該看到了有一個模擬調(diào)用其他服務(wù)的地方,這里主要解決的是服務(wù)和服務(wù)之間的調(diào)用header傳遞的問題,這里封裝了一個createRequest的方法,其主要內(nèi)容還是把當前應(yīng)用的requestHeader 賦值給請求其他服務(wù)的request上。這也是一個測試接口,最后面有測試的結(jié)果
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該發(fā)揮作用的時候了,直接看代碼
public class ApiHandler { public ApiHandler() { } public Response handleApiMethod(ProceedingJoinPoint pjp, ApiMethod apiMethod) { //獲取上游調(diào)用方的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 { //構(gòu)建response header ResponseHeader responseHeader = new ResponseHeader(); responseHeader.setTraceId(RequestContext.getHeader().getTraceId()); //執(zhí)行service方法 response = (Response) pjp.proceed(args); response.setHeader(responseHeader); } catch (Throwable throwable) { throwable.printStackTrace(); }finally { //清除ThreadLocal中當前請求的header 對象 RequestContext.clear(); } return response; } }
不想看代碼的,直接看下圖,原理比較簡單,淺黃色為AOP作用,接口執(zhí)行前和執(zhí)行后,其中reqeuest和header的定義在第1段代碼
這里沒有介紹如何收集數(shù)據(jù)和查詢展示,比較簡單的辦法是使用logback打本地日志,然后通過agent抽到集中式日志進行查詢展示,例如ELK。
測試一下結(jié)果:
1、接口文檔
2、執(zhí)行結(jié)果
以上就是Java 實現(xiàn)分布式服務(wù)的調(diào)用鏈跟蹤的詳細內(nèi)容,更多關(guān)于Java 分布式服務(wù)的調(diào)用鏈跟蹤的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java詳細分析講解自動裝箱自動拆箱與Integer緩存的使用
裝箱就是把基本類型轉(zhuǎn)換成包裝類,拆箱就是把包裝類轉(zhuǎn)換成基本類型,下面這篇文章主要給大家介紹Java中自動裝箱、自動拆箱與Integer緩存,需要的朋友可以參考下2022-04-04使用Java進行FreeMarker的web模板開發(fā)的基礎(chǔ)教程
這篇文章主要介紹了使用Java進行FreeMarker模板引擎開發(fā)的基礎(chǔ)教程,文中針對FreeMarker的網(wǎng)頁標簽用法給出了一些例子,需要的朋友可以參考下2016-03-03gateway與spring-boot-starter-web沖突問題的解決
這篇文章主要介紹了gateway與spring-boot-starter-web沖突問題的解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07mybatis關(guān)聯(lián)關(guān)系映射的實現(xiàn)
MyBatis的關(guān)聯(lián)關(guān)系映射在復(fù)雜數(shù)據(jù)模型中至關(guān)重要,使開發(fā)人員能夠以最靈活的方式滿足不同項目的需求,本文就來介紹一下mybatis關(guān)聯(lián)關(guān)系映射的實現(xiàn),感興趣的可以了解一下2023-09-09