1. 程式人生 > >巧用MDC實現簡易分散式鏈路追蹤

巧用MDC實現簡易分散式鏈路追蹤

背景

在目前的Java web應用或者RPC應用中,當應用發生異常時,我們很難知道這些異常資訊到底是從哪發生(起點),到哪結束(終點),在一個日誌檔案中,哪條日誌資訊是發生異常的使用者產生的?在什麼時候發生的異常?我們想要定位這些的時候,發現並不是很容易,因為沒有一個唯一的識別符號,能從使用者的請求到響應這一個完整的過程給串聯起來。那麼鑑於這種情況,我們就需要一個能夠記錄使用者請求一進來就產生的id,當用戶請求完成之後,將這個id移除。這個id下文統稱reqId。

本文將簡述怎麼利用logback或者log4j實現請求id的埋點和基於http協議不同系統之間請求id的傳遞,以及最後的小實戰展示一下效果。

針對主流的APM軟體,大多數會存在traceIdspanId這種概念,這裡的reqId類似traceId

目前主流APM監控軟體

在有現成的輪子的情況下,其實我一般是不會重新造輪子,除非這個輪子不好用,一個半掛車的輪子安裝在自行車上很顯然是不合適的,不過,還是介紹下一些開源的輪子。

市面上大多數輪子都是基於谷歌的Dapper論文而實現的,而且還有不少是基於OpenTracing規範實現的,例如

  • Zipkin
  • Skywalking
  • Cat
  • Pinpoint

等等,其實還有其他的,這裡就不一一列舉了。這些軟體都很優秀,只是個人覺得,目前的應用場景還不是很適合使用這些這麼大的輪子,要不然我這破自行車裝這麼些個輪子,會顯得很突兀的。那麼針對自行車,要怎麼造一個比較合適自行車的輪子呢?

MDC介紹

在網上一搜MDC會出現很多相關的介紹,這裡只是簡單介紹一下,如果有需要,請自行百度吧。

MDC據我瞭解,在Log4j/slf4j都有支援,其底層實際上是使用了ThreadLocal,保證同一個執行緒內,這個reqId都是唯一的。只要是在同一個執行緒內打的日誌,其reqId都會被打印出來(通過配置logger patten %X{reqId})。

如何進行埋點和分散式支援?

上面簡單介紹了一下MDC,那麼在何時埋點才比較合適呢?接下來簡單說下SpringMVC和Servlet的埋點。然後再談一談怎麼在分散式中實現!

如何埋點

SpringMVC中的埋點

不管是SpringMVC應用還是SpringBoot應用,這裡都是一樣的,就一般情況而言,埋點都會在請求進來的時候埋下,請求結束的時候移除(避免記憶體洩漏),在SpringMVC系的應用裡面,可以使用Interceptor

攔截器來實現這個功能,主要程式碼如下所示

public class LogInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String reqId = IdUtil.objectId();
        MDC.put(Constants.REQ_ID_KEY, reqId);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 從MDC移除請求id
        MDC.remove(Constants.REQ_ID_KEY);
    }
}

值得注意的是,這個攔截器的執行順序應該儘量靠前,這樣在其他攔截器裡面發生的異常資訊也可以通過這個埋點進行追蹤。

Servlet中的埋點

在Servlet應用中,或者基於Servelt的應用中,都可以通過Filter來實現埋點這個操作,原理也是請求進來的時候將請求id進行埋點,請求結束的時候,從MDC移除這個請求id。下面看下在過濾器裡頭如何實現。

public class LogFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
         // 請求前,設定reqId到MDC
         String reqId = IdUtil.objectId();
         MDC.put(Constants.REQ_ID_KEY, reqId);
         chain.doFilter(request, response);
         // 從MDC移除reqId
         MDC.remove(Constants.REQ_ID_KEY);
    }
}

可以看到,其實不管是SpringMVC還是原生的Servlet Filter,都很簡單就實現了埋點這個動作。而且原理都是一樣的。

其實在SpringMVC內,如果是5以前,用的是webmvc的包,底層也是走Servlet,所有請求都是經過DispatchServlet這個Servlet進入,那麼一樣可以使用Filter來埋點!如果用的是webflux那就不能直接用Filter來埋點了。

如何在不同系統之間傳遞reqId?

現在假定這麼一種情況,如果同一家公司的不同系統之間,都採用統一的通訊協議以及序列化協議來實現,例如統一採用dubbo的rpc實現,或者統一基於http+json的形式。下面以比較簡單的http+json+springmvc來舉例子,如果採用其他mvc框架也是大同小異的。

在上述的埋點例子中,我們是在基於攔截器進行埋點進去,那麼當前請求是否是整個分散式請求鏈中的第一環呢?為了區分到底是不是第一個環(也就是最先接收到請求的那個環節),我們約定,如果請求頭中,帶有x-forward-reqId的都不是第一環。那麼埋點的程式碼可以改寫成如下的樣子

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 針對分散式情況,先從header嘗試獲取reqId,如果獲取不到,則有可能是從當前服務開始發起呼叫的
        String reqId = request.getHeader(Constants.X_FORWARD_REQ_ID);
        if (StrUtil.isBlank(reqId)) {
            // 生成請求id並加入MDC
            reqId = IdUtil.objectId();
        }
        MDC.put(Constants.REQ_ID_KEY, reqId);
        return true;
    }

埋點的時候,先獲取請求頭是否有x-forward-reqId,如果有,則不再重新生成reqId,繼續沿用傳遞過來的reqId。那麼現在問題來了,怎麼將這個請求頭傳遞過去其他系統呢?下面舉例說明一下。

對於一般的http請求而言,大多數使用的客戶端都是httpclientokhttphttpurlconnection等,然而不同的情況配置不大相同,這裡也沒有辦法一一列舉出來。下面對筆者遇到過的幾種情況做出簡要分析,僅供參考:

  • 如果採取的是使用zuul等作為閘道器的話,基本不用怎麼設定,請求經過閘道器之後,就生成reqId,從閘道器傳遞下去即可,因為其他系統的請求也會經過閘道器。
  • 如果直接使用http客戶端對其他系統做呼叫,不管採用的是httpclient還是okhttp,皆可做全域性全域性配置,即構造一個HttpReqProxy的物件,在Proxy物件裡面將獲取到的請求頭設定進去,方便傳遞。
  • 採用Spring的RestTemplate,這種時候可以編寫一個工具類,在工具類內注入RestTemplate或者new一個RestTemplate,然後通過HttpServletRequest request =((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest(); 獲取到request物件,從而獲取請求頭設定到RestTemplate中。

到此,就介紹完如何埋點到MDC,又如何利用請求頭進行傳遞reqId到其他系統中去。這裡只是給出一種思路,是一種很輕量級的解決方案,如果是比較大型的系統,還是建議使用類似zipkin這樣的完備的產品做追蹤。

Skywalking和zipkin目前都比較成熟,可以使用在大型專案中,只是說一些較為簡單的單體應用或者並不希望引入如此龐大的依賴的分散式應用可以考慮此種思路。該思路較為簡單,功能上也較為單一

小實戰

其實也算不上什麼實戰吧,本人之前寫過一篇部落格,《springboot2+logback將日誌輸出到oracle資料庫的踩坑之旅》介紹了怎麼使用logback將日誌存放到資料庫中,那麼我們加入到MDC的值也一併會被記錄進去,正是利用這個特性,可以將多個不同的系統的logback的DBAppender的資料庫連線指向同一個資料庫,那麼在資料庫裡面,就可以通過reqId查詢出對應的logger記錄,每條logger對應有其自己的eventid,根據這個可以查詢出對應的異常堆疊了。

以下貼出本人實現的截圖

檢視堆疊

相關推薦

MDC實現簡易分散式追蹤

背景 在目前的Java web應用或者RPC應用中,當應用發生異常時,我們很難知道這些異常資訊到底是從哪發生(起點),到哪結束(終

基於SLF4J MDC機制實現日誌的追蹤

問題描述 最近經常做線上問題的排查,而排查問題用得最多的方式是檢視日誌,但是在現有系統中,各種無關日誌穿行其中,導致我沒辦法快速的找出使用者在一次請求中所有的日誌。 問題分析 我們沒辦法快速定位使用者在一次請求中對應的所有日誌,或者說是定位某個使用者操

.NET Core整合SkyWalking+SkyAPM-dotne實現分散式追蹤

.NET Core整合SkyWalking+SkyAPM-dotnet實現分散式鏈路追蹤 SkyWalking是一款APM(應用效能管理),其他的還有Cat、Zipkin、Pinpoint等。 隨著微服務架構的流行,一次請求會涉及多個服務的呼叫,並且服務本身也可能會依賴其他服務,整個請求路徑會構成一個呼叫鏈

分散式追蹤框架的基本實現原理

[TOC] 本系列共有三篇: .NET Core 中的日誌與分散式鏈路追蹤 分散式鏈路追蹤框架的基本實現原理(當前) 開源一個簡單的相容 Jaeger 的框架 檸檬([Lemon丶](https://www.cnblogs.com/liuhaoyang/))大佬在一月份開業了檸檬研究院,研究院指導

Laravel + go-micro + grpc 實踐基於 Zipkin 的分散式追蹤系統 摘自https://mp.weixin.qq.com/s/JkLMNabnYbod-b4syMB3Hw?

分散式呼叫鏈跟蹤系統,屬於監控系統的一類。系統架構逐步演進時,後期形態往往是一個平臺由很多不同的服務、元件構成,使用者請求過來後,可能會經過其中多個服務,如圖     不過,出問題時往往很難排查,如整個請求變慢、偶爾報錯、不可用等,我們很難得知具體是由哪一個或哪些服務引起的,通常

開源APM系統skywalking整合springcloud分散式追蹤

SkyWalking    被用於追蹤、監控和診斷分散式系統,特別是使用微服務架構,雲原生或容積技術。主要功能如下:分散式追蹤和上下文傳輸、應用、例項、服務效能指標分析、根源分析、應用拓撲分析、應用和服務依賴分析、慢服務檢測、效能優化 demo搭建如下: 1.下載

【阿里雲ACE成長記第5期】分散式追蹤系統架構設計的經驗分享

【引言】本期由阿里雲ACE(阿里雲開發者社群)&成都檸檬雲網絡技術有限公司資深架構師 曾昌強 為大家分享個人成長經歷與個人專業技術之分散式鏈路追蹤系統架構設計。視訊:https://yq.aliyun.com/live/581 Part 1:成長經歷講述一個不知道什麼叫程式設計的門外漢,如何穿越幾千

skywalking分散式追蹤監控系統部署

SkyWalking 是針對分散式系統的 APM 系統,也被稱為分散式追蹤系統 全自動探針監控,不需要修改應用程式程式碼。檢視支援的中介軟體和元件庫列表:https://github.com/apache/incubator-skywalking 支援手動探針監控, 提供了支援 Ope

spring cloud 分散式追蹤

  微服務之間進行呼叫 那麼如果我負責一個模組 別人負責另一個模組 我呼叫了他的方法 測試那邊卻報了錯 那是我的問題還是他的問題   這個時候大家應該就能想到日誌可以解決這個問題   如何使用日誌呢 先在配置檔案中加   logging:   path:D:\logs

Spring Boot + Spring Cloud 構建微服務系統(八):分散式追蹤(Sleuth、Zipkin)

技術背景 在微服務架構中,隨著業務發展,系統拆分導致系統呼叫鏈路愈發複雜,一個看似簡單的前端請求可能最終需要呼叫很多次後端服務才能完成,那麼當整個請求出現問題時,我們很難得知到底是哪個服務出了問題導致的,這時就需要解決一個問題,如何快速定位服務故障點,於是,分散式系統呼叫鏈追蹤技術就此誕生了。 ZipKin

springcloud-slenth-zipkin進行分散式追蹤

Spring Cloud Sleuth 一般的,一個分散式服務跟蹤系統,主要有三部分:資料收集、資料儲存和資料展示。根據系統大小不同,每一部分的結構又有一定變化。譬如,對於大規模分散式系統,資料儲存可分為實時資料和全量資料兩部分,實時資料用於故障排查(troub

分散式追蹤

隨著業務發展,系統拆分導致系統呼叫鏈路愈發複雜一個前端請求可能最終需要呼叫很多次後端服務才能完成,當整個請求變慢或不可用時,我們是無法得知該請求是由某個或某些後端服務引起的,這時就需要解決如何快讀定位服務故障點,以對症下藥。於是就有了分散式系統

java分散式追蹤;jvm應用監控-skywalking

當企業應用進入分散式微服務時代,應用服務依賴會越來越多,skywalking可以很好的解決服務呼叫鏈路追蹤的問題,而且基於java探針技術,基本對應用零侵入零耦合。skywalking是什麼,有什麼用?Skywalking 是一個APM系統,即應用效能監控系統,為微服務架構和

分散式追蹤技術對比

方案選擇 本文最終選擇了zipkin+sleuth做二次開發,這樣做靈活性比較大一點。 有興趣的可以進我部落格看一下,我會將我二次開發過程當中遇到的問題發出來。 常見開源產品 cat, zipkin, pinpoint , skywalking  cat 

關於分散式追蹤的一些記錄

基本原理    目前所有的分散式鏈路追蹤都是來自於谷歌的一篇論文。論文地址如下:https://www.jianshu.com/p/cdefc9971951?utm_campaign=maleskine&utm_content=note&utm_medium=

skyWalking分散式追蹤部署

使用環境 centos7.3 JKD1.8 elasticsearch5.6.8 skyWalking3.2.6 筆者把所有的環境都打包在這百度網盤裡面 下載完之後筆者是把檔案放在下面這個目錄 mv sky/ /usr/

spring cloud 2.0 入門系列一 (10)分散式追蹤-Zipkin

服務說明 Zipkin是什麼 Zipkin分散式跟蹤系統;它可以幫助收集時間資料,解決在microservice架構下的延遲問題;它管理這些資料的收集和查詢;Zipkin的設計是基於谷歌的Google Dapper論文。 每個應用程式向Zipkin報告定時

分散式追蹤系統深入理解

背景 對於普通系統或者服務來說,一般通過打日誌來進行埋點,然後再通過elk進行定位及分析問題,更有甚者直接遠端伺服器,使用各種linux命令單手操作檢視日誌,說到這,我也沒擺脫這種困境。那麼隨著業務越來越複雜,企業應用也進入了分散式服務化的階段,傳統的日誌監控

第十八天:浪跡天涯網上商城(1.0版本)--引入spring cloud sleuth分散式追蹤

1、需求 我們都知道隨著專案的發展,各個底層的服務呼叫關係複雜,有時候因為某個服務的效能問題導致整個呼叫鏈出現故障,那麼排查問題是很困難的。現在我們引入Spring Cloud Sleuth分散式鏈路追蹤來解決這個問題。 2、Spring Cloud Sleut

聊聊分散式追蹤

原文連結:http://lidawn.github.io/2018/12/26/distribute-tracing/ 起因 最近一直在做分散式鏈路追蹤的調研和實踐,整理一下其中的知識點。 什麼是鏈路追蹤 分散式系統變得日趨複雜,越來越多的元件開始走向分散式化,如微服務、分散式資料庫、分散式快取等,使