從 1.5 開始搭建一個微服務架構——紀錄檔追蹤 traceId

2022-07-06 06:00:41

> 原文首發:微信公眾號,悟空聊架構,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 -