缓存 Caching

导读

缓存是一种简单而出色的技术,有助于提高应用的性能。它充当临时数据存储,提供高性能数据访问。

安装

首先安装所需的软件包:

bash
$ npm install @nestjs/cache-manager cache-manager
警告

cache-manager 版本 4 使用秒作为 TTL(生存时间)。当前版本的 cache-manager(v5)已改为使用毫秒。NestJS 不会转换该值,而只是将您提供的 ttl 转发给库。换句话说:

  • 如果使用 cache-manager v4,则以秒为单位提供 ttl
  • 如果使用 cache-manager v5,则以毫秒为单位提供 ttl
  • 文档指的是秒,因为 NestJS 是针对 cache-manager 版本 4 发布的。

内存缓存

Nest 为各种缓存存储提供商提供了统一的 API。内置的是内存数据存储。但是,您可以轻松切换到更全面的解决方案,例如 Redis。

为了启用缓存,请导入 CacheModule 并调用其 register() 方法。

ts
import { Module } from '@nestjs/common'
import { CacheModule } from '@nestjs/cache-manager'
import { AppController } from './app.controller'

@Module({
  imports: [CacheModule.register()],
  controllers: [AppController],
})
export class AppModule {}

与缓存存储交互

要与缓存管理器实例交互,请使用CACHE_MANAGER令牌将其注入到您的类中,如下所示:

ts
constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}
提示

Cache 类从 cache-manager 导入,而 CACHE_MANAGER 令牌从 @nestjs/cache-manager 包导入。

Cache 实例(来自 cache-manager 包)上的 get 方法用于从缓存中检索项目。如果缓存中不存在该项目,则将返回 null

ts
const value = await this.cacheManager.get('key')

要将项目添加到缓存中,请使用 set 方法:

ts
await this.cacheManager.set('key', 'value')
注意

内存缓存存储只能存储结构化克隆算法支持的类型的值。

缓存的默认过期时间为 5 秒。

您可以手动为该特定键指定 TTL(以秒为单位的过期时间),如下所示:

ts
await this.cacheManager.set('key', 'value', 1000)

要禁用缓存过期,请将 ttl 配置属性设置为 0

ts
await this.cacheManager.set('key', 'value', 0)

要从缓存中删除项目,请使用 del 方法:

ts
await this.cacheManager.del('key')

要清除整个缓存,请使用 reset 方法:

ts
await this.cacheManager.reset()

自动缓存响应

警告

GraphQL 应用程序中,拦截器会针对每个字段解析器单独执行。因此,CacheModule(使用拦截器缓存响应)将无法正常工作。

要启用自动缓存响应,只需将 CacheInterceptor 绑定到您想要缓存数据的位置即可。

ts
@Controller()
@UseInterceptors(CacheInterceptor)
export class AppController {
  @Get()
  findAll(): string[] {
    return []
  }
}

仅缓存 GET 端点。此外,注入本机响应对象 (@Res()) 的 HTTP 服务器路由无法使用缓存拦截器。有关更多详细信息,请参阅 响应映射

为了减少所需的样板代码量,您可以将 CacheInterceptor 全局绑定到所有端点:

ts
import { Module } from '@nestjs/common'
import { CacheInterceptor, CacheModule } from '@nestjs/cache-manager'
import { APP_INTERCEPTOR } from '@nestjs/core'
import { AppController } from './app.controller'

@Module({
  imports: [CacheModule.register()],
  controllers: [AppController],
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: CacheInterceptor,
    },
  ],
})
export class AppModule {}

自定义缓存

所有缓存数据都有自己的过期时间 (TTL)。要自定义默认值,请将选项对象传递给 register() 方法。

ts
CacheModule.register({
  ttl: 5, // seconds
  max: 10, // maximum number of items in cache
})

全局使用模块

当您想在其他模块中使用 CacheModule 时,您需要导入它(这是任何 Nest 模块的标准做法)。或者,通过将选项对象的 isGlobal 属性设置为 true 将其声明为 全局模块,如下所示。在这种情况下,一旦在根模块(例如 AppModule)中加载了 CacheModule,您就不需要在其他模块中导入它。

ts
CacheModule.register({
  isGlobal: true,
})

全局缓存覆盖

启用全局缓存后,缓存条目将存储在根据路由路径自动生成的 CacheKey 下。您可以根据每个方法覆盖某些缓存设置(@CacheKey()@CacheTTL()),从而允许为各个控制器方法定制缓存策略。在使用 不同的缓存存储 时,这可能最为相关

您可以根据每个控制器应用 @CacheTTL() 装饰器来为整个控制器设置缓存 TTL。在同时定义控制器级和方法级缓存 TTL 设置的情况下,在方法级指定的缓存 TTL 设置将优先于在控制器级设置的缓存 TTL 设置。

ts
@Controller()
@CacheTTL(50)
export class AppController {
  @CacheKey('custom_key')
  @CacheTTL(20)
  findAll(): string[] {
    return []
  }
}
提示

@CacheKey()@CacheTTL() 装饰器从 @nestjs/cache-manager 包导入。

@CacheKey() 装饰器可以与相应的 @CacheTTL() 装饰器一起使用,也可以单独使用,反之亦然。可以选择仅覆盖 @CacheKey() 或仅覆盖 @CacheTTL()。未使用装饰器覆盖的设置将使用全局注册的默认值(请参阅 自定义缓存)。

WebSockets 和微服务

您还可以将 CacheInterceptor 应用于 WebSocket 订阅者以及微服务的模式(无论使用哪种传输方法)。

ts
@CacheKey('events')
@UseInterceptors(CacheInterceptor)
@SubscribeMessage('events')
handleEvent(client: Client, data: string[]): Observable<string[]> {
  return [];
}
js
@CacheKey('events')
@UseInterceptors(CacheInterceptor)
@SubscribeMessage('events')
handleEvent(client, data) {
  return [];
}

但是,需要额外的 @CacheKey() 装饰器来指定用于随后存储和检索缓存数据的密钥。另外,请注意,您不应缓存所有内容。执行某些业务操作而不是简单地查询数据的操作永远不应被缓存。

此外,您可以使用 @CacheTTL() 装饰器指定缓存过期时间 (TTL),它将覆盖全局默认 TTL 值。

ts
TS
ts
@CacheTTL(10)
@UseInterceptors(CacheInterceptor)
@SubscribeMessage('events')
handleEvent(client: Client, data: string[]): Observable<string[]> {
  return [];
}
提示

@CacheTTL() 装饰器可以与相应的 @CacheKey() 装饰器一起使用,也可以单独使用。

调整跟踪

默认情况下,Nest 使用请求 URL(在 HTTP 应用程序中)或缓存键(在 websockets 和微服务应用程序中,通过 @CacheKey() 装饰器设置)将缓存记录与您的端点关联。不过,有时您可能希望根据不同的因素设置跟踪,例如使用 HTTP 标头(例如,Authorization 以正确识别 profile 端点)。

为了实现这一点,请创建 CacheInterceptor 的子类并重写 trackBy() 方法。

ts
@Injectable()
class HttpCacheInterceptor extends CacheInterceptor {
  trackBy(context: ExecutionContext): string | undefined {
    return 'key'
  }
}

不同的存储

此服务在底层利用了 cache-managercache-manager 包支持各种有用的存储,例如 Redis 存储。受支持存储的完整列表可在此处找到。要设置 Redis 存储,只需将包连同相应选项一起传递给 register() 方法。

ts
import type { RedisClientOptions } from 'redis'
import * as redisStore from 'cache-manager-redis-store'
import { Module } from '@nestjs/common'
import { CacheModule } from '@nestjs/cache-manager'
import { AppController } from './app.controller'

@Module({
  imports: [
    CacheModule.register<RedisClientOptions>({
      store: redisStore,

      // Store-specific configuration:
      host: 'localhost',
      port: 6379,
    }),
  ],
  controllers: [AppController],
})
export class AppModule {}

警告警告 cache-manager-redis-store 不支持 redis v4。为了使 ClientOpts 接口存在并正常工作,您需要安装 最新的 redis 3.x.x 主要版本。请参阅此 问题 以跟踪此升级的进度。

异步配置

您可能希望异步传递模块选项,而不是在编译时静态传递它们。在这种情况下,使用 registerAsync() 方法,它提供了几种处理异步配置的方法。

一种方法是使用工厂函数:

ts
CacheModule.registerAsync({
  useFactory: () => ({
    ttl: 5,
  }),
})

我们的工厂的行为与所有其他异步模块工厂一样(它可以是异步的,并且能够通过inject注入依赖项)。

ts
CacheModule.registerAsync({
  imports: [ConfigModule],
  useFactory: async (configService: ConfigService) => ({
    ttl: configService.get('CACHE_TTL'),
  }),
  inject: [ConfigService],
})

或者,您可以使用useClass方法:

ts
CacheModule.registerAsync({
  useClass: CacheConfigService,
})

上述构造将在CacheModule内实例化CacheConfigService,并使用它来获取选项对象。CacheConfigService必须实现CacheOptionsFactory接口才能提供配置选项:

ts
@Injectable()
class CacheConfigService implements CacheOptionsFactory {
  createCacheOptions(): CacheModuleOptions {
    return {
      ttl: 5,
    }
  }
}

如果您希望使用从不同模块导入的现有配置提供程序,请使用 useExisting 语法:

ts
CacheModule.registerAsync({
  imports: [ConfigModule],
  useExisting: ConfigService,
})

这与 useClass 的工作方式相同,但有一个关键区别 - CacheModule 将查找导入的模块以重用任何已创建的 ConfigService,而不是实例化其自己的。

提示

CacheModule#registerCacheModule#registerAsync 以及 CacheOptionsFactory 具有可选的泛型(类型参数)来缩小特定于存储的配置选项,使其类型安全。

示例

此处 提供了一个工作示例。