穿针引线之 AsyncLocalStorage

news2024/12/30 0:40:49

在 Node.js 中,如何更优雅地获取请求上下文一直是一个问题,看一下下面的例子。

背景

const http = require('http');
function handler1(req, res) {
    console.log(req.url);
}

function handler2(req, res) {
     console.log(req.url);
}

http.createServer((req, res) => {
    handler1(req, res);
    handler2(req, res);
    res.end();
}).listen();

上面的例子中,每次收到一个请求时都会执行 handler1 和 handler2,为了在不同的地方里都能拿到请求上下文,我们只能逐级进行传递,如果业务逻辑很复杂,这个维护性是非常差的,下接下来看看如何使用 AsyncLocalStorage 解决这个问题。

AsyncLocalStorage

AsyncLocalStorage 是基于 Async Hooks 实现的,它通过上下文传递实现了异步代码的上下文共享和隔离。下面看一个例子。

const { AsyncLocalStorage } = require('async_hooks');

const asyncLocalStorage = new AsyncLocalStorage();
function logWithId(msg) {
  const id = asyncLocalStorage.getStore();
  console.log(`${id !== undefined ? id : '-'}:`, msg);
}

asyncLocalStorage.run(1, () => {
	logWithId('start');
	setImmediate(() => {
	  logWithId('finish');
	});
 });

上面的代码会输出

1: start
1: finish

从中可以看到两个 logWithId 共享了同一个上下文,这个上下文是由 run 函数设置的 1,那这种技术如何解决我们刚开始提出的问题呢?看一下下面的例子。

const http = require('http');
const { AsyncLocalStorage } = require('async_hooks');

const asyncLocalStorage = new AsyncLocalStorage();

function handler1() {
    const { req } = asyncLocalStorage.getStore();
    console.log(req.url);
}

function handler2() {
    setImmediate(() => {
    	const { req } = asyncLocalStorage.getStore();
        console.log(req.url);
    });
}

http.createServer((req, res) => {
    asyncLocalStorage.run({ req, res }, () => {   
        handler1();
        handler2();
    });
    res.end();
}).listen(9999, () => {
    http.get({ port: 9999, path: '/test' })
});

执行上面代码输出如下。

/test
/test

可以看到,我们不需要逐级地传递请求上下文并且可以在任意异步代码中获取请求上下文。这让代码的编写和维护带来了非常大的好处,不过缺点就是,因为 AsyncLocalStorage 是基于 Async hooks 的,所以会带来一些性能损耗,不同的版本可能不一样,但是 Node.js 也在不断地优化其性能,我印象中,社区有人提过使用其他技术实现 AsyncLocalStorage。

AsyncLocalStorage 原理

知其然知其所以然,只知道怎么使用是不够的,理解其原理可以帮助我们更好地使用它。下面来分析一下 AsyncLocalStorage 的原理。先看一下创建 AsyncLocalStorage 的逻辑

class AsyncLocalStorage {
  constructor() {
    this.kResourceStore = Symbol('kResourceStore');
    this.enabled = false;
  }
}

创建AsyncLocalStorage的时候很简单,主要是置状态为false,并且设置kResourceStore的值为Symbol(‘kResourceStore’)。设置为Symbol(‘kResourceStore’)而不是 ‘kResourceStore’ 很重要,我们后面会看到。继续看一下执行AsyncLocalStorage.run的逻辑。

const storageList = [];
const storageHook = createHook({
  init(asyncId, type, triggerAsyncId, resource) {
    const currentResource = executionAsyncResource();
    // 传递上下文
    for (let i = 0; i < storageList.length; ++i) {
      storageList[i]._propagate(resource, currentResource, type);
    }
  },
});

run(store, callback, ...args) {
	// 把当前 AsyncLocalStorage 加入队列
    ArrayPrototypePush(storageList, this);
    // 启动 AsyncHooks
    storageHook.enable();
   // 获取当前的异步资源,比如收到的请求
   const resource = executionAsyncResource();
   // 记录旧的上下文
   const oldStore = resource[this.kResourceStore];
   // 修改当前异步资源的上下文
   resource[this.kResourceStore] = store;
   // 在新的上下文中执行传入的回调函数
   try {
     return ReflectApply(callback, null, args);
   } finally {
     resource[this.kResourceStore] = oldStore;
   }
 }

回调函数里可以通过 asyncLocalStorage.getStore() 获得设置的公共上下文。

getStore() {
  const resource = executionAsyncResource();
  return resource[this.kResourceStore];
}

getStore的原理很简单,首先拿到当前的异步资源,然后根据AsyncLocalStorage的kResourceStore的值从resource中拿到公共上下文,如果是同步执行getStore(比如 handler1 中),那么executionAsyncResource返回的就是我们请求所对应的异步资源,上下文就是在run时设置的上下文({req, res}),但是如果是异步getStore那么怎么办呢?因为这时候executionAsyncResource返回的不再是请求所对应的异步资源,也就拿不到他挂载的公共上下文。为了解决这个问题,Node.js对公共上下文进行了传递。

const storageList = []; // AsyncLocalStorage对象数组
const storageHook = createHook({
  init(asyncId, type, triggerAsyncId, resource) {
    const currentResource = executionAsyncResource();
    for (let i = 0; i < storageList.length; ++i) {
      storageList[i]._propagate(resource, currentResource);
    }
  }
});

 _propagate(resource, triggerResource) {
    const store = triggerResource[this.kResourceStore];
    resource[this.kResourceStore] = store;
  }

我们看到在每次资源创建的时候,Node.js会把当前异步资源的上下文挂载到新创建的异步资源中。所以在asyncLocalStorage.getStore() 时即使不是我们在执行run时创建的资源对象,也可以获得具体asyncLocalStorage对象所设置的资源( handler2 中)。关系图如下。

这样就实现了异步资源上下文的共享和隔离。

总结

AsyncLocalStorage 有很多用法和用处,我们在 Node.js APM 中也大量用到该技术,通过 AsyncLocalStorage,我们可以无侵入地实现对 Node.js 应用的内部进行观测,时间关系,本文简单地介绍了 AsyncLocalStorage 的使用和原理,有兴趣的同学可以自行探索。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/605854.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【react全家桶】react-Hook (下)

本人大二学生一枚&#xff0c;热爱前端&#xff0c;欢迎来交流学习哦&#xff0c;一起来学习吧。 <专栏推荐> &#x1f525;&#xff1a;js专栏 &#x1f525;&#xff1a;vue专栏 &#x1f525;&#xff1a;react专栏 文章目录 15【react-Hook &#xff08;下&#x…

进程控制(Linux)

进程控制 fork 在Linux中&#xff0c;fork函数是非常重要的函数&#xff0c;它从已存在进程中创建一个新进程。新进程为子进程&#xff0c;而原进程为父进程。 返回值&#xff1a; 在子进程中返回0&#xff0c;父进程中返回子进程的PID&#xff0c;子进程创建失败返回-1。 …

Spring - BeanFactory与ApplicationContext介绍

文章目录 Spring Bean一、BeanFactory 快速入门1.1 BeanFactory 开发步骤1.2 DI依赖注入 二、ApplicationContext快速入门2.1 入门2.2 BeanFactory 与 ApplicationContext关系2.3 BeanFactory 继承体系2.4 ApplicationContext 继承体系 Spring Bean 之前也了解过Spring Bean&a…

高斯过程回归 | Matlab实现高斯过程回归多输入单输出预测(Gaussian Process Regression)

文章目录 效果一览文章概述研究内容程序设计参考资料效果一览 文章概述 高斯过程回归 | Matlab实现高斯过程回归多输入单输出预测(Gaussian Process Regression) 研究内容 高斯过程回归(Gaussian Process Regression,GPR)是一种基于概率模型的非参数回归方法,可以用于

mybatisplus数据权限插件学习初探 动态表名更换插件

文章目录 学习链接 mybatisplus数据权限插件学习初探前言案例建表用户表订单表 环境准备UserUserMapperUserMapper.xmlOrdersOrdersMapperOrdersMapper.xml 配置UserTypeEnumUserContextHolderCustomizeDataPermissionHandlerMybatisPlusConfig 测试测试类bossdeptManagerclerk…

Zinx框架学习 - 消息封装

Zinx - V0.5 消息封装 之前我们使用Request来保存服务器的数据&#xff0c;很显然使用[]byte来接收数据&#xff0c;没有长度也没有消息类型&#xff0c;接下来就要针对这个消息进行封装 创建消息类型 定义一个基本的message包&#xff0c;会包含消息ID、数据、数据长度三个…

路径规划算法:基于探路者优化的路径规划算法- 附代码

路径规划算法&#xff1a;基于探路者优化的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于探路者优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用智能优化算法…

涉及float和double

文章目录 涉及float和double的问题&#xff1a;它们的存储方式&#xff1a;有效位&#xff1f; 链式结构 涉及float和double的问题&#xff1a; 它们的存储方式&#xff1a; 它们会分成小数部分和指数部分分别存储。小数部分的有效位数越多&#xff0c;精度就越高&#xff0c;…

NLP超详细新手快速入门上手篇(1)常用函数

前言 自然语言处理(NLP)是机器学习的应用之一&#xff0c;用于分析、理解和生成自然语言&#xff0c;以便人类与计算机&#xff0c;人类与人类更好的交流。自然语言处理按照任务类型可以分为分类、匹配、翻译、结构化预测、与序贯决策过程这五类。 本篇参考自TensorFlow官方文…

MyBatis 查询数据库

✏️作者&#xff1a;银河罐头 &#x1f4cb;系列专栏&#xff1a;JavaEE &#x1f332;“种一棵树最好的时间是十年前&#xff0c;其次是现在” 目录 MyBatis 是什么&#xff1f;第⼀个MyBatis查询创建数据库和表添加MyBatis框架支持设置 MyBatis 配置信息添加业务代码 查询操…

【VBA】实现批量生成二维码

系列文章 【C#】单号生成器&#xff08;编号规则、固定字符、流水号、产生业务单号&#xff09; 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/129129787 【C#】日期范围生成器&#xff08;开始日期、结束日期&#xff09; 本文链接&#xff1a;h…

Nginx - ​一个高性能、灵活可靠的开源Web服务器

Nginx是什么&#xff1f; Nginx是一个高性能的HTTP和反向代理web服务器&#xff0c;同时也提供了IMAP/POP3/SMTP服务。Nginx是由伊戈尔赛索耶夫为俄罗斯访问量第二的Rambler.ru站点&#xff08;俄文&#xff1a;Рамблер&#xff09;开发的&#xff0c;第一个公开版本0.1…

idea使用native-image打包springboot项目

native-image简介 native-image 是一个用于将 Java 程序编译为本地可执行文件的工具。它是 GraalVM 的一部分&#xff0c;GraalVM 是一个高性能的通用虚拟机&#xff0c;支持多种语言。 使用步骤 下载GraalVM 安装 GraalVM&#xff1a;首先&#xff0c;你需要安装 GraalVM。…

20230603-周六随笔

周六闲来无事&#xff0c;给新电脑装下开发环境&#xff0c;记录一下遇到的问题 git下载代码报错 报错1&#xff1a;schannel: SEC_E_UNTRUSTED_ROOT (0x80090325)解决方法&#xff1a;执行git config --system http.sslbackend openssl命令 报错2&#xff1a;SSL certifica…

【Java 8 新特性】获取对象列表中的某个属性组成的列表

文章目录 获取对象列表中的某个属性组成的列表1、用法示例2、详细案例 附录&#xff1a;Java 8 Stream 基本用法1、map2、filter3、forEach4、limit5、sorted6、并行&#xff08;parallel&#xff09;程序7、Collectors8、统计 获取对象列表中的某个属性组成的列表 1、用法示例…

高完整性系统工程(十一):Fault Tolerant Design

目录 1. INTRODUCTION TO FAULT TOLERANCE 1.2 Definitions 1.3 Two Kinds of Faults 1.4 Hardware vs Software Faults 1.4.1 Failure Curve for Hardware 1.4.2 Hardware and Software Failures 1.5 Causes of Failures 1.6 3 Ways to Class Failures 1.6.1 Tempora…

【LLM】大模型值得探索的十个研究方向

note 基础理论&#xff1a;大模型的基础理论是什么&#xff1f; 网络架构&#xff1a;Transformer是终极框架吗&#xff1f; 高效计算&#xff1a;如何使大模型更加高效&#xff1f; 高效适配&#xff1a;大模型如何适配到下游任务&#xff1f; 可控生成&#xff1a;如何实…

ChatGPT有关的模块知多少?

本文由 大侠(AhcaoZhu)原创&#xff0c;转载请声明。 链接: https://blog.csdn.net/Ahcao2008 ChatGPT有关的模块知多少&#xff1f; &#x1f9ca;摘要&#x1f9ca;ChatGPT 开发库清单 &#x1f9ca;摘要 本文介绍了基于OpenAI ChatGPT 的API 开发的python 模块库。【原创&am…

STM32cubemx定时外部模式测量10M以上频率

STM32cubemx定时外部模式测量10M以上频率 本文讲解利用定时器的外部时钟功能&#xff0c;巧妙测量高频外部信号频率。范围可以到高达30M以上。 所需工具&#xff1a; 开发板:STM32F103RCT6STM32CubeMXIDE: Keil-MDK 文章目录 STM32cubemx定时外部模式测量10M以上频率原理讲解…

【Java技术专题】「Guava开发指南」手把手教你如何进行使用Guava工具箱进行开发系统实战指南(Optional篇)

Guava相关的介绍 Guava工程包含了许多被Google的Java项目广泛依赖的核心库。我们希望通过本文档为Guava中最流行和最强大的功能提供更具可读性和解释性的说明。 本教程是中级教程&#xff0c;适合 Guava 中级开发者的进阶学习。 学习Guava前的准备工作 学习目标和计划&#xf…