12

Node.js AsyncHooks 与异步回调上下文

 3 years ago
source link: https://zhuanlan.zhihu.com/p/263529297
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Node.js AsyncHooks 与异步回调上下文

我们都知道,Nodejs 最显著特点是单进程、异步、事件驱动。每当我们的代码碰到异步调用时,需要传入一个回调函数,等待异步调用结束时再被执行。一个典型的处理用户登录流程如下:

v2-e71d67f808f349aa84aa252d44b0d772_720w.jpg
图1注:图中数字标号表示事件发生时间顺序

但是,真实的线上环境,往往多个用户同时在登录,真实的场景如下:

v2-d74cca076da24615cee3f6627e05918a_720w.jpg
图2

上图假设数据库操作最为耗时,当用户 A 请求到达后,在等待数据库查询比对用户密码时,用户 B 发起了登录请求。让我们稍微修改下上图,加上时间轴:

图3

假设用户的一次请求作为一个独立上下文,那么上图一共发生了三次上下文的切换:

  • 第一次:事件 2 和事件 3,从用户 A 切换到了用户 B
  • 第二次:事件 4 和事件 5,从用户 B 切换到了用户 A
  • 第三次:事件 5 和事件 6,从用户 A 再次切换到用户 B

在类似 Express、Egg 等 Web 框架,是如何帮助我们识别上下文切换的?

  • Express 所有的请求都封装为一个 Request 对象,作为回调函数的第一个参数
  • Egg 则是封装了一个 Context 对象,每次请求都是新的 Context。

这些方式的特点,都是先显式创建一个局部变量,后续的业务逻辑代码,都会在函数调用中层层传递这个变量,便于识别上下文信息。那有没有办法,隐式的传递上下文信息?

隐式传递上下文

结合前面的案例,在 Node.js 中同步代码不会导致用户上下文切换,只有当发生异步回调时,才有可能发生请求上下文的切换。所以要想隐式传递上下文,首先要做的就是能自动识别出异步回调。Node.js 8.1 版本开始支持监听异步调用:AsyncHooks API。

AsyncHooks

AsyncHooks 核心提供了四个钩子:init、before、after、destory

  • init 每次异步调用都会触发,执行时间点是异步请求的资源准备完毕时。
  • before 执行异步回调函数前调用
  • after 执行异步回调函数后调用
  • destory 异步调用关联的资源被销毁时调用

补充两个例子说明:

  • fs.open 打开文件操作,执行时间点为所请求的文件资源准备完毕时调用
  • net.createServer init 会在端口监听成功时执行,而 before 则会在每次有新请求,触发 createServer 中的回调函数执行前,调用

四个钩子覆盖了一次异步调用的整个生命周期。除此之外还提供了两个关键 ID:triggerAsyncIdasyncId 。这两个 ID 可以表达两次异步调用之间的"父子"关系。

追踪上下文

结合 AsyncHooks,将上文的案例,改成 triggerAsyncId 和 asyncId 的关系图,如下:

每次异步调用,其 triggerAsyncId 和 asyncId 值类似如下(按上图事件顺序):

  • (1) 用户 A 发起 Http Request , triggerAsyncId: 0, asyncId 1
    • (2) 用户 A 发起 Database Query,triggerAsyncId: 1, asyncId 2
      • (5) 用户 A 发起 Write Session,triggerAsyncId 2, asyncId 5
  • (3) 用户 B 发起 Http Request,triggerAsyncId: 0, asyncId 3
    • (4) 用户 B 发起 Database Query, triggerAsyncId 3, asyncId 4
      • (6) 用户 B 发起 Write Sessiony, triggerAsyncId 4, asyncId 6

注:此处对系统发生真实异步调用进行了简化

通过 Hooks 以及 triggerAsyncId 和 asyncId 的关系,我们就可以找回每次异步调用发生时,该调用所属的上下文。相关的完整实现,可以参考 cls-hooked。

为什么没有普遍应用?

佛瑞德·布鲁克斯早就告诉我们软件工程没有银弹,之所以这种方式没有普及,个人理解主要以下几点:

  1. 隐式传递,代码可读性更差,不利于维护
  2. AsyncHooks 会有比较大的性能损耗,详见async-hooks-performance-impact,目前 API 稳定性还处于试验阶段

本文仅是从一个简单场景:异步上下文出发,引出了 AsyncHook 相关 API 的功能与基本使用。实际应用中,很多 APM 类程序都使用类似的能力。本文并没有对相关实现做深入讨论,希望通过此文,先介绍相关概念。

欢迎关注我们知乎账号,后续做更多深入分享。

本文作者所属团队:阿里淘系营销活动前端团队,主导集团超大型营销活动双11全球狂欢节,负责淘系电商每年几千场营销活动以及支撑这个体系的搭建技术产品,服务千级运营,万级商家,亿级消费者,提供如丝般顺滑的线上购物浏览体验。
欢迎有志之士加入,简历投递 [email protected] 注明【简历】

——————————————————————————————————————————

本账号主体为阿里巴巴淘系技术,淘系技术部是阿里巴巴新零售技术的王牌军,支撑淘宝、天猫核心电商以及淘宝直播、闲鱼、躺平、阿里汽车、阿里房产等创新业务,服务9亿用户,赋能各行业1000万商家。我们打造了全球领先的线上新零售技术平台,并作为核心技术团队保障了11次双十一购物狂欢节的成功。我们的愿景是致力于成为全球最懂商业的技术创新团队,让科技引领面向未来的商业创新和进步。

入驻知乎,将会给大家带来超多干货分享,立体化输出我们对于技术和商业的思考与见解。
详情介绍可以看这里 阿里巴巴淘系技术介绍

欢迎收藏点赞关注我们!共同进步~ :)

更多技术干货可关注【淘系技术】公众号


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK