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

悟空聊架構發表於2022-07-05

> 原文首發:微信公眾號,悟空聊架構,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 條日誌並沒有任何聯絡,唯一的聯絡就是時間是按照時間循序列印的,但是如果有其他併發的請求呼叫,則會干擾日誌的連續性。

 

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

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

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

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

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

 

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

痛點三:跨執行緒的日誌如何關聯

主執行緒和子執行緒的日誌如何關聯?

 

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

 

方法 A 列印了第一條日誌,子執行緒 E 列印了第二條日誌和第三條日誌。

 

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

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

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

二、方案

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.5 開始搭建一個微服務框架——日誌追蹤 traceId

 

(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 相同。

 

從 1.5 開始搭建一個微服務框架——日誌追蹤 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 跨服務跟蹤多條日誌

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

 

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

 

訂單服務遠端呼叫優惠券服務,需要在訂單服務中新增 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 一致。

 

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

當然這些日誌都會匯入到 Elasticsearch 中的,然後通過 kibana 視覺化介面搜尋 traceId,就可以將整個呼叫鏈路串起來了!

四、總結

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

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

- END -

相關文章