1. 程式人生 > >Dubbo 全鏈路追蹤日誌的實現

Dubbo 全鏈路追蹤日誌的實現

微服務架構的專案,一次請求可能會呼叫多個微服務,這樣就會產生多個微服務的請求日誌,當我們想要檢視整個請求鏈路的日誌時,就會變得困難,所幸的是我們有一些集中日誌收集工具,比如很熱門的ELK,我們需要把這些日誌串聯起來,這是一個很關鍵的問題,如果沒有串聯起來,查詢起來很是很困難,我們的做法是在開始請求系統時生成一個全域性唯一的id,這個id伴隨這整個請求的呼叫週期,即當一個服務呼叫另外一個服務的時候,會往下傳遞,形成一條鏈路,當我們檢視日誌時,只需要搜尋這個id,整條鏈路的日誌都可以查出來了。

現在以dubbo微服務架構為背景,舉個栗子:

A -> B -> C

我們需要將A/B/C/三個微服務間的日誌按照鏈式列印,我們都知道Dubbo的RpcContext只能做到消費者和提供者共享同一個RpcContext,比如A->B,那麼A和B都可以獲取相同內容的RpcContext,但是B->C時,A和C就無法共享相同內容的RpcContext了,也就是無法做到鏈式列印日誌了。

那麼我們是如何做到呢?

我們可以用左手交換右手的思路來解決,假設左手是執行緒的ThreadLocal,右手是RpcContext,那麼在交換之前,我們首先將必要的日誌資訊儲存到ThreadLocal中。

在我們的專案微服務中大致分為兩種容器型別的微服務,一種是Dubbo容器,這種容器的特點是隻使用spring容器啟動,然後使用dubbo進行服務的暴露,然後將服務註冊到zookeeper,提供服務給消費者;另一種是SpringMVC容器,也即是我們常見的WEB容器,它是我們專案唯一可以對外開放介面的容器,也是充當專案的閘道器功能。

在瞭解了微服務容器之後,我們現在知道了呼叫鏈的第一層一定是在SpringMVC容器層中,那麼我們直接在這層寫個自定義攔截器就ojbk了,talk is cheap,show you the demo code:

舉例一個Demo程式碼,公共攔截器的前置攔截中程式碼如下:

public class CommonInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler)
        throws Exception {

        // ...

        // 初始化全域性的Context容器
        Request request = initRequest(httpServletRequest);
        // 新建一個全域性唯一的請求traceId,並set進request中
        request.setTraceId(JrnGenerator.genTraceId());
        // 將初始化的請求資訊放進ThreadLocal中
        Context.initialLocal(request);

        // ...

        return true;
    }
    
    // ...
    
}

系統內部上下文物件:

public class Context {
    
    // ...
    
    private static final ThreadLocal<Request> REQUEST_LOCAL = new ThreadLocal<>();
    
    public final static void initialLocal(Request request) {
        if (null == request) {
            return;
        }
        REQUEST_LOCAL.set(request);
    }
    
    public static Request getCurrentRequest() {
        return REQUEST_LOCAL.get();
    }
    
    // ...
}

攔截器實現了org.springframework.web.servlet.HandlerInterceptor介面,它的主要作用是用於攔截處理請求,可以在MVC層做一些日誌記錄與許可權檢查等操作,這相當於MVC層的AOP,即符合橫切關注點的所有功能都可以放入攔截器實現。

這裡的initRequest(httpServletRequest);就是將請求資訊封裝成系統內容的請求物件Request,並初始化一個全域性唯一的traceId放進Request中,然後再把它放進系統內部上下文ThreadLocal欄位中。

接下來講講如何將ThreadLocal中的內容放到RpcContext中,在講之前,我先來說說Dubbo基於spi擴充套件機制,官方文件對攔截器擴充套件解釋如下:

服務提供方和服務消費方呼叫過程攔截,Dubbo 本身的大多功能均基於此擴充套件點實現,每次遠端方法執行,該攔截都會被執行,請注意對效能的影響。

也就是說我們進行服務遠端呼叫前,攔截器會對此呼叫進行攔截處理,那麼就好辦了,在消費者呼叫遠端服務之前,我們可以偷偷把ThreadLocal的內容放進RpcContext容器中,我們可以基於dubbo的spi機制擴充套件兩個攔截器,一個在消費者端生效,另一個在提供者端生效:

在META-INF中加入com.alibaba.dubbo.rpc.Filter檔案,內容如下:

provider=com.objcoding.dubbo.filter.ProviderFilter
consumer=com.objcoding.dubbo.filter.ConsumerFilter

消費者端攔截處理:


public class ConsumerFilter implements Filter {
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) 
        throws RpcException {

        //1.從ThreadLocal獲取請求資訊
        Request request = Context.getCurrentRequest();
        //2.將Context引數放到RpcContext
        RpcContext rpcCTX = RpcContext.getContext();
        // 將初始化的請求資訊放進ThreadLocal中
        Context.initialLocal(request);

        // ...

    }   
}

Context.getCurrentRequest();就是從ThreadLocal中拿到Request請求內容,contextToDubboContext(request);將Request內容放進當前執行緒的RpcContext容器中。

很容易聯想到提供者也就是把RpcContext中的內容拿出來放到ThreadLocal中:

public class ProviderFilter extends AbstractDubboFilter implements Filter{
     @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) 
        throws RpcException {
        // 1.獲取RPC遠端呼叫上下文
        RpcContext rpcCTX = RpcContext.getContext();
        // 2.初始化請求資訊
        Request request = dubboContextToContext(rpcCTX);
        // 3.將初始化的請求資訊放進ThreadLocal中
        Context.initialLocal(request);

        // ...

    }   
}

接下來我們還要配置log4j2,使得我們同一條請求在關聯的每一個容器列印的訊息,都有一個共同的traceId,那麼我們在ELK想要查詢某個請求時,只需要搜尋traceId,就可以看到整條請求鏈路的日誌了。

我們在Context上下文物件的initialLocal(Request request)方法中在log4j2的上下文中新增traceId資訊:

public class Context {
    
    // ...

    final public static String TRACEID = "_traceid";

    public final static void initialLocal(Request request) {
        if (null == request) {
            return;
        }
        // 在log4j2的上下文中新增traceId
        ThreadContext.put(TRACEID, request.getTraceId());
        REQUEST_LOCAL.set(request);
    }
    
    // ...
}

接下來實現org.apache.logging.log4j.core.appender.rewrite.RewritePolicy

@Plugin(name = "Rewrite", category = "Core", elementType = "rewritePolicy", printObject = true)
public final class MyRewritePolicy implements RewritePolicy {

    // ...
    
    @Override
    public LogEvent rewrite(final LogEvent source) {
        HashMap<String, String> contextMap = Maps.newHashMap(source.getContextMap());
        contextMap.put(Context.TRACEID, contextMap.containsKey(Context.TRACEID) ? contextMap.get(Context.TRACEID) : NULL);
        return new Log4jLogEvent.Builder(source).setContextMap(contextMap).build();
    }
    
    // ...
}

RewritePolicy的作用是我們每次輸出日誌,log4j都會呼叫這個類進行一些處理的操作。

配置log4j2.xml:

<Configuration status="warn">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout
                pattern="[%d{yyyy/MM/dd HH:mm:ss,SSS}][${ctx:_traceid}]%m%n" />
        </Console>
        
        <!--定義一個Rewrite-->
        <Rewrite name="Rewrite">
            <MyRewritePolicy/>
            <!--引用輸出模板-->
            <AppenderRef ref="Console"/>
        </Rewrite>
    </Appenders>
    <Loggers>
       
        <!--使用日誌模板-->
        <Logger name="com.objcoding.MyLogger" level="debug" additivity="false">
            <!--引用Rewrite-->
            <AppenderRef ref="Rewrite"/>
        </Logger>
    </Loggers>
</Configuration>

自定義日誌類:

public class MyLogger {
    private static final Logger logger = LoggerFactory.getLogger(MyLogger.class);
    
     public static void info(String msg, Object... args) {
        if (canLog() == 1 && logger.isInfoEnabled()) {
            logger.info(msg, args);
        }
    }
    
    public static void debug(String message, Object... args) {
        if (canLog() == 1 && logger.isDebugEnabled()) {
            logger.debug(message, args);
        }
    }
    
    // ..
}

更多精彩文章請關注作者維護的公眾號「後端進階」,這是一個專注後端相關技術的公眾號。
關注公眾號並回復「後端」免費領取後端相關電子書籍。
歡迎分享,轉載請保留出處。

相關推薦

Dubbo 追蹤日誌實現

微服務架構的專案,一次請求可能會呼叫多個微服務,這樣就會產生多個微服務的請求日誌,當我們想要檢視整個請求鏈路的日誌時,就會變得困難,所幸的是我們有一些集中日誌收集工具,比如很熱門的ELK,我們需要把這些日誌串聯起來,這是一個很關鍵的問題,如果沒有串聯起來,查詢起來很是很困難,我們的做法是在開始請求系統時生成一

基於SLF4J的MDC機制和Dubbo的Filter機制,實現分散式系統的日誌追蹤

#原文連結:[基於SLF4J的MDC機制和Dubbo的Filter機制,實現分散式系統的日誌全鏈路追蹤](https://blog.csdn.net/Howinfun/article/details/109478879) # 一、日誌系統 ## 1、日誌框架 在每個系統應用中,我們都會使用日誌系統,主要

dubbo + zipkin 實現追蹤

lte parser gflags 成功 bstr factory arr hup 分布式系 隨著業務的發展,應用的規模不斷的擴大,傳統的應用架構無法滿足訴求,服務化架構改造勢在必行,以 Dubbo 為代表的分布式服務框架成為了服務化改造架構中的基石。隨著微服務理念逐漸被大

追蹤spring-cloud-sleuth-zipkin

authorize 采樣 quest child 手機號 main rgs lin oot 微服務架構下 多個服務之間相互調用,在解決問題的時候,請求鏈路的追蹤是十分有必要的,鑒於項目中采用的spring cloud架構,所以為了方便使用,便於接入等 項目中采用了sprin

request的追蹤(翻譯自官網)

MDC全稱Mapped Diagnostic Context,翻譯為上下文資訊診斷對映。為了追蹤不同客戶端對服務端的請求,並記錄他們的日誌呼叫資訊。一個簡單的做法就是為每個提供服務的客戶端請求單獨的記錄日誌資訊。LOGBACK利用了一系列這個技術應用到SLF4J API,

Net和Java基於zipkin的追蹤

  在各大廠分散式鏈路跟蹤系統架構對比 中已經介紹了幾大框架的對比,如果想用免費的可以用zipkin和pinpoint還有一個忘了介紹:SkyWalking,具體介紹可參考:https://github.com/apache/incubator-skywalking/blob

Spring Cloud(Finchley.RELEASE版本)微服務學習實踐:6.2追蹤監控-Zipkin

環境:jdk1.8;spring boot2.0.3;spring cloud(Finchley.RELEASE版本);Maven3.3摘要說明:Zipkin:Zipkin是一個分散式追蹤系統。它有助於收集解決微服務架構中的延遲問題所需的時序資料。它管理這些資料的收集和查詢。

springboot zipkin elasticsearch追蹤

https://github.com/apache/incubator-zipkin https://github.com/op

一鍵託管,阿里雲追蹤服務正式商用:成本僅自建1/5或更少

隨著網際網路架構的擴張,分散式系統變得日趨複雜,越來越多的元件開始走向分散式化,如微服務、訊息收發、分散式資料庫、分散式快取、分散

基於Spring Boot + Dubbo日誌追蹤(一)

一、 概要 當前公司後端整體架構為:Spring Boot + Dubbo。由於早期專案進度等原因,對日誌這塊沒有統一的規範,基本

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

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

Dubbo 結合 Spring Boot 的探索(註冊發現,管理監控,日誌跟蹤)

dubboot-example 探索dubbo 和 spring boot 的結合, 採用https://github.com/dubbo/dubbo-spring-boot-project 來實現dubbo和spring boot的結合。(新的Spring

餓了麽壓測平臺的實現與原理

test www. 試用 推送 定位 用戶操作 吞吐量 查詢 定期清理 背景 在上篇文章中,我們曾介紹過餓了麽的全鏈路壓測的探索與實踐,重點是業務模型的梳理與數據模型的構建,在形成腳本之後需要人工觸發執行並分析數據和排查問題,整個過程實踐下來主要還存在以下問題: 測試成本

Spring Boot + Spring Cloud 實現許可權管理系統 後端篇(二十二):追蹤(Sleuth、Zipkin)

線上演示 演示地址:http://139.196.87.48:9002/kitty 使用者名稱:admin 密碼:admin 技術背景 在微服務架構中,隨著業務發展,系統拆分導致系統呼叫鏈路愈發複雜,一個看似簡單的前端請求可能最終需要呼叫很多次後端服務才能完成,那麼當整個請求出現問題時,我們很難得知到

dubbo分散式系統追蹤_zipkin

基礎知識儲備 分散式跟蹤的目標 一個分散式系統由若干分散式服務構成,每一個請求會經過多個業務系統並留下足跡,但是這些分散的資料對於問題排查,或是流程優化都很有限,要能做到追蹤每個請求的完整鏈路呼叫,收集鏈路呼叫上每個服務的效能資料,計算效能資料和比對效能指標(SLA),甚至能夠再反饋

Centos7 安裝skywalking+elasticsearch,實現java web程式追蹤

skywalking+elasticsearch實現java web程式鏈路追蹤 原始碼:聯絡作者1782800572 Skywalking暫不支援elasticsearch版本6.0.0以上 安裝elastic search5.6.13: tar -xzvf el

阿里雲釋出追蹤服務Tracing Analysis,從此告別告別日誌查詢

近日,在杭州雲棲大會上,阿里雲釋出了鏈路追蹤服務Tracing Analysis,成本是自建鏈路追蹤系統的1/5或更少,可為分散式應用的開發者提供完整的呼叫鏈路還原、呼叫請求量統計、鏈路拓撲、應用依賴分析等工具,幫助開發者快速分析和診斷分散式應用架構下的效能瓶頸,提高微服務時代下的開發診斷效

Dubbox 追蹤(基於Brave+Zipkin的簡單實現)上

很多時候,我們都能體會到分散式架構的話好處,其實一個系統不大,做分散式的成本是很高的,系統變得鬆耦合,這樣做的好處不言而喻,說說壞處吧,A系統遠端呼叫B系統,B系統又依賴C,D系統,當線上某個介面報錯,或者超時的時候,亦或者是業務問題的時候,定位一個問題是麻煩的,因為日記不

SpringCloud學習記錄——Sleuth服務追蹤(Zipkin實現

1、簡介。Spring Cloud Sleuth 主要功能就是在分散式系統中提供追蹤解決方案,並且相容支援了 zipkin,你只需要在pom檔案中引入相應的依賴即可。(呃呃,講人話就是:“既然是搞分散式微服務架構,那麼隨著專案的越來越大,微服務就會越來越多,微服務之間的呼叫會

原理分析dubbo分散式應用中使用zipkin做追蹤

zipkin是什麼 Zipkin是一款開源的分散式實時資料追蹤系統(Distributed Tracking System),基於 Google Dapper的論文設計而來,由 Twitter 公司開發貢獻。其主要功能是聚集來自各個異構系統的實時監控資料。分散式跟蹤系統還有其他比較成熟的實現,例如:Nave