什么是asyncLocalStorage?

552 阅读3分钟

翻译自:What's Async Local Storage in Node.js v14?

引言

像Apache这样运行Java来托管网站的Web服务器,当Java收到来自客户端的请求时,您的Web服务器会确保启动一个新线程。它允许该线程管理该特定请求的所有资源、局部变量、函数调用堆栈等。但是JavaScript出现了一个问题。

JavaScript是单线程的——这意味着你不能让多个JS线程在同一个父进程下一起运行。但是不要被这个骗了——JS在处理 Web服务器请求方面与其他成熟的解决方案(如Java后端)一样快(甚至更快)。可以参考Java与Node.js性能PK

Node在执行JS的时候是单线程,因此无法在不同的线程内创建全局变量记录环境。因此你不能使用thread local storage。

为什么单线程是很大的问题?

在这种情况下,单线程是一个问题,因为Node只要不耗尽事件循环中的所有同步操作,就会一直执行同步代码。然后它会检查事件和回调,并在必要时执行该代码。

在Node中,一个简单的HTTP请求只不过是客户端向后台节点触发以处理请求的事件——它是异步的。

现在假设您想将一些数据与此异步操作相关联。你会怎么做?

好吧,您可以创建某种“全局”变量并将您的特殊数据分配给它。然后,当另一个请求来自同一用户时,您可以使用全局变量来读取您之前存储的任何内容。

但是当你手头有多个请求时这种方法会失效,因为Node不会串行执行异步代码(当然,这是异步的定义!)。

然我们考虑一下下面的伪代码:

server.listen(1337).on('request', (req) => {
  // some synchronous operation (save state)
  // some asynchronous operation
  // some asynchronous operation
})

运行顺序:

  • 用户1访问服务器的1337端口
  • Node开始运行同步操作代码
  • 当节点运行该同步代码时,另一个用户2访问了服务器
  • Node会继续执行同步代码,第二个HTTP请求在任务队列中等待
  • 当Node完成同步操作并进入异步操作时,它会将其扔到任务队列中,然后开始处理任务队列中的第一个任务——第二个HTTP请求
  • 这次它运行的是同步代码,但代表用户2的请求。 当用户2的同步代码完成后,它会恢复用户1的异步执行,依此类推。

现在,如果您想在调用特定用户的异步代码时为特定用户保留特定数据怎么办?这时AsyncStorage就派上用场了。

代码演示:

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

const asyncLocalStorage = new AsyncLocalStorage();

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

let idSeq = 0;
http.createServer((req, res) => {
  asyncLocalStorage.run(idSeq++, () => {
    logWithId('start');
    // Imagine any chain of async operations here
    setImmediate(() => {
      logWithId('finish');
      res.end();
    });
  });
}).listen(8080);

http.get('http://localhost:8080');
http.get('http://localhost:8080');
// Prints:
//   0: start
//   1: start
//   0: finish
//   1: finish

性能

在你尝试在生产中推出它之前,请注意——如果不是绝对需要,不真正建议任何人这样做。这是因为它对你的应用程序带来了不可忽略的性能影响。这主要是因为async_hooks的底层API仍然是 WIP,但情况应该会逐渐改善。