摘要:當前線程的子線程會繼承其父線程中的的內容。若希望在線程池與主線程間傳遞,需配合和使用。
一、背景
開發排查系統問題用得最多的手段就是查看系統日志,在分布式環境中一般使用ELK來統一收集日志,但是在并發大時使用日志定位問題還是比較麻煩,由于大量的其他用戶/其他線程的日志也一起輸出穿行其中導致很難篩選出指定請求的全部相關日志,以及下游線程/服務對應的日志。
?
二、解決思路每個請求都使用一個唯一標識來追蹤全部的鏈路顯示在日志中,并且不修改原有的打印方式(代碼無入侵)
使用Logback的MDC機制日志模板中加入traceId標識,取值方式為%X{traceId}
MDC(Mapped Diagnostic Context,映射調試上下文)是 log4j 和 logback 提供的一種方便在多線程條件下記錄日志的功能。MDC 可以看成是一個與當前線程綁定的Map,可以往其中添加鍵值對。MDC 中包含的內容可以被同一線程中執行的代碼所訪問。當前線程的子線程會繼承其父線程中的 MDC 的內容。當需要記錄日志時,只需要從 MDC 中獲取所需的信息即可。MDC 的內容則由程序在適當的時候保存進去。對于一個 Web 應用來說,通常是在請求被處理的最開始保存這些數據。
?
三、方案實現由于MDC內部使用的是ThreadLocal所以只有本線程才有效,子線程和下游的服務MDC里的值會丟失;所以方案主要的難點是解決值的傳遞問題,主要包括以幾下部分:
API網關中的MDC數據如何傳遞給下游服務
服務如何接收數據,并且調用其他遠程服務時如何繼續傳遞
異步的情況下(線程池)如何傳給子線程
3.1. 修改日志模板logback配置文件模板格式添加標識%X{traceId}
?
3.2. 網關添加過濾器生成traceId并通過header傳遞給下游服務
@Component public class TraceFilter extends ZuulFilter { @Autowired private TraceProperties traceProperties; @Override public String filterType() { return FilterConstants.PRE_TYPE; } @Override public int filterOrder() { return FORM_BODY_WRAPPER_FILTER_ORDER - 1; } @Override public boolean shouldFilter() { //根據配置控制是否開啟過濾器 return traceProperties.getEnable(); } @Override public Object run() { //鏈路追蹤id String traceId = IdUtil.fastSimpleUUID(); MDC.put(CommonConstant.LOG_TRACE_ID, traceId); RequestContext ctx = RequestContext.getCurrentContext(); ctx.addZuulRequestHeader(CommonConstant.TRACE_ID_HEADER, traceId); return null; } }
?
3.3. 下游服務增加spring攔截器接收并保存traceId的值
攔截器
public class TraceInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String traceId = request.getHeader(CommonConstant.TRACE_ID_HEADER); if (StrUtil.isNotEmpty(traceId)) { MDC.put(CommonConstant.LOG_TRACE_ID, traceId); } return true; } }
注冊攔截器
public class DefaultWebMvcConfig extends WebMvcConfigurationSupport { @Override protected void addInterceptors(InterceptorRegistry registry) { //日志鏈路追蹤攔截器 registry.addInterceptor(new TraceInterceptor()).addPathPatterns("/**"); super.addInterceptors(registry); } }
?
3.4. 下游服務增加feign攔截器繼續把當前服務的traceId值傳遞給下游服務
public class FeignInterceptorConfig { @Bean public RequestInterceptor requestInterceptor() { RequestInterceptor requestInterceptor = template -> { //傳遞日志traceId String traceId = MDC.get(CommonConstant.LOG_TRACE_ID); if (StrUtil.isNotEmpty(traceId)) { template.header(CommonConstant.TRACE_ID_HEADER, traceId); } }; return requestInterceptor; } }
?
3.5. 解決父子線程傳遞問題主要針對業務會使用線程池(異步、并行處理),并且spring自己也有@Async注解來使用線程池,要解決這個問題需要以下兩個步驟
3.5.1. 重寫logback的LogbackMDCAdapter由于logback的MDC實現內部使用的是ThreadLocal不能傳遞子線程,所以需要重寫替換為阿里的TransmittableThreadLocal
TransmittableThreadLocal 是Alibaba開源的、用于解決 “在使用線程池等會緩存線程的組件情況下傳遞ThreadLocal” 問題的 InheritableThreadLocal 擴展。若希望 TransmittableThreadLocal 在線程池與主線程間傳遞,需配合 TtlRunnable 和 TtlCallable 使用。
TtlMDCAdapter類
package org.slf4j; import com.alibaba.ttl.TransmittableThreadLocal; import org.slf4j.spi.MDCAdapter; public class TtlMDCAdapter implements MDCAdapter { /** * 此處是關鍵 */ private final ThreadLocal
其他代碼與ch.qos.logback.classic.util.LogbackMDCAdapter一樣,只需改為調用copyOnInheritThreadLocal變量
?
TtlMDCAdapterInitializer類用于程序啟動時加載自己的mdcAdapter實現
public class TtlMDCAdapterInitializer implements ApplicationContextInitializer{ @Override public void initialize(ConfigurableApplicationContext applicationContext) { //加載TtlMDCAdapter實例 TtlMDCAdapter.getInstance(); } }
?
3.5.2. 擴展線程池實現增加TtlRunnable和TtlCallable擴展實現TTL
public class CustomThreadPoolTaskExecutor extends ThreadPoolTaskExecutor { @Override public void execute(Runnable runnable) { Runnable ttlRunnable = TtlRunnable.get(runnable); super.execute(ttlRunnable); } @Override publicFuture submit(Callable task) { Callable ttlCallable = TtlCallable.get(task); return super.submit(ttlCallable); } @Override public Future> submit(Runnable task) { Runnable ttlRunnable = TtlRunnable.get(task); return super.submit(ttlRunnable); } @Override public ListenableFuture> submitListenable(Runnable task) { Runnable ttlRunnable = TtlRunnable.get(task); return super.submitListenable(ttlRunnable); } @Override public ListenableFuture submitListenable(Callable task) { Callable ttlCallable = TtlCallable.get(task); return super.submitListenable(ttlCallable); } }
?
四、場景測試 4.1. 測試代碼如下
?
網關生成traceId值為13d9800c8c7944c78a06ce28c36de670
?
顯示的traceId與網關相同,這里特意模擬發生異常的場景
?
當系統出現異常時,可直接通過該異常日志的traceId?的值,在日志中心中詢該請求的所有日志信息
?
五、源碼下載附上我的開源微服務框架(包含本文中的代碼),歡迎 star 關注
https://gitee.com/zlt2000/mic...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/76187.html
摘要:今年的無論是常態全鏈路壓測或者是雙十一當天,面臨的主要問題是如何保障自身系統在海量數據沖擊下的穩定性,以及如何更快的展現各個系統的狀態及更好的幫助開發同學發現及定位問題。在整個雙十一備戰過程中,遇到并解決了很多疑難雜癥。 摘要: EagleEye作為阿里集團老牌的鏈路跟蹤系統,其自身業務雖不在交易鏈路上,但卻監控著全集團的鏈路狀態,特別是在中間件的遠程調用上,覆蓋了集團絕大部分的場景,...
摘要:接下來我們以余額寶為例,重點剖析天弘基金在日志數據分析領域是如何突破的此前,天弘基金一直使用開源的日志方案,研發和運維人員通過對日志數據進行處理,使用日志文件進行查詢檢索。 雙十一剛剛結束,其實最緊張的不是商鋪理貨,也不是網友緊盯大促商品準備秒殺,而是網購幕后的運維人員,他們最擔心:什么網絡中斷、應用卡頓、響應速度慢,服務器宕機……雙十一作為電商 IT 部門的頭等大事,大促前,運維人員就需要...
摘要:在軟件世界里,觀察意味著設置斷點添加調試語句監視程序值以及檢查內存在醫學領域,需要測試血樣和進行光透視。福爾摩斯,最后一案如果你不修復,它不會自動消失。修復解決問題的能力,是軟件工程師的核心競爭力之一。 這篇文章是《調試九法:軟硬件錯誤的排查之道》的閱讀筆記。這本書的主旨,是介紹如何修復bug:找出bug發生的原因、并給出修復方案。 調試bug的九個規則列舉如下,建議將這個清單打印出來...
摘要:阿里云上領域各個產品最終目標是為了對以上各個組件進行有效監控。阿里云的解決方案地圖基于今天的云上的應用架構,阿里云的解決方案地圖如下所示。其他阿里云服務包括緩存,等。阿里云解決方案地圖以下表格對阿里云解決方案進行總結。 摘要: PM是近5年來伴隨著云技術、微服務架構發展起來的一個新興監控領域。在國內外,無論是云廠商(如AWS, Azure,等)還是獨立的公司(Dynatrace, Ap...
閱讀 2781·2023-04-25 14:41
閱讀 2375·2021-11-23 09:51
閱讀 3674·2021-11-17 17:08
閱讀 1667·2021-10-18 13:31
閱讀 5528·2021-09-22 15:27
閱讀 910·2019-08-30 15:54
閱讀 2222·2019-08-30 13:16
閱讀 728·2019-08-29 17:04