中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

日志排查問題困難?分布式日志鏈路跟蹤來幫你

發布時間:2020-07-31 16:50:52 來源:網絡 閱讀:234 作者:wx5c62e4b4e4ab9 欄目:編程語言

一、背景

開發排查系統問題用得最多的手段就是查看系統日志,在分布式環境中一般使用ELK來統一收集日志,但是在并發大時使用日志定位問題還是比較麻煩,由于大量的其他用戶/其他線程的日志也一起輸出穿行其中導致很難篩選出指定請求的全部相關日志,以及下游線程/服務對應的日志。

?

二、解決思路

  • 每個請求都使用一個唯一標識來追蹤全部的鏈路顯示在日志中,并且不修改原有的打印方式(代碼無***)
  • 使用Logback的MDC機制日志模板中加入traceId標識,取值方式為%X{traceId}

    MDC(Mapped Diagnostic Context,映射調試上下文)是 log4j 和 logback 提供的一種方便在多線程條件下記錄日志的功能。MDC 可以看成是一個與當前線程綁定的Map,可以往其中添加鍵值對。MDC 中包含的內容可以被同一線程中執行的代碼所訪問。當前線程的子線程會繼承其父線程中的 MDC 的內容。當需要記錄日志時,只需要從 MDC 中獲取所需的信息即可。MDC 的內容則由程序在適當的時候保存進去。對于一個 Web 應用來說,通常是在請求被處理的最開始保存這些數據。

?

三、方案實現

由于MDC內部使用的是ThreadLocal所以只有本線程才有效,子線程和下游的服務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注解來使用線程池,所以需要擴展ThreadPoolTaskExecutor線程池實現將父線程的MDC內容復制給子線程

public class CustomThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {
    /**
     * 把父線程的MDC內容賦值給子線程
     * @param runnable
     */
    @Override
    public void execute(Runnable runnable) {
        Map<String, String> mdcContext = MDC.getCopyOfContextMap();
        super.execute(() -> run(runnable, mdcContext));
    }

    @Override
    public <T> Future<T> submit(Callable<T> task) {
        Map<String, String> mdcContext = MDC.getCopyOfContextMap();
        return super.submit(() -> call(task, mdcContext));
    }

    /**
     * 子線程委托的執行方法
     * @param runnable {@link Runnable}
     * @param mdcContext 父線程MDC內容
     */
    private void run(Runnable runnable, String tenantId, Map<String, String> mdcContext) {
        // 將父線程的MDC內容傳給子線程
        if (mdcContext != null) {
            MDC.setContextMap(mdcContext);
        }
        try {
            // 執行異步操作
            runnable.run();
        } finally {
            // 清空MDC內容
            MDC.clear();
        }
    }

    /**
     * 子線程委托的執行方法
     * @param task {@link Callable}
     * @param mdcContext 父線程MDC內容
     */
    private <T> T call(Callable<T> task, Map<String, String> mdcContext) throws Exception {
        // 將父線程的MDC內容傳給子線程
        if (mdcContext != null) {
            MDC.setContextMap(mdcContext);
        }
        try {
            // 執行異步操作
            return task.call();
        } finally {
            // 清空MDC內容
            MDC.clear();
        }
    }
}

?

四、場景測試

4.1. 測試代碼如下

日志排查問題困難?分布式日志鏈路跟蹤來幫你
?

4.2. api網關打印的日志

網關生成traceId值為13d9800c8c7944c78a06ce28c36de670
日志排查問題困難?分布式日志鏈路跟蹤來幫你
?

4.3. 請求跳轉到文件服務時打印的日志

顯示的traceId與網關相同,這里特意模擬發生異常的場景
日志排查問題困難?分布式日志鏈路跟蹤來幫你
?

4.4. ELK聚合日志通過traceId查詢整條鏈路日志

當系統出現異常時,可直接通過該異常日志的traceId?的值,在日志中心中詢該請求的所有日志信息
日志排查問題困難?分布式日志鏈路跟蹤來幫你

?

五、源碼地址

附上我的開源微服務框架(包含本文中的代碼),歡迎 star 關注

https://gitee.com/zlt2000/microservices-platform

?
推薦閱讀

  • Spring Cloud開發人員如何解決服務沖突和實例亂竄?
  • zuul集成Sentinel最新的網關流控組件
  • 阿里注冊中心Nacos生產部署方案
  • Spring Boot自定義配置項在IDE里面實現自動提示
  • Spring Cloud Zuul的動態路由怎樣做?集成Nacos實現很簡單

?
日志排查問題困難?分布式日志鏈路跟蹤來幫你

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

仁怀市| 兖州市| 高淳县| 电白县| 陆丰市| 西峡县| 开化县| 佛教| 怀集县| 渑池县| 阜康市| 江北区| 抚远县| 喜德县| 军事| 怀来县| 余干县| 富顺县| 台北县| 凌源市| 东阿县| 合山市| 马龙县| 日喀则市| 泊头市| 临汾市| 湘西| 台江县| 乌鲁木齐市| 兴安盟| 峨眉山市| 陇川县| 连州市| 镇江市| 寿阳县| 昂仁县| 衡东县| 苏尼特右旗| 牙克石市| 民和| 定西市|