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