node 跟踪异步资源(回调)async_hook、AsyncResource

跟踪异步回调的原因

  • node 基于事件循环的异步非阻塞 I/O 模型,发起一次异步调用,回调在之后的循环中才被调用,此时已经无法追踪到是谁发起了这个异步调用
    • 比如下面谁的 callback 先调用、以及 callback 属实谁的回调?无法从日志中确认调用链
const fs = require('fs')

function callback(err, data) {
    console.log('callback', data)
}

// 无改文件
fs.readFile("a.txt", callback)
console.log('after a')
// 无改文件
fs.readFile("b.txt", callback)
console.log('after b')
// after a
// after b
// callback undefined
// callback undefined

异步调用链基础

  • async scope:每一个函数(不论异步还是同步)都会提供一个上下文, 异步称之为 async scope
  • asyncId:每一个 async scope 中都有一个 asyncId ,它是当前 async scope 的标志,同一个的 async scope 中 asyncId 必然相同,每个异步资源在创建时, asyncId 自动递增,全局唯一
    • 同一个 async scope 可能会被调用及执行多次,不管执行多少次,其 asyncId 必然相同,通过监听函数,我们很方便追踪其执行的次数、时间以及上下文关系
  • triggerAsyncId:每一个 async scope 中都有一个 triggerAsyncId ,用来表示当前函数是由哪个 async scope 触发生成的
  • 通过 asyncId 和 triggerAsyncId 就可以追踪整个异步的调用关系及链路,这个是全链路追踪的核心

async_hook

文档

  • async_hook 提供了一系列钩子用于监听当前执行环境中的所有类型异步回调
  • type:触发异步回调的类型,自定义类型通过 AsyncResource 创建
FSEVENTWRAP, FSREQCALLBACK, GETADDRINFOREQWRAP, GETNAMEINFOREQWRAP, HTTPINCOMINGMESSAGE,

HTTPCLIENTREQUEST, JSSTREAM, PIPECONNECTWRAP, PIPEWRAP, PROCESSWRAP, QUERYWRAP,

SHUTDOWNWRAP, SIGNALWRAP, STATWATCHER, TCPCONNECTWRAP, TCPSERVERWRAP, TCPWRAP,

TTYWRAP, UDPSENDWRAP, UDPWRAP, WRITEWRAP, ZLIB, SSLCONNECTION, PBKDF2REQUEST,

RANDOMBYTESREQUEST, TLSWRAP, Microtask, Timeout, Immediate, TickObject

示例:

// 监听 fs.readFile 异步回调

const fs = require('fs')
const async_hooks = require('async_hooks');
const { fd } = process.stdout;

let indent = 0;
async_hooks.createHook({
  init(asyncId, type, triggerAsyncId) {
    const eid = async_hooks.executionAsyncId();
    const indentStr = ' '.repeat(indent);
    fs.writeSync(
      fd,
      `${indentStr}${type}(${asyncId}):` +
      ` trigger: ${triggerAsyncId} execution: ${eid} \n`);
  },
  before(asyncId) {
    const indentStr = ' '.repeat(indent);
    fs.writeSync(fd, `${indentStr}before:  ${asyncId}\n`);
    indent += 2;
  },
  after(asyncId) {
    indent -= 2;
    const indentStr = ' '.repeat(indent);
    fs.writeSync(fd, `${indentStr}after:  ${asyncId}\n`);
  },
  destroy(asyncId) {
    const indentStr = ' '.repeat(indent);
    fs.writeSync(fd, `${indentStr}destroy:  ${asyncId}\n`);
  },
}).enable();

function callback(err, data) {
    console.log('callback', data)
}

fs.readFile("a.txt", callback)
console.log('after a')
fs.readFile("b.txt", callback)
console.log('after b')

// 打印结果
FSREQCALLBACK(4): trigger: 1 execution: 1      # a
after a
TickObject(5): trigger: 1 execution: 1
FSREQCALLBACK(6): trigger: 1 execution: 1      # b
after b
before:  5
after:  5
before:  4
callback undefined
  TickObject(7): trigger: 4 execution: 4       # trigger by a
after:  4
before:  7
after:  7
before:  6
callback undefined
  TickObject(8): trigger: 6 execution: 6       # trigger by b
after:  6
before:  8
after:  8
destroy:  5
destroy:  7
destroy:  4
destroy:  8
destroy:  6

AsyncResource

文档

用于监听自定义异步回调,自定义时一般通过继承 AsyncResource 实现自己的逻辑

  • asyncResource.runInAsyncScope

    • 当配置好 async_hook后 ,new AsyncResource 会触发 init 钩子
    • 通过 runInAsyncScope(fn) 执行异步回调会触发 before-> fn -> after 钩子
    • 通过在 runInAsyncScope 方法中包裹要传入的异步调用。可以保证这个资源( fn )的异步行为是可追踪的

追踪自定义异步回调示例:

const async_hooks = require("async_hooks");

const fs = require("fs");
const { fd } = process.stdout;

let indent = 0;

// 配置好 hook
async_hooks
  .createHook({
    init(asyncId, type, triggerAsyncId) {
      const eid = async_hooks.executionAsyncId();
      const indentStr = " ".repeat(indent);
      fs.writeSync(
        fd,
        `${indentStr}${type}(${asyncId}):` +
          ` trigger: ${triggerAsyncId} execution: ${eid} \n`
      );
    },
    before(asyncId) {
      const indentStr = " ".repeat(indent);
      fs.writeSync(fd, `${indentStr}before:  ${asyncId}\n`);
      indent += 2;
    },
    after(asyncId) {
      indent -= 2;
      const indentStr = " ".repeat(indent);
      fs.writeSync(fd, `${indentStr}after:  ${asyncId}\n`);
    },
    destroy(asyncId) {
      const indentStr = " ".repeat(indent);
      fs.writeSync(fd, `${indentStr}destroy:  ${asyncId}\n`);
    },
  })
  .enable();

function callback(err, data) {
  console.log("callback", data);
}

// 继承 AsyncResource 实现自定义逻辑
class MyResource extends async_hooks.AsyncResource {
  constructor() {
    super("my-resource");
  }

  close() {
    this.emitDestroy();
  }
}

function p() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, 1000);
  });
}

let resource = new MyResource();
// 追踪自定义异步回调
resource.runInAsyncScope(async () => {
  console.log("hello");
  await p();
});

resource.close();

// 打印结果
my-resource(4): trigger: 1 execution: 1 
before:  4
  PROMISE(5): trigger: 4 execution: 4 
hello
  TickObject(6): trigger: 4 execution: 4 
  PROMISE(7): trigger: 4 execution: 4 
  Timeout(8): trigger: 4 execution: 4 
  PROMISE(9): trigger: 7 execution: 4 
after:  4
before:  6
after:  6
destroy:  4
destroy:  6
before:  8
after:  8
before:  9
after:  9
destroy:  8
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值