從 1.5 開始搭建一個微服務框架——日誌追蹤 traceId

> 原文首發:微信公衆號,悟空聊架構,https://mp.weixin.qq.com/s/SDxH9k96aP5-X12yFtus0w

你好,我是悟空。

前言

最近在搭一個基礎版的項目框架,基於 SpringCloud 微服務框架。

如果把 SpringCloud 這個框架當做 1,那麼現在已經有的基礎組件比如 swagger/logback 等等就是 0.5 ,然後我在這 1.5 基礎上進行組裝,完成一個微服務項目框架。

爲什麼要造二代輪子呢?市面上現成的項目框架不香嗎?

因爲項目組不允許用外部的現成框架,比如 Ruoyi。另外因爲我們的項目需求具有自身的特色,技術選型也會選擇我們自己熟悉的框架,所以自己來造二代輪子也是一個不錯的選擇。

核心功能

需要包含以下核心功能:

 

  • 多個微服務模塊拆分,抽取出一個 demo 微服務模塊供擴展,已完成

  • 提取核心框架模塊,已完成

  • 註冊中心 Eureka,已完成

  • 遠程調用 OpenFeign,已完成

  • 日誌 logback,包含 traceId 跟蹤,已完成

  • Swagger API 文檔,已完成

  • 配置文件共享,已完成

  • 日誌檢索,ELK Stack,已完成

  • 自定義 Starter,待定

  • 整合緩存 Redis,Redis 哨兵高可用,已完成

  • 整合數據庫 MySQL,MySQL 高可用,已完成

  • 整合 MyBatis-Plus,已完成

  • 鏈路追蹤組件,待定

  • 監控,待定

  • 工具類,待開發

  • 網關,技術選型待定

  • 審計日誌進入 ES,待定

  • 分佈式文件系統,待定

  • 定時任務,待定

  • 等等 

本篇要介紹的內容是關於日誌鏈路追蹤的。

一、痛點

痛點一:進程內的多條日誌無法追蹤

一個請求調用,假設會調用後端十幾個方法,打印十幾次日誌,無法將這些日誌串聯起來。 

如下圖所示:客戶端調用訂單服務,訂單服務中方法 A 調用方法 B,方法 B 調用方法 C。

方法 A 打印第一條日誌和第五條日誌,方法 B 打印第二條日誌,方法 C 打印第三條日誌和第四條日誌,但是這 5 條日誌並沒有任何聯繫,唯一的聯繫就是時間是按照時間循序打印的,但是如果有其他併發的請求調用,則會干擾日誌的連續性。

 

痛點二:跨服務的日誌如何進行關聯

每個微服務都會記錄自己這個進程的日誌,跨進程的日誌如何進行關聯?

如下圖所示:訂單服務和優惠券服務屬於兩個微服務,部署在兩臺機器上,訂單服務的 A 方法遠程調用優惠券服務的 D 方法。 

方法 A 將日誌打印到日誌文件 1 中,記錄了 5 條日誌,方法 D 將日誌打印到日誌文件 2 中,記錄了 5 條日誌。但是這 10 條日誌是無法關聯起來的。

 

痛點三:跨線程的日誌如何關聯

主線程和子線程的日誌如何關聯?

 

如下圖所示:主線程的方法 A 啓動了一個子線程,子線程執行方法 E。

 

方法 A 打印了第一條日誌,子線程 E 打印了第二條日誌和第三條日誌。

 

痛點四:第三方調用我們的服務,如何追蹤?

本篇要解決的核心問題是第一個和第二個問題,多線程目前還未引入,目前也沒有第三方來調用,後期再來優化第三個和第四個問題。

二、方案

1.1 解決方案

① 使用 Skywalking traceId 進行鏈路追蹤,或者 sleuth + zipkin 方案。

② 使用 Elastic APM 的 traceId 進行鏈路追蹤

③ MDC 方案:自己生成 traceId 並 put 到 MDC 裏面。

項目初期,先不引入過多的中間件,用簡單可行的方案先嚐試,所以這裏用第三種方案 MDC。

1.2 MDC 方案

MDC(Mapped Diagnostic Context)用於存儲運行上下文的特定線程的上下文數據。因此,如果使用 log4j 進行日誌記錄,則每個線程都可以擁有自己的MDC,該 MDC 對整個線程是全局的。屬於該線程的任何代碼都可以輕鬆訪問線程的 MDC 中存在的值。

三、原理和實戰

2.1 追蹤一個請求的多條日誌

我們先來看第一個痛點,如何在一個請求中,將多條日誌串聯起來。

 

該方案的原理如下圖所示:

 

 

(1)在 logback 日誌配置文件中的日誌格式中添加 %X{traceId} 配置。

 

<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %X{traceId} %-5level %logger - %msg%n</pattern>

(2)自定一個攔截器,從請求的 header 中獲取 traceId ,如果存在則放到 MDC 中,否則直接用 UUID 當做 traceId,然後放到 MDC 中。

(3)配置攔截器。

 

當我們打印日誌的時候,會自動打印 traceId,如下所示,多條日誌的 traceId 相同。

 

示例代碼

攔截器代碼:

 

/**
 * @author www.passjava.cn,公衆號:悟空聊架構
 * @date 2022-07-05 
 */
@Service
public class LogInterceptor extends HandlerInterceptorAdapter {

    private static final String TRACE_ID = "traceId";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String traceId = request.getHeader(TRACE_ID);
        if (StringUtils.isEmpty(traceId)) {
            MDC.put("traceId", UUID.randomUUID().toString());
        } else {
            MDC.put(TRACE_ID, traceId);
        }

        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        //防止內存泄露
        MDC.remove("traceId");
    }
}

 

配置攔截器:

 

/**
 * @author www.passjava.cn,公衆號:悟空聊架構
 * @date 2022-07-05 
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Resource
    private LogInterceptor logInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(logInterceptor).addPathPatterns("/**");
    }
}

2.2 跨服務跟蹤多條日誌

解決方案的原理圖如下所示:

 

 

訂單服務遠程調用優惠券服務,需要在訂單服務中添加 OpenFeign 的攔截器,攔截器裏面做的事就是往 請求的 header 中添加 traceId,這樣調用到優惠券服務時,就能從 header 中拿到這次請求的 traceId。 

代碼如下所示:

 

/**
 * @author www.passjava.cn,公衆號:悟空聊架構
 * @date 2022-07-05 
 */
@Configuration
public class FeignInterceptor implements RequestInterceptor {
    private static final String TRACE_ID = "traceId";

    @Override
    public void apply(RequestTemplate requestTemplate) {
        requestTemplate.header(TRACE_ID, (String) MDC.get(TRACE_ID));
    }
}

 

兩個微服務打印的日誌中,兩條日誌的 traceId 一致。

 

當然這些日誌都會導入到 Elasticsearch 中的,然後通過 kibana 可視化界面搜索 traceId,就可以將整個調用鏈路串起來了!

四、總結

本篇通過攔截器、MDC 功能,全鏈路加入了 traceId,然後將 traceId 輸出到日誌中,就可以通過日誌來追蹤調用鏈路。不論是進程內的方法級調用,還是跨進程間的服務調用,都可以進行追蹤。 

另外日誌還需要通過 ELK Stack 技術將日誌導入到 Elasticsearch 中,然後就可以通過檢索 traceId,將整個調用鏈路檢索出來了。

- END -

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章