在 Node 中通过 Async Hooks 实现请求作用域
共 2933字,需浏览 6分钟
·
2021-08-10 06:22
点击上方 程序员成长指北,关注公众号
回复1,加入高级Node交流群
作者:繁易
原文地址:https://mp.weixin.qq.com/s/I22TvmTqCKFClsp0YLDoZw
在日常的 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
“分享、点赞、在看” 支持一波