

Node.js AsyncHooks 与异步回调上下文
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.

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

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

上图假设数据库操作最为耗时,当用户 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:triggerAsyncId
和 asyncId
。这两个 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
- (2) 用户 A 发起 Database Query,triggerAsyncId: 1, asyncId 2
- (3) 用户 B 发起 Http Request,triggerAsyncId: 0, asyncId 3
- (4) 用户 B 发起 Database Query, triggerAsyncId 3, asyncId 4
- (6) 用户 B 发起 Write Sessiony, triggerAsyncId 4, asyncId 6
- (4) 用户 B 发起 Database Query, triggerAsyncId 3, asyncId 4
注:此处对系统发生真实异步调用进行了简化
通过 Hooks 以及 triggerAsyncId 和 asyncId 的关系,我们就可以找回每次异步调用发生时,该调用所属的上下文。相关的完整实现,可以参考 cls-hooked。
为什么没有普遍应用?
佛瑞德·布鲁克斯早就告诉我们软件工程没有银弹,之所以这种方式没有普及,个人理解主要以下几点:
- 隐式传递,代码可读性更差,不利于维护
- AsyncHooks 会有比较大的性能损耗,详见async-hooks-performance-impact,目前 API 稳定性还处于试验阶段
本文仅是从一个简单场景:异步上下文出发,引出了 AsyncHook 相关 API 的功能与基本使用。实际应用中,很多 APM 类程序都使用类似的能力。本文并没有对相关实现做深入讨论,希望通过此文,先介绍相关概念。
欢迎关注我们知乎账号,后续做更多深入分享。
本文作者所属团队:阿里淘系营销活动前端团队,主导集团超大型营销活动双11全球狂欢节,负责淘系电商每年几千场营销活动以及支撑这个体系的搭建技术产品,服务千级运营,万级商家,亿级消费者,提供如丝般顺滑的线上购物浏览体验。
欢迎有志之士加入,简历投递 [email protected] 注明【简历】
——————————————————————————————————————————
本账号主体为阿里巴巴淘系技术,淘系技术部是阿里巴巴新零售技术的王牌军,支撑淘宝、天猫核心电商以及淘宝直播、闲鱼、躺平、阿里汽车、阿里房产等创新业务,服务9亿用户,赋能各行业1000万商家。我们打造了全球领先的线上新零售技术平台,并作为核心技术团队保障了11次双十一购物狂欢节的成功。我们的愿景是致力于成为全球最懂商业的技术创新团队,让科技引领面向未来的商业创新和进步。
入驻知乎,将会给大家带来超多干货分享,立体化输出我们对于技术和商业的思考与见解。
详情介绍可以看这里 阿里巴巴淘系技术介绍
欢迎收藏点赞关注我们!共同进步~ :)
更多技术干货可关注【淘系技术】公众号
Recommend
-
44
【51CTO.com原创稿件】 JavaScript的异步处理是前端工程师必须接触的一块内容。ES6在JavaScript异步的处理上引入了新的特性,使得程序员能够更加优雅地...
-
51
说明 Netty推荐使用addListener的方式来回调异步执行的结果,这种方式优于Future.get,能够更精确地把握异步执行结束的时间。 错误理解使用addListener的方式 代码如下:
-
37
异步调用是相对于同步调用而言的,同步调用是指程序按预定顺序一步步执行,每一步必须等到上一步执行完后才能执行,异步调用则无需等待上一步程序执行完即可执行。异步调用指,在程序在执行时,无需等待执行的返回值即可继续执行后面的代...
-
38
netty是一个经典的网络框架,提供了基于NIO、AIO的方式来完成少量线程支持海量用户请求连接的模型。netty里面充斥了大量的非阻塞回调模式,主要是靠Future/Promise异步模型来实现的。 Future是java.util.concurrent.Future,是Java提供的接口,可以...
-
8
dapp异步请求的回调 | 登链社区 | 技术问答 dapp异步请求的回调 ...
-
7
JS深挖:异步方案发展史 ——回调函数、Promise和async/await发布于 今天 13:57 本章围绕JS中异步进行展开,解决问题: 1、什么是异步,为何出现? 2、...
-
7
如果您已经学习 JavaScript 一段时间了,那么您之前可能听说过“异步”这个术语。 这是因为 JavaScript 是一种异步语言……但这到底意味着什么?在本文中,我希望向您展示这个概念并不像听起来那么困...
-
4
一文看懂JS异步编程,回调、Promise、Generator、async/await用法详解 | Lenix Blog JavaScript是一种单线程的编程语言,需要通过异步的方式才能获得较高的性能...
-
5
之前文章也有介绍给useState,包括手写一个useState,但是都没有介绍useState 异步回...
-
7
Lua实现http的异步回调 想用lua实现与http服务器的通信,请求一些数据会回来,默认lua.socket.http是同步的,所以想弄一个异步的方式 lua 5.1 以下是同步的代码,其中http.requ...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK