掃二維碼與項目經理溝通
我們在微信上24小時期待你的聲音
解答本文疑問/技術咨詢/運營咨詢/技術建議/互聯(lián)網(wǎng)交流
你好,我是悟空。

10多年的牟平網(wǎng)站建設經驗,針對設計、前端、開發(fā)、售后、文案、推廣等六對一服務,響應快,48小時及時工作處理。全網(wǎng)整合營銷推廣的優(yōu)勢是能夠根據(jù)用戶設備顯示端的尺寸不同,自動調整牟平建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調整網(wǎng)站的寬度,無論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設計,從而大程度地提升瀏覽體驗。成都創(chuàng)新互聯(lián)公司從事“牟平網(wǎng)站設計”,“牟平網(wǎng)站推廣”以來,每個客戶項目都認真落實執(zhí)行。
最近在搭一個基礎版的項目框架,基于 SpringCloud 微服務框架。
如果把 SpringCloud 這個框架當做 1,那么現(xiàn)在已經有的基礎組件比如 swagger/logback 等等就是 0.5 ,然后我在這 1.5 基礎上進行組裝,完成一個微服務項目框架。
為什么要造二代輪子呢?市面上現(xiàn)成的項目框架不香嗎?
因為項目組不允許用外部的現(xiàn)成框架,比如 Ruoyi。另外因為我們的項目需求具有自身的特色,技術選型也會選擇我們自己熟悉的框架,所以自己來造二代輪子也是一個不錯的選擇。
需要包含以下核心功能:
本篇要介紹的內容是關于日志鏈路追蹤的。
一個請求調用,假設會調用后端十幾個方法,打印十幾次日志,無法將這些日志串聯(lián)起來。
如下圖所示:客戶端調用訂單服務,訂單服務中方法 A 調用方法 B,方法 B 調用方法 C。
方法 A 打印第一條日志和第五條日志,方法 B 打印第二條日志,方法 C 打印第三條日志和第四條日志,但是這 5 條日志并沒有任何聯(lián)系,唯一的聯(lián)系就是時間是按照時間循序打印的,但是如果有其他并發(fā)的請求調用,則會干擾日志的連續(xù)性。
每個微服務都會記錄自己這個進程的日志,跨進程的日志如何進行關聯(lián)?
如下圖所示:訂單服務和優(yōu)惠券服務屬于兩個微服務,部署在兩臺機器上,訂單服務的 A 方法遠程調用優(yōu)惠券服務的 D 方法。
方法 A 將日志打印到日志文件 1 中,記錄了 5 條日志,方法 D 將日志打印到日志文件 2 中,記錄了 5 條日志。但是這 10 條日志是無法關聯(lián)起來的。
主線程和子線程的日志如何關聯(lián)?
如下圖所示:主線程的方法 A 啟動了一個子線程,子線程執(zhí)行方法 E。
方法 A 打印了第一條日志,子線程 E 打印了第二條日志和第三條日志。
本篇要解決的核心問題是第一個和第二個問題,多線程目前還未引入,目前也沒有第三方來調用,后期再來優(yōu)化第三個和第四個問題。
① 使用 Skywalking traceId 進行鏈路追蹤
② 使用 Elastic APM 的 traceId 進行鏈路追蹤
③ MDC 方案:自己生成 traceId 并 put 到 MDC 里面。
項目初期,先不引入過多的中間件,用簡單可行的方案先嘗試,所以這里用第三種方案 MDC。
MDC(Mapped Diagnostic Context)用于存儲運行上下文的特定線程的上下文數(shù)據(jù)。因此,如果使用 log4j 進行日志記錄,則每個線程都可以擁有自己的MDC,該 MDC 對整個線程是全局的。屬于該線程的任何代碼都可以輕松訪問線程的 MDC 中存在的值。
我們先來看第一個痛點,如何在一個請求中,將多條日志串聯(lián)起來。
該方案的原理如下圖所示:
(1)在 logback 日志配置文件中的日志格式中添加 %X{traceId} 配置。
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %X{traceId} %-5level %logger - %msg%n
(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("/**");
}
}
解決方案的原理圖如下所示:
訂單服務遠程調用優(yōu)惠券服務,需要在訂單服務中添加 OpenFeign 的攔截器,攔截器里面做的事就是往 請求的 header 中添加 traceId,這樣調用到優(yōu)惠券服務時,就能從 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,將整個調用鏈路檢索出來了。

我們在微信上24小時期待你的聲音
解答本文疑問/技術咨詢/運營咨詢/技術建議/互聯(lián)網(wǎng)交流