0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

介紹Node.js應(yīng)用全鏈路信息獲取的方法

OSC開(kāi)源社區(qū) ? 來(lái)源:vivo互聯(lián)網(wǎng)技術(shù) ? 2023-02-10 11:21 ? 次閱讀

全鏈路追蹤技術(shù)的兩個(gè)核心要素分別是 全鏈路信息獲取全鏈路信息存儲(chǔ)展示。

一、Node.js 應(yīng)用全鏈路追蹤系統(tǒng)

目前行業(yè)內(nèi), 不考慮 Serverless 的情況下,主流的 Node.js 架構(gòu)設(shè)計(jì)主要有以下兩種方案:

通用架構(gòu):只做 ssr 和 bff,不做服務(wù)器和微服務(wù);

全場(chǎng)景架構(gòu):包含 ssr、bff、服務(wù)器、微服務(wù)。

上述兩種方案對(duì)應(yīng)的架構(gòu)說(shuō)明圖如下圖所示:

f4fc0c64-a8d8-11ed-bfe3-dac502259ad0.png

在上述兩種通用架構(gòu)中,nodejs 都會(huì)面臨一個(gè)問(wèn)題,那就是:

在請(qǐng)求鏈路越來(lái)越長(zhǎng),調(diào)用服務(wù)越來(lái)越多,其中還包含各種微服務(wù)調(diào)用的情況下,出現(xiàn)了以下訴求:

如何在請(qǐng)求發(fā)生異常時(shí)快速定義問(wèn)題所在;

如何在請(qǐng)求響應(yīng)慢的時(shí)候快速找出慢的原因;

如何通過(guò)日志文件快速定位問(wèn)題的根本原因。

我們要解決上述訴求,就需要有一種技術(shù),將每個(gè)請(qǐng)求的關(guān)鍵信息聚合起來(lái),并且將所有請(qǐng)求鏈路串聯(lián)起來(lái)。讓我們可以知道一個(gè)請(qǐng)求中包含了幾次服務(wù)、微服務(wù)請(qǐng)求的調(diào)用,某次服務(wù)、微服務(wù)調(diào)用在哪個(gè)請(qǐng)求的上下文。

這種技術(shù),就是Node.js應(yīng)用全鏈路追蹤。它是 Node.js 在涉及到復(fù)雜服務(wù)端業(yè)務(wù)場(chǎng)景中,必不可少的技術(shù)保障。

綜上,我們需要Node.js應(yīng)用全鏈路追蹤,說(shuō)完為什么需要后,下面將介紹如何做Node.js應(yīng)用的全鏈路信息獲取。

二、全鏈路信息獲取

全鏈路信息獲取,是全鏈路追蹤技術(shù)中最重要的一環(huán)。只有打通了全鏈路信息獲取,才會(huì)有后續(xù)的存儲(chǔ)展示流程。

對(duì)于多線程語(yǔ)言如 JavaPython 來(lái)說(shuō),做全鏈路信息獲取有線程上下文如 ThreadLocal 這種利器相助。而對(duì)于Node.js來(lái)說(shuō),由于單線程和基于IO回調(diào)的方式來(lái)完成異步操作,所以在全鏈路信息獲取上存在天然獲取難度大的問(wèn)題。那么如何解決這個(gè)問(wèn)題呢?

三、業(yè)界方案

由于 Node.js 單線程,非阻塞 IO 的設(shè)計(jì)思想。在全鏈路信息獲取上,到目前為止,主要有以下 4 種方案:

domain: node api

zone.js: Angular 社區(qū)產(chǎn)物;

顯式傳遞:手動(dòng)傳遞、中間件掛載;

Async Hooks:node api;

而上述 4 個(gè)方案中, domain 由于存在嚴(yán)重的內(nèi)存泄漏,已經(jīng)被廢棄了;zone.js 實(shí)現(xiàn)方式非常暴力、API比較晦澀、最關(guān)鍵的缺點(diǎn)是 monkey patch 只能 mock api ,不能 mock language;顯式傳遞又過(guò)于繁瑣和具有侵入性;綜合比較下來(lái),效果最好的方案就是第四種方案,這種方案有如下優(yōu)點(diǎn):

node 8.x 新加的一個(gè)核心模塊,Node 官方維護(hù)者也在使用,不存在內(nèi)存泄漏;

非常適合實(shí)現(xiàn)隱式的鏈路跟蹤,入侵小,目前隱式跟蹤的最優(yōu)解;

提供了 API 來(lái)追蹤 node 中異步資源的生命周期;

借助 async_hook 實(shí)現(xiàn)上下文的關(guān)聯(lián)關(guān)系;

優(yōu)點(diǎn)說(shuō)完了,下面我們就來(lái)介紹如何通過(guò) Async Hooks 來(lái)獲取全鏈路信息。

四、Async Hooks【異步鉤子】

4.1 Async Hooks 概念

Async Hooks 是 Node.js v8.x 版本新增加的一個(gè)核心模塊,它提供了 API 用來(lái)追蹤 Node.js 中異步資源的生命周期,可幫助我們正確追蹤異步調(diào)用的處理邏輯及關(guān)系。在代碼中,只需要寫(xiě) import asyncHook from 'async_hooks' 即可引入 async_hooks 模塊。

一句話概括:async_hooks 用來(lái)追蹤 Node.js 中異步資源的生命周期。

目前 Node.js 的穩(wěn)定版本是 v14.17.0 。我們通過(guò)一張圖看下 Async Hooks 不同版本的 api 差異。如下圖所示:

f5352206-a8d8-11ed-bfe3-dac502259ad0.png

從圖中可以看到該 api 變動(dòng)較大。這是因?yàn)閺?8 版本到 14 版本,async_hooks 依舊還是 Stability: 1 - Experimental

Stability: 1 - Experimental :該特性仍處于開(kāi)發(fā)中,且未來(lái)改變時(shí)不做向后兼容,甚至可能被移除。不建議在生產(chǎn)環(huán)境中使用該特性。

但是沒(méi)關(guān)系,要相信官方團(tuán)隊(duì),這里我們的全鏈路信息獲取方案是基于 Node v9.x 版本 api 實(shí)現(xiàn)的。對(duì)于 Async Hooks api 介紹和基本使用, 大家可以閱讀官方文檔,下文會(huì)闡述對(duì)核心知識(shí)的理解。

下面我們將系統(tǒng)介紹基于 Async Hooks 的全鏈路信息獲取方案的設(shè)計(jì)和實(shí)現(xiàn),下文統(tǒng)稱(chēng)為 zone-context 。

4.2 理解 async_hooks 核心知識(shí)

在介紹 zone-context 之前,要對(duì) async_hooks 的核心知識(shí)有正確的理解,這里做了一個(gè)總結(jié),有如下6點(diǎn):

每一個(gè)函數(shù)(不論異步還是同步)都會(huì)提供一個(gè)上下文, 我們稱(chēng)之為 async scope ,這個(gè)認(rèn)知對(duì)理解 async_hooks 非常重要;

每一個(gè) async scope 中都有一個(gè) asyncId ,它是當(dāng)前 async scope 的標(biāo)志,同一個(gè)的 async scope 中 asyncId 必然相同,每個(gè)異步資源在創(chuàng)建時(shí), asyncId 自動(dòng)遞增,全局唯一;

每一個(gè) async scope 中都有一個(gè) triggerAsyncId ,用來(lái)表示當(dāng)前函數(shù)是由哪個(gè) async scope 觸發(fā)生成的;

通過(guò) asyncId 和 triggerAsyncId 我們可以追蹤整個(gè)異步的調(diào)用關(guān)系及鏈路,這個(gè)是全鏈路追蹤的核心;

通過(guò) async_hooks.createHook 函數(shù)來(lái)注冊(cè)關(guān)于每個(gè)異步資源在生命周期中發(fā)生的 init 等相關(guān)事件的監(jiān)聽(tīng)函數(shù);

同一個(gè) async scope 可能會(huì)被調(diào)用及執(zhí)行多次,不管執(zhí)行多少次,其 asyncId 必然相同,通過(guò)監(jiān)聽(tīng)函數(shù),我們很方便追蹤其執(zhí)行的次數(shù)、時(shí)間以及上下文關(guān)系。

上述6點(diǎn)知識(shí)對(duì)于理解 async_hooks 是非常重要的。正是因?yàn)檫@些特性,才使得 async_hooks 能夠優(yōu)秀的完成Node.js 應(yīng)用全鏈路信息獲取。

到這里,下面就要介紹 zone-context 的設(shè)計(jì)和實(shí)現(xiàn)了,請(qǐng)和我一起往下看。

五、zone-context

5.1 架構(gòu)設(shè)計(jì)

整體架構(gòu)設(shè)計(jì)如下圖所示:

f595ecee-a8d8-11ed-bfe3-dac502259ad0.png

核心邏輯如下:異步資源(調(diào)用)創(chuàng)建后,會(huì)被 async_hooks 監(jiān)聽(tīng)到。監(jiān)聽(tīng)到后,對(duì)獲取到的異步資源信息進(jìn)行處理加工,整合成需要的數(shù)據(jù)結(jié)構(gòu),整合后,將數(shù)據(jù)存儲(chǔ)到 invoke tree 中。在異步資源結(jié)束時(shí),觸發(fā) gc 操作,對(duì) invoke tree 中不再有用的數(shù)據(jù)進(jìn)行刪除回收。

從上述核心邏輯中,我們可以知道,此架構(gòu)設(shè)計(jì)需要實(shí)現(xiàn)以下三個(gè)功能:

異步資源(調(diào)用)監(jiān)聽(tīng)

invoke tree

gc

下面開(kāi)始逐個(gè)介紹上述三個(gè)功能的實(shí)現(xiàn)。

5.2 異步資源(調(diào)用)監(jiān)聽(tīng)

如何做到監(jiān)聽(tīng)異步調(diào)用呢?

這里用到了 async_hooks (追蹤 Node.js 異步資源的生命周期)代碼實(shí)現(xiàn)如下:

asyncHook
  .createHook({
    init(asyncId, type, triggerAsyncId) {
      // 異步資源創(chuàng)建(調(diào)用)時(shí)觸發(fā)該事件
    },
  })
  .enable()

是不是發(fā)現(xiàn)此功能實(shí)現(xiàn)非常簡(jiǎn)單,是的哦,就可以對(duì)所有異步操作進(jìn)行追蹤了。

在理解 async_hooks 核心知識(shí)中,我們提到了通過(guò) asyncId 和 triggerAsyncId 可以追蹤整個(gè)異步的調(diào)用關(guān)系及鏈路?,F(xiàn)在大家看 init 中的參數(shù),會(huì)發(fā)現(xiàn), asyncId 和triggerAsyncId 都存在,而且是隱式傳遞,不需要手動(dòng)傳入。這樣,我們?cè)诿看萎惒秸{(diào)用時(shí),都能在 init 事件中,拿到這兩個(gè)值。invoke tree 功能的實(shí)現(xiàn),離不開(kāi)這兩個(gè)參數(shù)。

介紹完異步調(diào)用監(jiān)聽(tīng),下面將介紹 invoke tree 的實(shí)現(xiàn)。

5.3 invoke tree 設(shè)計(jì)和異步調(diào)用監(jiān)聽(tīng)結(jié)合

5.3.1 設(shè)計(jì)

invoke tree 整體設(shè)計(jì)思路如下圖所示:

f5e821bc-a8d8-11ed-bfe3-dac502259ad0.png

具體代碼如下:

interface ITree {
  [key: string]: {
    // 調(diào)用鏈路上第一個(gè)異步資源asyncId
    rootId: number
    // 異步資源的triggerAsyncId
    pid: number
    // 異步資源中所包含的異步資源asyncId
    children: Array
  }
}
 
const invokeTree: ITree = {}

創(chuàng)建一個(gè)大的對(duì)象 invokeTree, 每一個(gè)屬性代表一個(gè)異步資源的完整調(diào)用鏈路。屬性的key和value代表含義如下:

屬性的 key 是代表這個(gè)異步資源的 asyncId。

屬性的 value 是代表這個(gè)異步資源經(jīng)過(guò)的所有鏈路信息聚合對(duì)象,該對(duì)象中的各屬性含義請(qǐng)看上面代碼中的注釋進(jìn)行理解。

通過(guò)這種設(shè)計(jì),就能拿到任何一個(gè)異步資源在整個(gè)請(qǐng)求鏈路中的關(guān)鍵信息。收集根節(jié)點(diǎn)上下文。

5.3.2和異步調(diào)用監(jiān)聽(tīng)結(jié)合

雖然 invoke tree 設(shè)計(jì)好了。但是如何在 異步調(diào)用監(jiān)聽(tīng)的 init 事件中,將 asyncId 、 triggerAsyncId 和 invokeTree 關(guān)聯(lián)起來(lái)呢?

代碼如下:

asyncHook
  .createHook({
    init(asyncId, type, triggerAsyncId) {
      // 尋找父節(jié)點(diǎn)
      const parent = invokeTree[triggerAsyncId]
      if (parent) {
        invokeTree[asyncId] = {
          pid: triggerAsyncId,
          rootId: parent.rootId,
          children: [],
        }
        // 將當(dāng)前節(jié)點(diǎn)asyncId值保存到父節(jié)點(diǎn)的children數(shù)組中
        invokeTree[triggerAsyncId].children.push(asyncId)
      }
    }
  })
  .enable()

大家看上面代碼,整個(gè)代碼大致有以下幾個(gè)步驟:

當(dāng)監(jiān)聽(tīng)到異步調(diào)用的時(shí)候,會(huì)先去 invokeTree 對(duì)象中查找是否含有 key 為 triggerAsyncId 的屬性;

有的話,說(shuō)明該異步調(diào)用在該追蹤鏈路中,則進(jìn)行存儲(chǔ)操作,將 asyncId 當(dāng)成 key , 屬性值是一個(gè)對(duì)象,包含三個(gè)屬性,分別是 pid、rootId、children , 具體含義上文已說(shuō)過(guò);

沒(méi)有的話,說(shuō)明該異步調(diào)用不在該追蹤鏈路中。則不進(jìn)行任何操作,如把數(shù)據(jù)存入 invokeTree 對(duì)象;

將當(dāng)前異步調(diào)用 asyncId 存入到 invokeTree 中 key 為 triggerAsyncId 的 children 屬性中。

至此,invoke tree 的設(shè)計(jì)、和異步調(diào)用監(jiān)聽(tīng)如何結(jié)合,已經(jīng)介紹完了。下面將介紹 gc 功能的設(shè)計(jì)和實(shí)現(xiàn)。

5.4gc

5.4.1 目的

我們知道,異步調(diào)用次數(shù)是非常多的,如果不做 gc 操作,那么 invoke tree 會(huì)越來(lái)越大,node應(yīng)用的內(nèi)存會(huì)被這些數(shù)據(jù)慢慢占滿,所以需要對(duì) invoke tree 進(jìn)行垃圾回收。

5.4.2設(shè)計(jì)

gc 的設(shè)計(jì)思想主要如下:當(dāng)異步資源結(jié)束的時(shí)候,觸發(fā)垃圾回收,尋找此異步資源觸發(fā)的所有異步資源,然后按照此邏輯遞歸查找,直到找出所有可回收的異步資源。

話不多說(shuō),直接上代碼, gc 代碼如下:

interface IRoot {
  [key: string]: Object
}
 
// 收集根節(jié)點(diǎn)上下文
const root: IRoot = {}
 
function gc(rootId: number) {
  if (!root[rootId]) {
    return
  }
 
  // 遞歸收集所有節(jié)點(diǎn)id
  const collectionAllNodeId = (rootId: number) => {
    const {children} = invokeTree[rootId]
    let allNodeId = [...children]
    for (let id of children) {
      // 去重
      allNodeId = [...allNodeId, ...collectionAllNodeId(id)]
    }
    return allNodeId
  }
 
  const allNodes = collectionAllNodeId(rootId)
 
  for (let id of allNodes) {
    delete invokeTree[id]
  }
 
  delete invokeTree[rootId]
  delete root[rootId]
}

gc 核心邏輯:用 collectionAllNodeId 遞歸查找所有可回收的異步資源( id )。然后再刪除 invokeTree 中以這些 id 為 key 的屬性。最后刪除根節(jié)點(diǎn)。

大家看到了聲明對(duì)象 root ,這個(gè)是什么呢?

root 其實(shí)是我們對(duì)某個(gè)異步調(diào)用進(jìn)行監(jiān)聽(tīng)時(shí),設(shè)置的一個(gè)根節(jié)點(diǎn)對(duì)象,這個(gè)節(jié)點(diǎn)對(duì)象可以手動(dòng)傳入一些鏈路信息,這樣可以為全鏈路追蹤增加其他追蹤信息,如錯(cuò)誤信息、耗時(shí)時(shí)間等。

5.5萬(wàn)事具備,只欠東風(fēng)

我們的異步事件監(jiān)聽(tīng)設(shè)計(jì)好了, invoke tree 設(shè)計(jì)好了,gc 也設(shè)計(jì)好了。那么如何將他們串聯(lián)起來(lái)呢?比如我們要監(jiān)聽(tīng)某一個(gè)異步資源,那么我們要怎樣才能把 invoke tree 和異步資源結(jié)合起來(lái)呢?

這里需要三個(gè)函數(shù)來(lái)完成結(jié)合,分別是 ZoneContext 、 setZoneContextgetZoneContext。下面來(lái)一一介紹下這三個(gè)函數(shù):

5.5.1 ZoneContext

這是一個(gè)工廠函數(shù),用來(lái)創(chuàng)建異步資源實(shí)例的,代碼如下所示:

// 工廠函數(shù)
async function ZoneContext(fn: Function) {
  // 初始化異步資源實(shí)例
  const asyncResource = new asyncHook.AsyncResource('ZoneContext')
  let rootId = -1
  return asyncResource.runInAsyncScope(async () => {
    try {
      rootId = asyncHook.executionAsyncId()
      // 保存 rootId 上下文
      root[rootId] = {}
      // 初始化 invokeTree
      invokeTree[rootId] = {
        pid: -1, // rootId 的 triggerAsyncId 默認(rèn)是 -1
        rootId,
        children: [],
      }
      // 執(zhí)行異步調(diào)用
      await fn()
    } finally {
      gc(rootId)
    }
  })
}

大家會(huì)發(fā)現(xiàn),在此函數(shù)中,有這樣一行代碼:

const asyncResource = new asyncHook.AsyncResource('ZoneContext')

這行代碼是什么含義呢?

它是指我們創(chuàng)建了一個(gè)名為 ZoneContext 的異步資源實(shí)例,可以通過(guò)該實(shí)例的屬性方法來(lái)更加精細(xì)的控制異步資源。

執(zhí)行 asyncResource.runInAsyncScope 方法有什么用處呢?

調(diào)用該實(shí)例的 runInAsyncScope方法,在runInAsyncScope 方法中包裹要傳入的異步調(diào)用??梢员WC在這個(gè)資源( fn )的異步作用域下,所執(zhí)行的代碼都是可追蹤到我們?cè)O(shè)置的 invokeTree 中,達(dá)到更加精細(xì)控制異步調(diào)用的目的。在執(zhí)行完后,進(jìn)行g(shù)c調(diào)用,完成內(nèi)存回收。

5.5.2setZoneContext

用來(lái)給異步調(diào)用設(shè)置額外的跟蹤信息。代碼如下:

function setZoneContext(obj: Object) {
  const curId = asyncHook.executionAsyncId()
  let root = findRootVal(curId)
  Object.assign(root, obj)
}

通過(guò) Object.assign(root, obj) 將傳入的 obj 賦值給 root 對(duì)象中, key 為 curId 的屬性。這樣就可以給我們想跟蹤的異步調(diào)用設(shè)置想要跟蹤的信息。

5.5.3 getZoneContext

用來(lái)拿到異步調(diào)的 rootId 的屬性值。代碼如下:

function findRootVal(asyncId: number) {
  const node = invokeTree[asyncId]
  return node ? root[node.rootId] : null
}
function getZoneContext() {
  const curId = asyncHook.executionAsyncId()
  return findRootVal(curId)
}

通過(guò)給 findRootVal 函數(shù)傳入 asyncId 來(lái)拿到 root 對(duì)象中 key 為 rootId 的屬性值。這樣就可以拿到當(dāng)初我們?cè)O(shè)置的想要跟蹤的信息了,完成一個(gè)閉環(huán)。

至此,我們將 Node.js應(yīng)用全鏈路信息獲取的核心設(shè)計(jì)和實(shí)現(xiàn)闡述完了。邏輯上有點(diǎn)抽象,需要多去思考和理解,才能對(duì)全鏈路追蹤信息獲取有一個(gè)更加深刻的掌握。

最后,我們使用本次全鏈路追蹤的設(shè)計(jì)實(shí)現(xiàn)來(lái)展示一個(gè)追蹤 demo 。

5.6 使用 zone-context

5.6.1 確定異步調(diào)用嵌套關(guān)系

為了更好的闡述異步調(diào)用嵌套關(guān)系,這里進(jìn)行了簡(jiǎn)化,沒(méi)有輸出 invoke tree 。例子代碼如下:

// 對(duì)異步調(diào)用A函數(shù)進(jìn)行追蹤
ZoneContext(async () => {
  await A()
})
 
// 異步調(diào)用A函數(shù)中執(zhí)行異步調(diào)用B函數(shù)
async function A() {
  // 輸出 A 函數(shù)的 asyncId
  fs.writeSync(1, `A 函數(shù)的 asyncId -> ${asyncHook.executionAsyncId()}
`)
  Promise.resolve().then(() => {
    // 輸出 A 函數(shù)中執(zhí)行異步調(diào)用時(shí)的 asyncId
    fs.writeSync(1, `A 執(zhí)行異步 promiseC 時(shí) asyncId 為 -> ${asyncHook.executionAsyncId()}
`)
    B()
  })
}
 
// 異步調(diào)用B函數(shù)中執(zhí)行異步調(diào)用C函數(shù)
async function B() {
  // 輸出 B 函數(shù)的 asyncId
  fs.writeSync(1, `B 函數(shù)的 asyncId -> ${asyncHook.executionAsyncId()}
`)
  Promise.resolve().then(() => {
    // 輸出 B 函數(shù)中執(zhí)行異步調(diào)用時(shí)的 asyncId
    fs.writeSync(1, `B 執(zhí)行異步 promiseC 時(shí) asyncId 為 -> ${asyncHook.executionAsyncId()}
`)
    C()
  })
}
 
// 異步調(diào)用C函數(shù)
function C() {
  const obj = getZoneContext()
  // 輸出 C 函數(shù)的 asyncId
  fs.writeSync(1, `C 函數(shù)的 asyncId -> ${asyncHook.executionAsyncId()}
`)
  Promise.resolve().then(() => {
    // 輸出 C 函數(shù)中執(zhí)行異步調(diào)用時(shí)的 asyncId
    fs.writeSync(1, `C 執(zhí)行異步 promiseC 時(shí) asyncId 為 -> ${asyncHook.executionAsyncId()}
`)
  })
}

輸出結(jié)果為:

A 函數(shù)的 asyncId -> 3
A 執(zhí)行異步 promiseA 時(shí) asyncId 為 -> 8
B 函數(shù)的 asyncId -> 8
B 執(zhí)行異步 promiseB 時(shí) asyncId 為 -> 13
C 函數(shù)的 asyncId -> 13
C 執(zhí)行異步 promiseC 時(shí) asyncId 為 -> 16

只看輸出結(jié)果就可以推出以下信息:

A 函數(shù)執(zhí)行異步調(diào)用后, asyncId 為 8 ,而 B 函數(shù)的 asyncId 是 8 ,這說(shuō)明, B 函數(shù)是被 A 函數(shù) 調(diào)用;

B 函數(shù)執(zhí)行異步調(diào)用后, asyncId 為 13 ,而 C 函數(shù)的 asyncId 是 13 ,這說(shuō)明, C 函數(shù)是被 B 函數(shù) 調(diào)用;

C 函數(shù)執(zhí)行異步調(diào)用后, asyncId 為 16 , 不再有其他函數(shù)的 asyncId 是 16 ,這說(shuō)明, C 函數(shù)中沒(méi)有調(diào)用其他函數(shù);

綜合上面三點(diǎn),可以知道,此鏈路的異步調(diào)用嵌套關(guān)系為:A —> B -> C;

至此,我們可以清晰快速的知道誰(shuí)被誰(shuí)調(diào)用,誰(shuí)又調(diào)用了誰(shuí)。

5.6.2 額外設(shè)置追蹤信息

在上面例子代碼的基礎(chǔ)下,增加以下代碼:

ZoneContext(async () => {
  const ctx = { msg: '全鏈路追蹤信息', code: 1 }
  setZoneContext(ctx)
  await A()
})
 
function A() {
  // 代碼同上個(gè)demo
}
 
function B() {
  // 代碼同上個(gè)demo
  D()
}
 
// 異步調(diào)用C函數(shù)
function C() {
  const obj = getZoneContext()
  Promise.resolve().then(() => {
    fs.writeSync(1, `getZoneContext in C -> ${JSON.stringify(obj)}
`)
  })
}
 
// 同步調(diào)用函數(shù)D
function D() {
  const obj = getZoneContext()
  fs.writeSync(1, `getZoneContext in D -> ${JSON.stringify(obj)}
`)
}

輸出以下內(nèi)容:

呈現(xiàn)代碼宏出錯(cuò):參數(shù)

'com.atlassian.confluence.ext.code.render.InvalidValueException'的值無(wú)效。

getZoneContext in D -> {"msg":"全鏈路追蹤信息","code":1}

getZoneContext in C-> {"msg":"全鏈路追蹤信息","code":1}

可以發(fā)現(xiàn), 執(zhí)行 A 函數(shù)前設(shè)置的追蹤信息后,調(diào)用 A 函數(shù), A 函數(shù)中調(diào)用 B 函數(shù), B 函數(shù)中調(diào)用 C 函數(shù)和 D 函數(shù)。在 C 函數(shù)和 D 函數(shù)中,都能訪問(wèn)到設(shè)置的追蹤信息。

這說(shuō)明,在定位分析嵌套的異步調(diào)用問(wèn)題時(shí),通過(guò) getZoneContext 拿到頂層設(shè)置的關(guān)鍵追蹤信息??梢院芸旎厮莩?,某個(gè)嵌套異步調(diào)用出現(xiàn)的異常,

是由頂層的某個(gè)異步調(diào)用異常所導(dǎo)致的。

5.6.3 追蹤信息大而全的 invoke tree

例子代碼如下:

ZoneContext(async () => {
  await A()
})
async function A() {
  Promise.resolve().then(() => {
    fs.writeSync(1, `A 函數(shù)執(zhí)行異步調(diào)用時(shí)的 invokeTree -> ${JSON.stringify(invokeTree)}
`)
    B()
  })
}
async function B() {
  Promise.resolve().then(() => {
    fs.writeSync(1, `B 函數(shù)執(zhí)行時(shí)的 invokeTree -> ${JSON.stringify(invokeTree)}
`)
  })
}

輸出結(jié)果如下:

A 函數(shù)執(zhí)行異步調(diào)用時(shí)的 invokeTree -> {"3":{"pid":-1,"rootId":3,"children":[5,6,7]},"5":{"pid":3,"rootId":3,"children":[10]},"6":{"pid":3,"rootId":3,"children":[9]},"7":{"pid":3,"rootId":3,"children":[8]},"8":{"pid":7,"rootId":3,"children":[]},"9":{"pid":6,"rootId":3,"children":[]},"10":{"pid":5,"rootId":3,"children":[]}}
 
B 函數(shù)執(zhí)行異步調(diào)用時(shí)的 invokeTree -> {"3":{"pid":-1,"rootId":3,"children":[5,6,7]},"5":{"pid":3,"rootId":3,"children":[10]},"6":{"pid":3,"rootId":3,"children":[9]},"7":{"pid":3,"rootId":3,"children":[8]},"8":{"pid":7,"rootId":3,"children":[11,12]},"9":{"pid":6,"rootId":3,"children":[]},"10":{"pid":5,"rootId":3,"children":[]},"11":{"pid":8,"rootId":3,"children":[]},"12":{"pid":8,"rootId":3,"children":[13]},"13":{"pid":12,"rootId":3,"children":[]}}

根據(jù)輸出結(jié)果可以推出以下信息:

1、此異步調(diào)用鏈路的 rootId (初始 asyncId ,也是頂層節(jié)點(diǎn)值) 是 3

2、函數(shù)執(zhí)行異步調(diào)用時(shí),其調(diào)用鏈路如下圖所示:

f5f9d916-a8d8-11ed-bfe3-dac502259ad0.png

3、函數(shù)執(zhí)行異步調(diào)用時(shí),其調(diào)用鏈路如下圖所示:

f61ee440-a8d8-11ed-bfe3-dac502259ad0.png

從調(diào)用鏈路圖就可以清晰看出所有異步調(diào)用之間的相互關(guān)系和順序。為異步調(diào)用的各種問(wèn)題排查和性能分析提供了強(qiáng)有力的技術(shù)支持。

六、總結(jié)

到這,關(guān)于Node.js 應(yīng)用全鏈路信息獲取的設(shè)計(jì)、實(shí)現(xiàn)和案例演示就介紹完了。全鏈路信息獲取是全鏈路追蹤系統(tǒng)中最重要的一環(huán),當(dāng)信息獲取搞定后,下一步就是全鏈路信息存儲(chǔ)展示。






審核編輯:劉清

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • SSR
    SSR
    +關(guān)注

    關(guān)注

    0

    文章

    73

    瀏覽量

    17709
  • JAVA語(yǔ)言
    +關(guān)注

    關(guān)注

    0

    文章

    138

    瀏覽量

    20063

原文標(biāo)題:Node.js應(yīng)用全鏈路追蹤技術(shù)——[全鏈路信息獲取]

文章出處:【微信號(hào):OSC開(kāi)源社區(qū),微信公眾號(hào):OSC開(kāi)源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    Node.js 給前端帶來(lái)了什么

    端分工方式。讓Node.js來(lái)改變這一切  Node.js一發(fā)布,立刻在前端工程師中引起了軒然大波,前端工程師們幾乎立刻對(duì)這一項(xiàng)技術(shù)表露出了相當(dāng)大的熱情和期待。上一次一種技術(shù)能被整個(gè)前端界如此關(guān)注那還
    發(fā)表于 05-06 14:23

    【Intel Edison試用體驗(yàn)】XDK篇:Node.js操作SQLite3

    首先得用opkg安裝sqlite3,接著需使用npm安裝sqlite3的接口文件,以保證node.js與sqlite3可以連接,運(yùn)行如下命令便可安裝: 接著就可以使用sqlite3了,新建一個(gè)
    發(fā)表于 07-25 09:39

    【orangepi zero試用體驗(yàn)】安裝Node.JS運(yùn)行環(huán)境與示例

    本帖最后由 eyecf 于 2016-12-24 17:51 編輯 本期要和大家介紹一下OrangePI zero安裝Node.js運(yùn)行環(huán)境的方法,并運(yùn)行一下vuejs的實(shí)例Node.j
    發(fā)表于 12-24 17:07

    基于Zetta(Node.js)的數(shù)據(jù)接收端server中,可接收數(shù)量不定傳感數(shù)據(jù)的IoT APP實(shí)現(xiàn)

    Zetta 是一個(gè)基于Node.js的IoT框架。上一篇 在基于Node.js的IoT框架Zetta中實(shí)現(xiàn)可變間隔發(fā)送數(shù)據(jù) 中對(duì)Zetta進(jìn)行了簡(jiǎn)要的介紹,以及給出了一個(gè)發(fā)送間隔可變數(shù)據(jù)的實(shí)現(xiàn)。這一
    發(fā)表于 08-09 17:57

    STM32MP157C-DK2有沒(méi)有辦法在不重建圖像的情況下安裝node.js?

    我想在 STM32MP157C-DK2 上使用 node.js。我發(fā)現(xiàn)這篇文章https://community.st.com/s/question/0D50X0000B0xovG
    發(fā)表于 02-07 09:00

    深入淺出Node.js迷你書(shū)

    [InfoQ]深入淺出Node.js迷你書(shū)
    發(fā)表于 11-04 15:50 ?0次下載

    node.jsjs要點(diǎn)總結(jié)

    Node.js是一個(gè)面向服務(wù)器的框架,立足于Chrome強(qiáng)大的V8 JS引擎。盡管它由C++編寫(xiě)而成,但是它及其應(yīng)用是運(yùn)行在JS上的。本文為開(kāi)發(fā)者總結(jié)了4個(gè)Node.js要點(diǎn)。 1.
    發(fā)表于 10-13 10:39 ?0次下載

    三大方面對(duì)比Go語(yǔ)言和Node.js 誰(shuí)更有優(yōu)勢(shì)

    Node.js與Go語(yǔ)言一直是互聯(lián)網(wǎng)大戰(zhàn)中的主戰(zhàn)場(chǎng),雖說(shuō)按照普通的各項(xiàng)指標(biāo)對(duì)比,那么這場(chǎng)戰(zhàn)爭(zhēng)可能在很長(zhǎng)時(shí)間內(nèi)都難分勝負(fù),但我們還是決定嘗試對(duì)這二者做一些研究,并力求做出更準(zhǔn)確的判斷。
    發(fā)表于 06-29 14:59 ?5131次閱讀

    node.js在訓(xùn)練好的神經(jīng)網(wǎng)絡(luò)模型識(shí)別圖像中物體的方法

    如何在Node.js環(huán)境下使用訓(xùn)練好的神經(jīng)網(wǎng)絡(luò)模型(Inception、SSD)識(shí)別圖像中的物體。
    的頭像 發(fā)表于 04-06 13:11 ?9093次閱讀

    第3部分:使用NoDE.JS的程序

    Implement MQTT to publish temperature data using Node.js*.
    的頭像 發(fā)表于 10-26 07:16 ?1753次閱讀

    Node.js 內(nèi)存泄漏問(wèn)題初探

    區(qū):專(zhuān)門(mén)用來(lái)存儲(chǔ)引用類(lèi)型的內(nèi)存區(qū)域,比如對(duì)象、字符串和閉包在 Node.js 中,我們可以通過(guò)調(diào)用process.memoryUsage() 方法來(lái)來(lái)查詢(xún)內(nèi)存使用情況。該函數(shù)返回值如下:memory
    的頭像 發(fā)表于 11-01 13:39 ?4703次閱讀

    Node.js的九大后端框架你都知道嗎

    Nest 是一個(gè)用于構(gòu)建高效,可擴(kuò)展的 Node.js 服務(wù)器端應(yīng)用程序的框架。
    發(fā)表于 04-26 17:40 ?3252次閱讀
    <b class='flag-5'>Node.js</b>的九大后端框架你都知道嗎

    Node.js包教不包會(huì)

    node-lessons.zip
    發(fā)表于 04-19 10:47 ?0次下載
    <b class='flag-5'>Node.js</b>包教不包會(huì)

    Node.js網(wǎng)頁(yè)控制的機(jī)器人小車(chē)

    電子發(fā)燒友網(wǎng)站提供《Node.js網(wǎng)頁(yè)控制的機(jī)器人小車(chē).zip》資料免費(fèi)下載
    發(fā)表于 02-08 16:06 ?0次下載
    <b class='flag-5'>Node.js</b>網(wǎng)頁(yè)控制的機(jī)器人小車(chē)

    node.js實(shí)戰(zhàn)源碼

    node.js實(shí)戰(zhàn)源碼
    發(fā)表于 05-16 18:06 ?1次下載