java
中可通过thread local
记录链路信息,每个请求进程都持有自己数据的副本,但node
是单进程的,无法通过进程进行链路信息存储与追踪。但提供了AsyncLocalStorage
实现异步资源状态共享,可以在整个异步操作中进行数据共享记录,通过此特性可以实现链路信息记录追踪。
基本使用
AsyncLocalStorage
本身使用比较简单,提供了run
方法用于入口设置,可以设置存储数据,并提供了get
方法获取存储数据,存储数据在整个异步执行过程中(也就是包裹在run
方法中的callback
中的执行过程)是共享的,并与其它异步资源隔离,如下:
import { AsyncLocalStorage } from 'async_hooks';
import { v4 as uuidV4 } from 'uuid';
type TraceStore = { // 数据存储格式,这里只存储一个id
traceId: string,
}
const localStorage = new AsyncLocalStorage<TraceStore>(); // 定义AsyncLocalStorage对象
const LocalStorageWrapper = {
run: (traceStore: TraceStore, callback?: (args?: unknown) => any) => {
// TODO 可以进行其它处理
localStorage.run(traceStore, callback);
},
getTraceStore: () => {
return localStorage.getStore();
},
}
export default LocalStorageWrapper;
这样,将主函数传入run
方法中,则在函数整个执行过程中都会维持一份数据记录,写一个打印函数,同时打印traceId
与函数名,如下:
const COLORS = {
NONE: '\x1B[0m',
CYAN: '\x1B[36m', // 青色
}
const println = (message: any, ...optionParams: any[]) => {
const traceStore = LocalStorageWrapper.getTraceStore();
const err = new Error();
const st = err.stack;
const caller = st.split('\n')[2].trim().split(' ')[1]; // 函数调用的地 ...
console.log(`${COLORS.CYAN}[${traceStore.traceId}, ${caller}]${COLORS.NONE}`, message, ...optionParams);
}
写一个测试函数如下:
function subFunc() {
println("I'm sub func ...");
}
function testMain() {
subFunc();
println("I'm main func ...");
}
通过localStorage
包裹调用,如下:
LocalStorageWrapper.run({traceId: new Date().getTime().toString()}, testMain);
则能看到输出如下:
在整个过程中存储信息是共享的。
在express
等框架中使用
再express
等web
框架中使用时,可将run
注册为中间件,在一个请求到来时执行中间件传入traceId
,因为中间件是伴随路由整个过程的,所以在请求发出前整个异步过程都能共享存储,如下:
import * as express from "express";
import { Request, Response } from 'express';
import LocalStorageWrapper from "LocalStorageWrapper";
const app = express();
const port = 3000;
app.use((req: Request, res: Response, next: () => any) => { // 应用中间件
const traceId = req.get('traceid') || new Date().getTime().toString();
LocalStorageWrapper.run({ traceId }, next);
});
class FakeService { // 模拟一个其它服务类
print() {
println('service was called ...'); // 打印
}
}
const fakeService = new FakeService();
app.get("/trace", (req: Request, res: Response) => {
println("enter /trace"); // 打印
fakeService.print();
res.send("Hello World!");
});
app.listen(port, () => {
console.log(`server listening on port ${port}`);
});
多次访问localhost:3000/trace
,输出如下:
性能
在引入异步状态存储后,由于每个异步执行都会触发响应hooks
,因此性能会有所下降,虽说已有很大优化改善,但据Github
上测试大约有8%
的性能下降。