摘要:為了追蹤一個請求完整的流轉(zhuǎn)過程,我可以給請求分配一個唯一的,當(dāng)請求調(diào)用其他服務(wù)時,我們傳遞這個。這是一個簡單的實現(xiàn)分布式調(diào)用追蹤的實踐,以上。
背景
分布式環(huán)境下,跨服務(wù)之間的調(diào)用錯綜復(fù)雜,如果突然爆出一個錯誤,雖然有日志記錄,但到底是哪個服務(wù)出了問題呢?是移動端傳的參數(shù)有錯誤,還是系統(tǒng)X或者系統(tǒng)Y提供的接口導(dǎo)致?在這種情況下,錯誤排查起來就非常費勁。
為了追蹤一個請求完整的流轉(zhuǎn)過程,我可以給請求分配一個唯一的traceId,當(dāng)請求調(diào)用其他服務(wù)時,我們傳遞這個traceId。在輸出日志時,將這個traceId打印到日志文件中,這樣,從日志文件中,根據(jù)traceId就可以分析一個請求完整的調(diào)用過程,若更進(jìn)一步,還可以做性能分析。
TraceID在Http服務(wù)中的實現(xiàn)在一個服務(wù)的內(nèi)部,我們不希望在調(diào)用每個方法時,都帶上traceId這個參數(shù)(這樣實在太蠢了- . -)。
在Java中,我們一般將traceId放到ThreadLocal中,這樣在打印日志時,日志框架從ThreadLocal取出traceId,和其他需要打印的信息一起打印出來。這樣對框架的使用者來說,traceId就是透明的,并不需要去關(guān)注它。
我們來看代碼實現(xiàn):
/** * 建立日志MDC上下文屬性的攔截器 */ public class WebLogMdcHandlerInterceptor extends HandlerInterceptorAdapter { /** * traceId一般由前端的負(fù)載生成,比如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字段,如果沒有,則通過隨機(jī)數(shù)生成 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(); } // 通過隨機(jī)數(shù)生成traceId,也可以通過其他方式實現(xiàn),只要保證唯一即可 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; } }
實現(xiàn)其實比較簡單,使用MDC(Mapped Diagnostic Contexts)來實現(xiàn),logback和log4j支持MDC,MDC的底層實現(xiàn)其實很容易理解,就是通過ThreadLocal來維護(hù)key-value,源碼如下:
public final class LogbackMDCAdapter implements MDCAdapter { final InheritableThreadLocal
WebLogMdcHandlerInterceptor繼承了HandlerInterceptorAdapter,HandlerInterceptorAdapter是一個攔截器適配器,我們實現(xiàn)了它其中的2個方法:
preHandle: 實現(xiàn)處理器的預(yù)處理
afterCompletion: 整個請求處理完畢回調(diào)方法,可以進(jìn)行一些資源清理
我們在afterCompletion方法中對MDC進(jìn)行了clear操作,底層調(diào)用了ThreadLocal的remove方法,清除當(dāng)前線程中的線程局部變量。其作用有兩個,一是防止ThreadLocal導(dǎo)致的內(nèi)存溢出,二是Tomcat容器線程復(fù)用時,新請求會依舊使用原來的MDC中的traceId,會導(dǎo)致traceId的"串碼"現(xiàn)象。
我們再來講一下preHandle方法中的ctxOpId,即我們向MDC中不僅僅寫入http header中的traceId,還通過UUID生成了一個ctxOpId。
如上圖,A服務(wù)的某個方法連續(xù)調(diào)用了B服務(wù)的某個接口3次(可能是重試機(jī)制導(dǎo)致,也有可能確實是業(yè)務(wù)邏輯),如何區(qū)分這3次調(diào)用呢?只通過traceId無法區(qū)分,因為這三次的traceId都相同,所以每次調(diào)用時UUID生成ctxOpId,來區(qū)分這三次調(diào)用。
然后在logback.xml文件中配置pattern,如下:
%d %-5level [%X{ctxTraceId}][%thread] %logger{5} - %msg%n
具體打印日志時,會根據(jù)pattern格式打印,各字段的含義可自行百度。
最后,當(dāng)我們在調(diào)用其他Http服務(wù)時,先獲取當(dāng)前線程的ThreadLocal上下文,將traceId寫入http client的header中,從而達(dá)到跨服務(wù)傳遞traceId。
這是一個簡單的實現(xiàn)分布式調(diào)用追蹤的實踐,以上。
原文鏈接https://segmentfault.com/a/11...
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/70501.html
摘要:微服務(wù)中調(diào)用棧的獲取,使用的開發(fā)者會很自然想到用來攔截,但是攔截同一個類的多個方法之間的調(diào)用很不方便,有侵入性,因此并不適合。調(diào)用棧的跟蹤也提供了這個能力,可以獲得當(dāng)前方法的調(diào)用棧信息。 一、調(diào)用鏈跟蹤的作用 調(diào)用鏈跟蹤包括 1.前端到后端的調(diào)用鏈 2.單個服務(wù)內(nèi)部方法之間的調(diào)用鏈 3.微服務(wù)之間的調(diào)用鏈 4.應(yīng)用服務(wù)和數(shù)據(jù)庫之間的調(diào)用鏈 5.應(yīng)用服務(wù)和第三方服務(wù)中...
摘要:除了以上級別的成員變量共享,在調(diào)用鏈跟蹤時要能識別不同分層下的多個類實例的調(diào)用是同一個請求,而這個請求的調(diào)用都在一個獨立線程內(nèi)完成,此時就要用到線程級變量共享。 一、Java類成員作用域 JAVA類成員作用域參考下圖: showImg(https://segmentfault.com/img/bVbvWlh?w=1695&h=925); Java虛擬機(jī)級作用域,通過在類成員變量前加...
摘要:但能拷貝圖粘貼后不失真通常是收費富文本編輯器才具備的能力。是否支持編程語言高亮,例如按,語言高亮是否支持?jǐn)?shù)學(xué)公式等等因此選擇了兩款富文本編輯器,支持截屏粘貼,當(dāng)做跟蹤系統(tǒng)時這個功能特別有用。 一、Web應(yīng)用技術(shù)棧 在開發(fā)Web應(yīng)用時,通常會使用到以下技術(shù)棧: showImg(https://segmentfault.com/img/bVbwceG);對應(yīng)這些技術(shù)棧都已有相應(yīng)的開源產(chǎn)品...
摘要:默認(rèn)情況下,當(dāng)數(shù)據(jù)元到達(dá)時,分段接收器將按當(dāng)前系統(tǒng)時間拆分,并使用日期時間模式命名存儲區(qū)。如果需要,可以使用數(shù)據(jù)元或元組的屬性來確定目錄。這將調(diào)用傳入的數(shù)據(jù)元并將它們寫入部分文件,由換行符分隔。消費者的消費者被稱為或等。 1 概覽 1.1 預(yù)定義的源和接收器 Flink內(nèi)置了一些基本數(shù)據(jù)源和接收器,并且始終可用。該預(yù)定義的數(shù)據(jù)源包括文件,目錄和插socket,并從集合和迭代器攝取數(shù)據(jù)...
閱讀 697·2021-09-22 10:02
閱讀 6660·2021-09-03 10:49
閱讀 624·2021-09-02 09:47
閱讀 2213·2019-08-30 15:53
閱讀 2982·2019-08-30 15:44
閱讀 981·2019-08-30 13:20
閱讀 1873·2019-08-29 16:32
閱讀 942·2019-08-29 12:46