<menuitem id="vnnt5"><em id="vnnt5"><progress id="vnnt5"></progress></em></menuitem><meter id="vnnt5"></meter>

        <sub id="vnnt5"></sub> <progress id="vnnt5"><strike id="vnnt5"><noframes id="vnnt5">

        <p id="vnnt5"></p>

        <dfn id="vnnt5"></dfn>

          <meter id="vnnt5"></meter>

          當前位置:聯升科技 > 技術資訊 > 行業動態 >

          使用 Node.js 的 Async Hooks 模塊追蹤異步資源

          2021-01-26    作者:五月君編程    來源:Nodejs技術棧    閱讀:

          作者簡介:五月君,Software Designer,公眾號「Nodejs技術?!棺髡?。
          Async Hooks 功能是 Node.js v8.x 版本新增加的一個核心模塊,它提供了 API 用來追蹤 Node.js 程序中異步資源的聲明周期,可在多個異步調用之間共享數據,本文從最基本入門篇開始學習,之后會有在某些場景下具體應用實踐篇介紹。
          executionAsyncId 和 triggerAsyncId
          async hooks 模塊提供了 executionAsyncId() 函數標志當前執行上下文的異步資源 Id,下文使用 asyncId 表示。還有一個 triggerAsyncId() 函數來標志當前執行上下文被觸發的異步資源 Id,也就是當前異步資源是由哪個異步資源創建的。每個異步資源都會生成 asyncId,該 id 會呈遞增的方式生成,且在 Node.js 當前實例里全局唯一。
          const asyncHooks = require('async_hooks'); 
          const fs = require('fs'); 
          const asyncId = () => asyncHooks.executionAsyncId(); 
          const triggerAsyncId = () => asyncHooks.triggerAsyncId(); 
           
          console.log(`Global asyncId: ${asyncHooks.executionAsyncId()}, Global triggerAsyncId: ${triggerAsyncId()}`); 
           
          fs.open('hello.txt', (err, res) => { 
            console.log(`fs.open asyncId: ${asyncId()}, fs.open triggerAsyncId: ${triggerAsyncId()}`); 
          }); 
          下面是我們運行的結果,全局的 asyncId 為 1,fs.open 回調里打印的 triggerAsyncId 為 1 由全局觸發。
          Global asyncId: 1, Global triggerAsyncId: 0 
          fs.open asyncId: 5, fs.open triggerAsyncId: 1 
          默認未開啟的 Promise 執行跟蹤
          默認情況下,由于 V8 提供的 promise introspection API 相對消耗性能,Promise 的執行沒有分配 asyncId。這意味著默認情況下,使用了 Promise 或 Async/Await 的程序將不能正確的執行和觸發 Promise 回調上下文的 ID。即得不到當前異步資源 asyncId 也得不到當前異步資源是由哪個異步資源創建的 triggerAsyncId,如下所示:
          Promise.resolve().then(() => { 
            // Promise asyncId: 0. Promise triggerAsyncId: 0 
            console.log(`Promise asyncId: ${asyncId()}. Promise triggerAsyncId: ${triggerAsyncId()}`); 
          }) 
          通過 asyncHooks.createHook 創建一個 hooks 對象啟用 Promise 異步跟蹤。
          const hooks = asyncHooks.createHook({}); 
          hooks.enable(); 
           
          Promise.resolve().then(() => { 
            // Promise asyncId: 7. Promise triggerAsyncId: 6 
            console.log(`Promise asyncId: ${asyncId()}. Promise triggerAsyncId: ${triggerAsyncId()}`); 
          }) 
          異步資源的生命周期
          asyncHooks 的 createHook() 方法返回一個用于啟用(enable)和禁用(disable)hooks 的實例,該方法接收 init/before/after/destory 四個回調來標志一個異步資源從初始化、回調調用之前、回調調用之后、銷毀整個生命周期過程。
          init(初始化)
          當構造一個可能發出異步事件的類時調用。
          async:異步資源唯一 id
          type:異步資源類型,對應于資源的構造函數名稱,更多類型參考 async_hooks_type
          triggerAsyncId:當前異步資源由哪個異步資源創建的異步資源 id
          resource:初始化的異步資源
          /** 
           * Called when a class is constructed that has the possibility to emit an asynchronous event. 
           * @param asyncId a unique ID for the async resource 
           * @param type the type of the async resource 
           * @param triggerAsyncId the unique ID of the async resource in whose execution context this async resource was created 
           * @param resource reference to the resource representing the async operation, needs to be released during destroy 
           */ 
          init?(asyncId: number, type: string, triggerAsyncId: number, resource: object): void; 
          before(回調函數調用前)
          當啟動異步操作(例如 TCP 服務器接收新鏈接)或完成異步操作(例如將數據寫入磁盤)時,系統將調用回調來通知用戶,也就是我們寫的業務回調函數。在這之前會先觸發 before 回調。
          /** 
           * When an asynchronous operation is initiated or completes a callback is called to notify the user. 
           * The before callback is called just before said callback is executed. 
           * @param asyncId the unique identifier assigned to the resource about to execute the callback. 
           */ 
          before?(asyncId: number): void; 
          after(回調函數調用后)
          當回調處理完成之后觸發 after 回調,如果回調出現未捕獲異常,則在觸發 uncaughtException 事件或域(domain)處理之后觸發 after 回調。
          /** 
           * Called immediately after the callback specified in before is completed. 
           * @param asyncId the unique identifier assigned to the resource which has executed the callback. 
           */ 
          after?(asyncId: number): void; 
          destory(銷毀)
          當 asyncId 對應的異步資源被銷毀后調用 destroy 回調。一些資源的銷毀依賴于垃圾回收,因此如果對傳遞給 init 回調的資源對象有引用,則有可能永遠不會調用 destory 從而導致應用程序中出現內存泄漏。如果資源不依賴垃圾回收,這將不會有問題。
          /** 
           * Called after the resource corresponding to asyncId is destroyed 
           * @param asyncId a unique ID for the async resource 
           */ 
          destroy?(asyncId: number): void; 
          promiseResolve
          當傳遞給 Promise 構造函數的 resolve() 函數執行時觸發 promiseResolve 回調。
          /** 
            * Called when a promise has resolve() called. This may not be in the same execution id 
            * as the promise itself. 
            * @param asyncId the unique id for the promise that was resolve()d. 
            */ 
          promiseResolve?(asyncId: number): void; 
          以下代碼會觸發兩次 promiseResolve() 回調,第一次是我們直接調用的 resolve() 函數,第二次是在 .then() 里雖然我們沒有顯示的調用,但是它也會返回一個 Promise 所以還會被再次調用。
          const hooks = asyncHooks.createHook({ 
            promiseResolve(asyncId) { 
              syncLog('promiseResolve: ', asyncId); 
            } 
          }); 
          new Promise((resolve) => resolve(true)).then((a) => {}); 
           
          // 輸出結果 
          promiseResolve:  2 
          promiseResolve:  3 
          注意 init 回調里寫日志造成 “棧溢出” 問題
          一個異步資源的生命周期中第一個階段 init 回調是當構造一個可能發出異步事件的類時會調用,要注意由于使用 console.log() 輸出日志到控制臺是一個異步操作,在 AsyncHooks 回調函數中使用類似的異步操作將會再次觸發 init 回調函數,進而導致無限遞歸出現 RangeError: Maximum call stack size exceeded 錯誤,也就是 “ 棧溢出”。
          調試時,一個簡單的記錄日志的方式是使用 fs.writeFileSync() 以同步的方式寫入日志,這將不會觸發 AsyncHooks 的 init 回調函數。
          const syncLog = (...args) => fs.writeFileSync('log.txt', `${util.format(...args)}\n`, { flag: 'a' }); 
          const hooks = asyncHooks.createHook({ 
            init(asyncId, type, triggerAsyncId, resource) { 
              syncLog('init: ', asyncId, type, triggerAsyncId) 
            } 
          }); 
          hooks.enable(); 
           
          fs.open('hello.txt', (err, res) => { 
            syncLog(`fs.open asyncId: ${asyncId()}, fs.open triggerAsyncId: ${triggerAsyncId()}`); 
          }); 
          輸出以下內容,init 回調只會被調用一次,因為 fs.writeFileSync 是同步的是不會觸發 hooks 回調的。
          init:  2 FSREQCALLBACK 1 
          fs.open asyncId: 2, fs.open triggerAsyncId: 1 
          異步之間共享上下文
          Node.js v13.10.0 增加了 async_hooks 模塊的 AsyncLocalStorage 類,可用于在一系列異步調用中共享數據。
          如下例所示,asyncLocalStorage.run() 函數第一個參數是存儲我們在異步調用中所需要訪問的共享數據,第二個參數是一個異步函數,我們在 setTimeout() 的回調函數里又調用了 test2 函數,這一系列的異步操作都不影響我們在需要的地方去獲取 asyncLocalStorage.run() 函數中存儲的共享數據。
          const { AsyncLocalStorage } = require('async_hooks'); 
          const asyncLocalStorage = new AsyncLocalStorage(); 
          asyncLocalStorage.run({ traceId: 1 }, test1); 
          async function test1() { 
            setTimeout(() => test2(), 2000); 
          async function test2() { 
            console.log(asyncLocalStorage.getStore().traceId); 
          AsyncLocalStorage 用途很多,例如在服務端必不可少的日志分析,一個 HTTP 從請求到響應整個系統交互的日志輸出如果能通過一個 traceId 來關聯,在分析日志時也就能夠清晰的看到整個調用鏈路。
          下面是一個 HTTP 請求的簡單示例,模擬了異步處理,并且在日志輸出時去追蹤存儲的 id
          const http = require('http'); 
          const { AsyncLocalStorage } = require('async_hooks'); 
          const asyncLocalStorage = new AsyncLocalStorage(); 
          function logWithId(msg) { 
            const id = asyncLocalStorage.getStore(); 
            console.log(`${id !== undefined ? id : '-'}:`, msg); 
          let idSeq = 0; 
          http.createServer((req, res) => { 
            asyncLocalStorage.run(idSeq++, () => { 
              logWithId('start'); 
              setImmediate(() => { 
                logWithId('processing...'); 
                setTimeout(() => { 
                  logWithId('finish'); 
                  res.end(); 
                }, 2000) 
              }); 
            }); 
          }).listen(8080); 
          下面是運行結果,我在第一次調用之后直接調用了第二次,可以看到我們存儲的 id 信息與我們的日志一起成功的打印了出來。
          image.png
          在下一節會詳細介紹, 如何在 Node.js 中使用 async hooks 模塊的 AsyncLocalStorage 類處理請求上下文, 也會詳細講解 AsyncLocalStorage 類是如何實現的本地存儲。


          相關文章

          我們很樂意傾聽您的聲音!
          即刻與我們取得聯絡
          成為日后肩并肩合作的伙伴。

          行業資訊

          聯系我們

          13387904606

          地址:新余市仙女湖區仙女湖大道萬商紅A2棟

          手機:13755589003
          QQ:122322500
          微信號:13755589003

          江西新余網站設計_小程序制作_OA系統開發_企業ERP管理系統_app開發-新余聯升網絡科技有限公司 贛ICP備19013599號-1   贛公網安備 36050202000267號   

          微信二維碼
          午夜激情在线观看

                <menuitem id="vnnt5"><em id="vnnt5"><progress id="vnnt5"></progress></em></menuitem><meter id="vnnt5"></meter>

                <sub id="vnnt5"></sub> <progress id="vnnt5"><strike id="vnnt5"><noframes id="vnnt5">

                <p id="vnnt5"></p>

                <dfn id="vnnt5"></dfn>

                  <meter id="vnnt5"></meter>