<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                # :-: 分布式日志鏈路跟蹤 ## 一、背景 開發排查系統問題用得最多的手段就是查看系統日志,在分布式環境中一般使用`ELK`來統一收集日志,但是在并發大時使用日志定位問題還是比較麻煩,由于大量的其他用戶/其他線程的日志也一起輸出穿行其中導致很難篩選出指定請求的全部相關日志,以及下游線程/服務對應的日志。 ## 二、解決思路 * 每個請求都使用一個`唯一標識`來追蹤全部的鏈路顯示在日志中,并且不修改原有的打印方式(代碼無入侵) * 使用Logback的`MDC`機制日志模板中加入`traceId`標識,取值方式為`%X{traceId}` >[danger] MDC(Mapped Diagnostic Context,映射調試上下文)是 log4j 和 logback 提供的一種方便在多線程條件下記錄日志的功能。MDC 可以看成是一個與當前線程綁定的Map,可以往其中添加鍵值對。MDC 中包含的內容可以被同一線程中執行的代碼所訪問。當前線程的子線程會繼承其父線程中的 MDC 的內容。當需要記錄日志時,只需要從 MDC 中獲取所需的信息即可。MDC 的內容則由程序在適當的時候保存進去。對于一個 Web 應用來說,通常是在請求被處理的最開始保存這些數據。 ## 三、方案實現 由于`MDC`內部使用的是`ThreadLocal`所以只有本線程才有效,子線程和下游的服務`MDC`里的值會丟失;所以方案主要的難點是解決**值的傳遞問題**,主要包括以幾下部分: * API網關中的`MDC`數據如何傳遞給下游服務 * 服務如何接收數據,并且調用其他遠程服務時如何繼續傳遞 * 異步的情況下(線程池)如何傳給子線程 ### 3.1. 修改日志模板 logback配置文件日志格式添加該標識 ![](https://img.kancloud.cn/8d/36/8d36a4680774b13ed85e9ff0d5638698_1625x176.png) ~~~ <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] %-5level %logger{50} - %msg%n</pattern> <charset>UTF-8</charset> <!-- 設置字符集 --> </encoder> ~~~ ### 3.2. 網關添加過濾器 生成`traceId`并通過`header`傳遞給下游服務 ![](https://img.kancloud.cn/44/f7/44f7dca46837bcf8dd5fe1b6ede7bc92_1440x949.jpg) 上面代碼有個MDC是屬于org.slf4j.MDC中的,下面就是常量的值: ~~~text /** * 日志鏈路追蹤id信息頭 */ String TRACE_ID_HEADER = "x-traceId-header"; /** * 日志鏈路追蹤id日志標志 */ String LOG_TRACE_ID = "traceId"; ~~~ ### 3.3. 下游服務增加spring攔截器 接收并保存`traceId`的值**攔截器** **注冊攔截器** ![](https://img.kancloud.cn/df/b2/dfb20e803043f32deabc058aea857b45_1440x848.jpg) ### 3.4. 下游服務增加feign攔截器 繼續把當前服務的`traceId`值傳遞給下游服務 ![](https://img.kancloud.cn/12/6e/126eb85d5789d303efd4c4343e362d14_1440x613.jpg) ### 3.5. 解決父子線程傳遞問題 主要針對業務會使用線程池(異步、并行處理),并且`spring`自己也有`@Async`注解來使用線程池,要解決這個問題需要以下兩個步驟 #### 3.5.1. 重寫logback的`LogbackMDCAdapter` 由于logback的`MDC`實現內部使用的是`ThreadLocal`不能傳遞子線程,所以需要重寫替換為阿里的`TransmittableThreadLocal` >[info] **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<Map<String, String>> copyOnInheritThreadLocal = new TransmittableThreadLocal<>(); private static TtlMDCAdapter mtcMDCAdapter; static { mtcMDCAdapter = new TtlMDCAdapter(); MDC.mdcAdapter = mtcMDCAdapter; } public static MDCAdapter getInstance() { return mtcMDCAdapter; } ~~~ >[success] 其他代碼與**ch.qos.logback.classic.util.LogbackMDCAdapter**一樣,只需改為調用`copyOnInheritThreadLocal`變量 **TtlMDCAdapterInitializer類**用于程序啟動時加載自己的mdcAdapter實現 ~~~ public class TtlMDCAdapterInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { @Override public void initialize(ConfigurableApplicationContext applicationContext) { //加載TtlMDCAdapter實例 TtlMDCAdapter.getInstance(); } } ~~~ #### 3.5.2. 擴展線程池實現 增加`TtlRunnable`和`TtlCallable`擴展 ~~~ public class CustomThreadPoolTaskExecutor extends ThreadPoolTaskExecutor { @Override public void execute(Runnable runnable) { Runnable ttlRunnable = TtlRunnable.get(runnable); super.execute(ttlRunnable); } @Override public <T> Future<T> submit(Callable<T> 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 <T> ListenableFuture<T> submitListenable(Callable<T> task) { Callable ttlCallable = TtlCallable.get(task); return super.submitListenable(ttlCallable); } } ~~~ ## 四、場景測試 ### 4.1. 測試代碼如下 ![](https://img.kancloud.cn/20/07/2007c9dbd1a3e49d5320918bcfc667d2_1338x640.png) ### 4.2. api網關打印的日志 網關生成`traceId`值為`13d9800c8c7944c78a06ce28c36de670` ![](https://img.kancloud.cn/0a/5e/0a5e7b47e2fabf8ca3904263413e7e86_1440x52.jpg) ### 4.4. ELK聚合日志通過`traceId`查詢整條鏈路日志 當系統出現異常時,可直接通過該異常日志的`traceId`?的值,在日志中心中詢該請求的所有日志信息 ![](https://img.kancloud.cn/7a/3e/7a3ef25ffeead83a307b4e043e7d4b0c_2158x823.png)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看