一、异步本地存储 ALS

异步本地存储(Async Local Storage,简称 ALS)是 Node.js 提供的一种机制,用于在异步调用链中维护上下文状态,而无需显式地在函数之间传递参数。它的底层基于 async_hooks API,可以类比为“异步版的线程本地存储”。

核心概念
  • 上下文传播:在异步操作中自动携带状态(如用户 ID、请求 ID、事务对象等),避免手动传参。
  • 调用链隔离:每个异步调用链拥有独立的存储空间,互不干扰。
  • 使用方式:通过 AsyncLocalStorage.run(store, callback) 创建上下文,后续异步代码可通过 getStore() 访问。

二、线程本地存储 TLS

上文提到线程本地存储,那什么是线程本地存储?

线程本地存储(Thread Local Storage,TLS)是一种让每个线程拥有自己独立变量副本的机制,避免了多线程访问共享变量时的竞争问题。它的实现方式因语言和平台而异,但核心思想是:为每个线程维护一份私有的数据副本

核心原理

在多线程程序中,普通全局变量是共享的,所有线程访问的是同一份内存。而 TLS 则通过以下方式实现线程隔离:

  • 每个线程启动时,系统为其分配一块专属的 TLS 区域。
  • TLS 变量在编译时或运行时被映射到该区域的某个偏移位置。
  • 线程访问 TLS 变量时,实际上是访问自己 TLS 区域中的副本。

三、NodeJs中的应用

在 Node.js 中,ALS(AsyncLocalStorage)async_hooks 模块提供的一个高级封装,用于在异步调用链中存储和访问上下文数据。它非常适合实现像请求级别的 traceId、用户信息等“线程局部变量”的功能。

import { AsyncLocalStorage } from 'async_hooks';

const asyncLocalStorage = new AsyncLocalStorage<{ traceId: string }>();

function logWithContext(message: string) {
  const store = asyncLocalStorage.getStore();
  console.log(`[${store?.traceId || 'no-trace'}] ${message}`);
}

function handleRequest(traceId: string) {
  asyncLocalStorage.run({ traceId }, () => {
    logWithContext('Start processing');
    setTimeout(() => {
      logWithContext('Finished processing');
    }, 100);
  });
}

handleRequest('req-123');
handleRequest('req-456');

输出示例:

[req-123] Start processing
[req-456] Start processing
[req-123] Finished processing
[req-456] Finished processing
注意事项
  • AsyncLocalStorage 只在异步链中有效,不能跨线程或跨进程。
  • 在某些异步边界(如 process.nextTick 或第三方库)可能会丢失上下文,需谨慎测试。
  • 不建议用于存储大量数据,仅用于轻量级上下文信息。

四、NestJs中的实现

NestJS 本身没有内置 ALS 抽象,在 NestJs 中实现异步本地存储,可以引用 Node.js 提供 AsyncLocalStorage 类,其本质上是对 async_hooks 的封装,自动追踪异步资源之间的上下文关系。

1 创建 ALS 实例并注入为服务
// als.module.ts
import { Module } from '@nestjs/common';
import { AsyncLocalStorage } from 'async_hooks';

@Module({
  providers: [
    {
      provide: AsyncLocalStorage,
      useValue: new AsyncLocalStorage(),
    },
  ],
  exports: [AsyncLocalStorage],
})
export class AlsModule {}
2 使用中间件初始化上下文
// app.module.ts
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { AsyncLocalStorage } from 'async_hooks';

@Module({
  imports: [AlsModule],
})
export class AppModule implements NestModule {
  constructor(private readonly als: AsyncLocalStorage<any>) {}

  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply((req, res, next) => {
        const store = { userId: req.headers['x-user-id'] };
        this.als.run(store, () => next());
      })
      .forRoutes('*');
  }
}
3 在服务中访问上下文数据
@Injectable()
export class CatsService {
  constructor(private readonly als: AsyncLocalStorage<any>) {}

  getCatForUser() {
    const store = this.als.getStore();
    const userId = store?.userId;
    return `获取用户 ${userId} 的猫咪数据`;
  }
}

五、NestJs中的应用

nestjs-cls 是一个专为 NestJS 设计的封装库,它基于 Node.js 的 AsyncLocalStorage,但提供了更优雅、更强大的开发体验。相比直接使用原生 ALS,它在类型安全、模块集成、传输方式兼容性等方面都有显著优势。

nestjs-cls 的优势
功能点 原生 AsyncLocalStorage nestjs-cls
NestJS DI 集成 ❌ 手动注入 ✅ 自动注入 ClsService
类型安全 ❌ 无泛型支持 ✅ 支持泛型定义上下文结构
多传输支持 ❌ 仅支持 HTTP ✅ 支持 GraphQL、WebSocket、Cron 等
中间件封装 ❌ 需手动编写 ✅ 自动挂载 ClsMiddleware
插件机制 ❌ 无 ✅ 支持事务插件、请求 ID 插件等
测试友好 ❌ 难以 mock ✅ 可模拟上下文数据
如何使用 nestjs-cls
1 安装依赖
npm install nestjs-cls
2 注册模块
// app.module.ts
import { Module } from '@nestjs/common';
import { ClsModule } from 'nestjs-cls';

@Module({
  imports: [
    ClsModule.forRoot({
      middleware: { mount: true }, // 自动挂载中间件
    }),
  ],
})
export class AppModule {}
3 设置上下文数据(如用户 ID)
// auth.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { ClsService } from 'nestjs-cls';

@Injectable()
export class AuthMiddleware implements NestMiddleware {
  constructor(private readonly cls: ClsService) {}

  use(req: any, res: any, next: () => void) {
    this.cls.set('userId', req.headers['x-user-id']);
    next();
  }
}
4 在服务中访问上下文
// cats.service.ts
import { Injectable } from '@nestjs/common';
import { ClsService } from 'nestjs-cls';

@Injectable()
export class CatsService {
  constructor(private readonly cls: ClsService) {}

  getCatForUser() {
    const userId = this.cls.get('userId');
    return `获取用户 ${userId} 的猫咪数据`;
  }
}
Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐