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