缓存
缓存是一种强大且简单的技术,可以显著提升应用程序的性能。它充当临时存储层,允许更快地访问频繁使用的数据,减少重复获取或计算相同信息的需要。这将带来更快的响应时间和更高的整体效率。
安装
要在 Nest 中开始使用缓存,你需要安装 @nestjs/cache-manager 包以及 cache-manager 包。
$ npm install @nestjs/cache-manager cache-manager默认情况下,所有数据都存储在内存中。由于 cache-manager 底层使用了 Keyv,你可以通过安装相应的包轻松切换到更高级的存储方案,例如 Redis。我们将在后面详细介绍。
内存缓存
要在应用程序中启用缓存,需要导入 CacheModule 并使用 register() 方法进行配置:
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 令牌将其注入到你的类中,如下所示:
constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}提示
Cache 类和 CACHE_MANAGER 令牌都从 @nestjs/cache-manager 包中导入。
Cache 实例(来自 cache-manager 包)上的 get 方法用于从缓存中检索项目。如果该项目在缓存中不存在,将返回 null。
const value = await this.cacheManager.get('key');要向缓存添加项目,请使用 set 方法:
await this.cacheManager.set('key', 'value');注意
内存缓存存储只能存储结构化克隆算法支持的类型的值。
你可以为特定的键手动指定 TTL(过期时间,以毫秒为单位),如下所示:
await this.cacheManager.set('key', 'value', 1000);其中 1000 是以毫秒为单位的 TTL —— 在此例中,缓存项将在一秒后过期。
要禁用缓存过期,请将 ttl 配置属性设置为 0:
await this.cacheManager.set('key', 'value', 0);要从缓存中删除某个项目,请使用 del 方法:
await this.cacheManager.del('key');要清除整个缓存,请使用 clear 方法:
await this.cacheManager.clear();自动缓存响应
警告
在 GraphQL 应用程序中,拦截器会为每个字段解析器单独执行。因此,CacheModule(使用拦截器缓存响应)将无法正常工作。
要启用自动缓存响应,只需在需要缓存数据的地方绑定 CacheInterceptor。
@Controller()
@UseInterceptors(CacheInterceptor)
export class AppController {
@Get()
findAll(): string[] {
return [];
}
}警告
只有 GET 端点会被缓存。此外,注入原生响应对象(@Res())的 HTTP 服务器路由无法使用缓存拦截器。更多详情请参阅响应映射。
为了减少所需的样板代码量,你可以将 CacheInterceptor 全局绑定到所有端点:
import { Module } from '@nestjs/common';
import { CacheModule, CacheInterceptor } from '@nestjs/cache-manager';
import { AppController } from './app.controller';
import { APP_INTERCEPTOR } from '@nestjs/core';
@Module({
imports: [CacheModule.register()],
controllers: [AppController],
providers: [
{
provide: APP_INTERCEPTOR,
useClass: CacheInterceptor,
},
],
})
export class AppModule {}生存时间(TTL)
ttl 的默认值为 0,这意味着缓存永不过期。要指定自定义 TTL,可以在 register() 方法中提供 ttl 选项,如下所示:
CacheModule.register({
ttl: 5000, // 毫秒
});全局使用模块
当你想在其他模块中使用 CacheModule 时,需要导入它(这是任何 Nest 模块的标准做法)。或者,通过将选项对象的 isGlobal 属性设置为 true 来将其声明为全局模块,如下所示。在这种情况下,一旦在根模块(例如 AppModule)中加载了 CacheModule,就不需要在其他模块中再次导入它。
CacheModule.register({
isGlobal: true,
});全局缓存覆盖
当启用全局缓存时,缓存条目存储在根据路由路径自动生成的 CacheKey 下。你可以在每个方法的基础上覆盖某些缓存设置(@CacheKey() 和 @CacheTTL()),从而为各个控制器方法定制缓存策略。这在使用不同的缓存存储时可能最为相关。
你可以在每个控制器的基础上应用 @CacheTTL() 装饰器,为整个控制器设置缓存 TTL。在同时定义了控制器级别和方法级别缓存 TTL 设置的情况下,方法级别指定的缓存 TTL 设置将优先于控制器级别设置的。
@Controller()
@CacheTTL(50)
export class AppController {
@CacheKey('custom_key')
@CacheTTL(20)
findAll(): string[] {
return [];
}
}提示
@CacheKey() 和 @CacheTTL() 装饰器从 @nestjs/cache-manager 包中导入。
@CacheKey() 装饰器可以与或不与对应的 @CacheTTL() 装饰器一起使用,反之亦然。你可以选择只覆盖 @CacheKey() 或只覆盖 @CacheTTL()。未通过装饰器覆盖的设置将使用全局注册的默认值(参见自定义缓存)。
WebSocket 和微服务
你也可以将 CacheInterceptor 应用于 WebSocket 订阅者以及微服务的模式(无论使用哪种传输方式)。
@CacheKey('events')
@UseInterceptors(CacheInterceptor)
@SubscribeMessage('events')
handleEvent(client: Client, data: string[]): Observable<string[]> {
return [];
}但是,需要额外的 @CacheKey() 装饰器来指定用于后续存储和检索缓存数据的键。另外,请注意你不应该缓存所有内容。执行某些业务操作而非简单查询数据的操作永远不应被缓存。
此外,你可以使用 @CacheTTL() 装饰器指定缓存过期时间(TTL),它将覆盖全局默认 TTL 值。
@CacheTTL(10)
@UseInterceptors(CacheInterceptor)
@SubscribeMessage('events')
handleEvent(client: Client, data: string[]): Observable<string[]> {
return [];
}提示
@CacheTTL() 装饰器可以与或不与对应的 @CacheKey() 装饰器一起使用。
调整追踪
默认情况下,Nest 使用请求 URL(在 HTTP 应用中)或缓存键(在 WebSocket 和微服务应用中,通过 @CacheKey() 装饰器设置)将缓存记录与端点关联。然而,有时你可能希望根据不同的因素设置追踪,例如使用 HTTP 头部(如 Authorization)来正确识别 profile 端点。
为此,需要创建 CacheInterceptor 的子类并覆盖 trackBy() 方法。
@Injectable()
class HttpCacheInterceptor extends CacheInterceptor {
trackBy(context: ExecutionContext): string | undefined {
return 'key';
}
}使用替代缓存存储
切换到不同的缓存存储非常简单。首先,安装相应的包。例如,要使用 Redis,请安装 @keyv/redis 包:
$ npm install @keyv/redis安装完成后,你可以如下所示注册具有多个存储的 CacheModule:
import { Module } from '@nestjs/common';
import { CacheModule } from '@nestjs/cache-manager';
import { AppController } from './app.controller';
import KeyvRedis from '@keyv/redis';
import { Keyv } from 'keyv';
import { CacheableMemory } from 'cacheable';
@Module({
imports: [
CacheModule.registerAsync({
useFactory: async () => {
return {
stores: [
new Keyv({
store: new CacheableMemory({ ttl: 60000, lruSize: 5000 }),
}),
new KeyvRedis('redis://localhost:6379'),
],
};
},
}),
],
controllers: [AppController],
})
export class AppModule {}在此示例中,我们注册了两个存储:CacheableMemory 和 KeyvRedis。CacheableMemory 存储是一个简单的内存存储,而 KeyvRedis 是 Redis 存储。stores 数组用于指定你要使用的存储。数组中的第一个存储是默认存储,其余的是备用存储。
查看 Keyv 文档以获取有关可用存储的更多信息。
异步配置
你可能希望异步传入模块选项,而不是在编译时静态传入。在这种情况下,请使用 registerAsync() 方法,它提供了多种处理异步配置的方式。
一种方法是使用工厂函数:
CacheModule.registerAsync({
useFactory: () => ({
ttl: 5,
}),
});我们的工厂函数与所有其他异步模块工厂函数一样(它可以是 async 的,并且能够通过 inject 注入依赖项)。
CacheModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
ttl: configService.get('CACHE_TTL'),
}),
inject: [ConfigService],
});或者,你可以使用 useClass 方法:
CacheModule.registerAsync({
useClass: CacheConfigService,
});上述构造将在 CacheModule 内部实例化 CacheConfigService,并使用它来获取选项对象。CacheConfigService 必须实现 CacheOptionsFactory 接口以提供配置选项:
@Injectable()
class CacheConfigService implements CacheOptionsFactory {
createCacheOptions(): CacheModuleOptions {
return {
ttl: 5,
};
}
}如果你希望使用从其他模块导入的现有配置提供者,请使用 useExisting 语法:
CacheModule.registerAsync({
imports: [ConfigModule],
useExisting: ConfigService,
});这与 useClass 的工作方式相同,但有一个关键区别 —— CacheModule 将查找已导入的模块以复用任何已创建的 ConfigService,而不是实例化自己的。
提示
CacheModule#register、CacheModule#registerAsync 和 CacheOptionsFactory 有一个可选的泛型(类型参数),用于缩小特定存储的配置选项范围,使其类型安全。
你还可以向 registerAsync() 方法传递所谓的 extraProviders。这些提供者将与模块提供者合并。
CacheModule.registerAsync({
imports: [ConfigModule],
useClass: ConfigService,
extraProviders: [MyAdditionalProvider],
});当你需要为工厂函数或类构造函数提供额外的依赖项时,这非常有用。
示例
一个可运行的示例可以在这里找到。