監(jiān)控之traceid

監(jiān)控之前只總結(jié)了一篇《微服務(wù)-監(jiān)控》,比較宏觀。其中很多細(xì)節(jié)沒(méi)有過(guò)深關(guān)注到,主要還是沒(méi)有實(shí)踐過(guò),更沒(méi)有去深度思考,所以很多有意思的技術(shù)點(diǎn)都錯(cuò)過(guò)了,比如traceid的生成,傳遞

大牛圈總的大作《微服務(wù)系統(tǒng)架構(gòu)之分布式traceId追蹤參考實(shí)現(xiàn)》已經(jīng)給出解決方案,但還是再主動(dòng)總結(jié)一下

意義

為什么需要traceid,為了查看完整的調(diào)用鏈,一旦調(diào)用過(guò)程中出現(xiàn)問(wèn)題,可以第一時(shí)間定位到問(wèn)題現(xiàn)場(chǎng)

整個(gè)調(diào)用鏈?zhǔn)且豢脴?shù)形結(jié)構(gòu),traceid的傳遞涉及到主干與支干,進(jìn)程內(nèi)與進(jìn)程外

生成

原則是唯一不重復(fù),比如現(xiàn)成的UUID

但UUID一是丑、無(wú)意義,二是string;

從字面意義以及未來(lái)落盤(pán)都不能說(shuō)是最佳方案,比如想讓traceid包含信息更豐富一些,能一眼看出此traceid是主干還是分支

此traceid有沒(méi)有最終落盤(pán)(這兒涉及到落盤(pán)抽樣率,每天服務(wù)處理海量請(qǐng)求,總不能每個(gè)traceid都落盤(pán))

Random

這兒引申到如何更好地獲取一個(gè)隨機(jī)數(shù)又是一個(gè)課題,另開(kāi)篇吧

傳遞

《熔斷機(jī)制》中提過(guò),服務(wù)調(diào)用是一個(gè)1->N扇出,調(diào)用鏈展現(xiàn)出對(duì)應(yīng)的樹(shù)形結(jié)構(gòu),但調(diào)用嵌套都不會(huì)深,一般兩層就差不多了

  • traceId1
    • traceId1.1
      • traceId1.1.1
    • traceId2.1
    • traceId3.1

進(jìn)程外

服務(wù)之間的傳遞

serverA --> serverB -- serverC

這兒在設(shè)計(jì)傳輸協(xié)議時(shí),在協(xié)議頭里面帶上traceid

進(jìn)程內(nèi)

主干

這種場(chǎng)景ThreadLocal是最佳手法

支干

比如serviceA -- > remote.serviceB

trace是個(gè)樹(shù)形結(jié)構(gòu),可以將remote.serviceB的traceId.parentId = serviceA.traceId

異步子任務(wù)

子線程可以通過(guò)InheritableThreadLocal傳遞traceid

順帶一下,InheritableThreadLocal的詳細(xì)實(shí)現(xiàn),先可補(bǔ)習(xí)一下ThreadLocal《解析ThreadLocal》

在創(chuàng)建Thread時(shí),會(huì)從父線程的inheritableThreadLocals復(fù)制到子線程中去,這樣在子線程中就能拿到在父線程中的賦值

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

/*
 * InheritableThreadLocal values pertaining to this thread. This map is
 * maintained by the InheritableThreadLocal class.
 */
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
if (parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

線程池

如果沒(méi)有線程池,以上就算是解決所有問(wèn)題了,可實(shí)現(xiàn)畢竟是實(shí)現(xiàn)

/**
 * 子線程從父線程中取值
 * @throws InterruptedException
 */
private static void testThreadpool() throws InterruptedException {
    ExecutorService executorService = Executors.newFixedThreadPool(1);
    final ThreadLocal<String>  threadLocal = threadLocal();//new InheritableThreadLocal<>()
    threadLocal.set("parent");
    for(int i=0;i<1;i++) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() +" get parent value:" + threadLocal.get());
                threadLocal.set("sun");
                System.out.println(Thread.currentThread().getId() + "==" + threadLocal.get());
            }
        };
        executorService.execute(runnable);
        Thread.sleep(100);
        executorService.execute(runnable);
        Thread.sleep(100);
        System.out.println("main:" + threadLocal.get());
    }
    executorService.shutdown();
}

為了好重現(xiàn)問(wèn)題,線程池大小為1,但會(huì)連續(xù)跑兩次任務(wù)

pool-1-thread-1 get parent value:parent
11==sun
pool-1-thread-1 get parent value:sun
11==sun
main:parent

在第二次取父線程值時(shí),卻是第一次任務(wù)線程中的賦值,在線程池中子線程不能正常獲取父線程值

線程池中,線程會(huì)復(fù)用,線程中的inheritableThreadLocals沒(méi)有被清空

解決方法一是:池中線程數(shù)大于任務(wù)線程,讓線程沒(méi)有重用機(jī)會(huì)

ExecutorService executor = Executors.newFixedThreadPool(>=[任務(wù)線程數(shù)])

但在多線程應(yīng)用中,明顯不能解決問(wèn)題,任務(wù)數(shù)肯定遠(yuǎn)遠(yuǎn)超過(guò)線程數(shù)

解決方法二是:自定義實(shí)現(xiàn)在使用完線程主動(dòng)清空inheritableThreadLocals

阿里開(kāi)源transmittable-thread-local就實(shí)現(xiàn)這樣的功能

整體思路也是從主線程復(fù)制,使用,再清理

TtlRunnable 構(gòu)造方法中,調(diào)用了 TransmittableThreadLocal.Transmitter.capture() 獲取當(dāng)前線程中所有的上下文,并儲(chǔ)存在 AtomicReference 中

當(dāng)線程執(zhí)行時(shí),調(diào)用 TtlRunnable run 方法,TtlRunnable 會(huì)從 AtomicReference 中獲取出調(diào)用線程中所有的上下文,并把上下文給 TransmittableThreadLocal.Transmitter.replay 方法把上下文復(fù)制到當(dāng)前線程。并把上下文備份

當(dāng)線程執(zhí)行完,調(diào)用 TransmittableThreadLocal.Transmitter.restore 并把備份的上下文傳入,恢復(fù)備份的上下文,把后面新增的上下文刪除,并重新把上下文復(fù)制到當(dāng)前線程

transmittable-thread-local代碼不多,但有很多亮點(diǎn),可以自行膜拜

在此場(chǎng)景,transmittable-thread-local還是太重了,其實(shí)可以簡(jiǎn)單借鑒一下transmittable-thread-local的思路,自定義Runnable

public TransRunnable(Runnable runnable){
    this.runnable = runnable;
    //在創(chuàng)建時(shí),獲取父traceId
    this.parentId = TranceContext.getParentTrace();
}
@Override
public void run() {
    //
    String old = TranceContext.getParentTrace();
    //設(shè)置父traceid
    TranceContext.setParentTrace(parentId);
    runnable.run();
    //還原
    TranceContext.setParentTrace(old);
}

在創(chuàng)建子線程時(shí),把父traceId帶進(jìn)去,就能在子線程業(yè)務(wù)方法中拿到父traceId,這樣整個(gè)調(diào)用鏈也不會(huì)斷

schedule

traceid生成,有主動(dòng)請(qǐng)求時(shí),會(huì)生成,但如果是個(gè)系統(tǒng)的定時(shí)任務(wù)呢?

  1. 讓taskService調(diào)用一下入口,類(lèi)似模擬用戶(hù)行為
  2. 主動(dòng)生成一個(gè)parent traceId

總結(jié)

到此,對(duì)于traceid的知識(shí)結(jié)構(gòu)豐滿(mǎn)了很多

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言 ThreadLocal解決了在多個(gè)線程針對(duì)一個(gè)變量維護(hù)不同值的功能,如果你想在同一個(gè)線程內(nèi)傳遞一些值,那么就...
    土豆肉絲蓋澆飯閱讀 4,448評(píng)論 1 8
  • 轉(zhuǎn)自CSDN文章:Java面試題集 求職是在每個(gè)技術(shù)人員的生涯中都要經(jīng)歷多次。對(duì)于我們大部分人而言,在進(jìn)入自己心儀...
    流浪java閱讀 872評(píng)論 0 12
  • 面向?qū)ο蟮娜齻€(gè)特征 封裝,繼承,多態(tài).這個(gè)應(yīng)該是人人皆知.有時(shí)候也會(huì)加上抽象. 多態(tài)的好處 允許不同類(lèi)對(duì)象對(duì)同一消...
    Blizzard_liu閱讀 1,802評(píng)論 0 6
  • 兒子努力你就是最棒的 兒子現(xiàn)二年級(jí),周一至周五大學(xué)校,周六學(xué)屋,周末鋼琴畫(huà)畫(huà)練字。從周一至周日,一點(diǎn)空閑時(shí)間都沒(méi)有...
    香小廚閱讀 331評(píng)論 1 1
  • 這半個(gè)月的讀書(shū)計(jì)劃擱淺 洛麗塔看了100頁(yè),傅雷家書(shū)看了10頁(yè) 群山回唱還沒(méi)有看完,492頁(yè) 紅玫瑰與白玫瑰,50...
    簡(jiǎn)夕簡(jiǎn)西閱讀 249評(píng)論 1 2

友情鏈接更多精彩內(nèi)容