开门见山:什么是 AsyncLocalStorage 根据 Node.js 官方文档 :”This class is used to create asynchronous state within callbacks and promise chains. It allows storing data throughout the lifetime of a web request or any other asynchronous duration. It is similar to thread-local storage in other languages.”,
为了进一步简化解释,AsyncLocalStorage 允许你在执行异步函数时存储状态,然后使其可用于该函数中的所有代码路径。
场景:一个案例引入 如何实现请求的链路追踪?就是说如何确定一个Request从发起到返回处理结束过程中的所有调用路径?一般是通过发起方携带一个唯一请求标识,可以叫traceId,以此来实现链路追踪,实现日志溯源等。
如何实现请求的链路追踪机制? 方案一:全局变量 globalTraceId ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 const http = require ('http' );let globalTraceId function handleRequest (req, res ) { globalTraceId = generateTraceId() cookieValidator().then((res ) => { res.writeHead(200 , { 'Content-Type' : 'text/plain' }); res.write('Congrats! Your damn cookie is the best one!' ); res.end(); }).catch((err ) => { reportError(err, globalTraceId) }); } const server = http.createServer((req, res ) => { handleRequest(req, res) }); server.listen(3000 , () => { console .log('Server listening on port 3000' ); });
这里的会出现的问题是由于nodejs单线程执行,第一个请求进来的时候,globalTraceId被赋值,然后进入到异步检查用户cookie的逻辑中,此时node的main stack是空的,事件循环会执行处理下一个请求。此时就会导致globalTraceId被覆写,导致第一个请求的异步检查中错误上报的globalTraceId是错误的。
方案二:直接透传参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 const http = require ('http' );function handleRequest (req, res ) { const traceId = req.headers['x-trace-id' ] || generateTraceId(); req.traceId = traceId; cookieValidator().then((result ) => { }).catch((err ) => { reportError(err, req.traceId) }); } function cookieValidator ( ) { return new Promise ((resolve, reject ) => { setTimeout(() => { }, 1000 ); }); }
这种方式简单粗暴,的确可行。问题就是每次都得透传参数,写起来有点麻烦。
方案三:使用 AsyncLocalStorage 实现 下面这个例子,实现了一个日志追踪的最简逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 import http from "http" ;import { AsyncLocalStorage } from "async_hooks" ;const myAls = new AsyncLocalStorage();function logWithId (msg ) { const context = myAls.getStore(); const id = context.reqId; console .log(`${id !== undefined ? id : "-" } :` , msg); } let reqId = 0 ;http .createServer((req, res ) => { const myContext = { reqId: reqId++, }; myAls.run(myContext, () => { logWithId("start" ); setImmediate(() => { logWithId("end" ); res.end(); }); }); }) .listen(8080 ); http.get("http://localhost:8080" ); http.get("http://localhost:8080" );
总结 主要是简单了解了下 nodejs 中 AsyncLocalStorage 的初步使用,当然更深层次的理解还需要具体的业务场景来体验。