在 Node 中通过 Async Hooks 实现请求作用域

在日常的 Web 服务开发中,我们时常会遇到需要实现请求作用域的场景。

请求作用域在此指的是:该作用域的生命周期是会话级别的,在每一次会话过程中我们都会创建一个新的请求作用域,并在会话结束后进行销毁,且每个会话创建的请求作用域是相互隔离的。

应用场景

记录请求链路信息是请求作用域的一个经典场景,我们需要在请求开始前,为这个请求生成一条链路 ID。并将该次请求中访问的所有服务的请求信息、耗时、返回数据等数据附加在该链路上,最终生成一条完整的调用链路。

通过这种方式,我们得以将零散系统的调用信息实现了聚合。从而即使是在内部调用链路非常复杂的情况下,我们可以根据请求链路来快速的分析问题并进行定位。

如下图所示,我们可以方便的查看整体的请求调用链,也可以针对错误请求进行快速排错。

图源:打造立体化监控体系的最佳实践(https://cn.aliyun.com/aliware/news/monitoringsolution)

在 Node.js 中的实现

基于上述的场景,我们不难发现其实整体链路调用信息的生成,实际上是围绕着 TraceId 去做的。

这个 TraceId 在记录时有着如下的需求:

  • 全局可访问,在访问不同的服务之前都需要先获取到当前的 TraceId

  • TraceId 的生命周期为请求作用域级别,同一请求中获取到的 TraceId 是唯一的,不同请求的 TraceId 不一样

而这类需求在 Node.js 中实际上有着以下几种实现方式。

  • 手动传递:将 TraceId 作为函数的参数

  • 中间件挂载:利用 Midway/Koa 等 Web 框架提供的中间件能力,将 TraceId 挂载在请求 Context 上,每次调用前手动从 Context 获取

以上这两种方式实际上都可以实现,但也有着各自的缺陷。

  • 手动挂载:需要手动传递参数,较为繁琐

  • 中间件挂载:强依赖于 Web 框架提供的能力

因此在这儿,我们推荐使用一种全新的方式去实现请求作用域。那就是 Async Hooks。

Async Hooks

Async Hooks 是 Node.js 在 8.x 提供的原生模块,是为了用来追踪 Node.js 中异步资源的生命周期。在使用时,你可以根据给定的 Api,来创建一组 Hooks。

创建时的类型定义

其中参数解释如下:

  • asyncId:当前异步资源 Id,在生成时会自动递增,全局唯一。

  • type: 异步资源类型,例如:FSEVENTWRAP/FSREQCALLBACK/Timeout

  • triggerAsyncId:代表当前的异步资源 Id 是由哪个异步资源创建的

  • resource: 创建的具体资源

在这其中,asyncId 代表当前异步资源 Id,triggerAsyncId 代表创建该资源的异步资源 Id。

且 Async Hooks 为了调用便利,提供了直接的 Api 供开发者获取当前的 asyncId 与 triggerAsyncId。

使用 Demo

在使用时,我们只需要创建该 Hooks 并启用,即可开始监听异步事件。

如下图的 Demo 所示:

最终输出结果是:

不难看出,因为是在全局创建的 fs.open 事件,fs.open.triggerAsyncId 正是 global.asyncId。

实现请求作用域

基于以上的 Api 与 Demo,我们不难发现,Async Hooks 给我们提供了两个关键信息:

  • asyncId:自动递增,全局唯一

  • triggerAsyncId:代表当前的异步资源 Id 是由哪个异步资源 Id 创建的

根据  triggerAsyncId 的特性,我们可以知道轻松的推断出调用链。且由于 asyncId 是唯一的,所以即使是针对于同一个函数的调用,asyncId 也不同。

所以基于以上的推论,在 Async Hooks 中,我们在每一次异步调用过程中,都会生成一条独一无二的调用链。而这也是实现请求作用域的诀窍。

因此对于具体实现而言,我们需要做到如下几点:

  • 创建请求作用域,并往请求作用域存入我们需要数据

  • 函数在调用时可以访问到该数据

  • 该数据的生命周期为会话级别,不同会话之间互不干扰

关于代码侧的具体实现,实际上利用好 asyncId 与 triggerAsync 的特性,是不难做到的。

具体实现

以下是具体实现:

运行后的输出结果为:

通过这种方式,我们充分的利用了 Async Hooks 的特性,实现了我们自己的请求作用域。且在实际开发中使用起来也是非常简单的。

AsyncLocalStorage

在上面的 Demo 中,我采用了 AsyncLocalStorage 作为名称。
这个名称实际上是因为 Node.js 最近在 Async Hooks 上落地了一个 feature,名称就叫  AsyncLocalStorage。作用也是我们提到的实现异步请求作用作用域。

关于这部分,有兴趣的同学可以根据下方的参考资料,自行查阅 Node.js 官方文档(版本 V13.11.0)。

参考资料

本文参考资料如下:

  • Async Hooks 官方文档:https://nodejs.org/api/async_hooks.html

  • async-hooks: introduce async-storage API: https://github.com/nodejs/node/pull/26540

  • node-request-context:https://github.com/guyguyon/node-request-context

  • 学习使用 Node.js 中 async-hooks 模块:https://zhuanlan.zhihu.com/p/53036228

  • 打造立体化监控体系的最佳实践:https://cn.aliyun.com/aliware/news/monitoringsolution

❤️爱心三连击

1.看到这里了就点个在看支持下吧,你的在看是我创作的动力。

2.关注公众号程序员成长指北「每天为您分享原创或精选文章」

3.特殊阶段,带好口罩,做好个人防护。

4.添加微信【ikoala520】,拉你进技术交流群一起学习。

“在看转发”是最大的支持

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值