NestJS 高速缓存
缓存是一项伟大而简单的技术,可以帮助提高应用程序的性能。它充当临时数据存储,提供高性能的数据访问。
安装
我们首先需要安装所需的包:
$ npm install cache-manager
$ npm install -D @types/cache-manager
内存缓存
Nest为各种缓存存储提供程序提供了统一的 API。内置的是内存中的数据存储。但是,您可以轻松地切换到更全面的解决方案,比如 Redis 。为了启用缓存,首先导入 CacheModule 并调用它的 register() 方法。
import { CacheModule, Module } from '@nestjs/common';
import { AppController } from './app.controller';
@Module({
imports: [CacheModule.register()],
controllers: [AppController],
})
export class ApplicationModule {}
与缓存存储的交互
为了和缓存管理器实例进行交互,需要使用CACHE_MANAGER标记将其注入到你的类,如下所示:
constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}
Cache类是从cache-manager中导入的,而CACHE_MANAGER则是从@nestjs/common包中导入的。
Cache实例(来自cache-manager包)上的get方法被用来从缓存中检索键值。如果该键在缓存中不存在,则返回null。
const value = await this.cacheManager.get('key');
使用set方法将一个键值对添加到缓存中:
await this.cacheManager.set('key', 'value');
缓存的默认过期时间是5秒。
你可以为特定的键手动指定一个TTL(过期时间,以秒为单位),如下所示:
await this.cacheManager.set('key', 'value', { ttl: 1000 });
如果要让缓存永不过期,请将配置的ttl属性设置为0。
await this.cacheManager.set('key', 'value', { ttl: 0 });
使用del方法从缓存中删除一个键值对:
await this.cacheManager.del('key');
使用reset方法清空整个缓存:
await this.cacheManager.reset();
自动缓存响应
在 GraphQL 应用中,拦截器针对每个字段解析器分别运行,因此,CacheModule(使用拦截器来缓存响应)将无法正常工作。
要启用自动缓存响应,只需在想缓存数据的地方绑定CacheInterceptor。
@Controller()
@UseInterceptors(CacheInterceptor)
export class AppController {
@Get()
findAll(): string[] {
return [];
}
}
警告: 只有使用 GET 方式声明的节点会被缓存。此外,注入本机响应对象( @Res() )的 HTTP 服务器路由不能使用缓存拦截器。有关详细信息,请参见响应映射。
全局缓存
为了减少重复代码量,可以将CacheInterceptor全局绑定到每个端点(endpoints):
import { CacheModule, Module, CacheInterceptor } from '@nestjs/common';
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)。要个性化不同值,将选项对象传递给register()方法。
CacheModule.register({
ttl: 5, //秒
max: 10, //缓存中最大和最小数量
});
全局缓存重载
使能全局缓存后,缓存入口存储在基于路径自动生成的Cachekey中。你可能需要基于每个方法重载特定的缓存设置(@CacheKey()和@CacheTTL()),允许为独立控制器方法自定义缓存策略。这在使用不同存储缓存时是最有意义的。
@Controller()
export class AppController {
@CacheKey('custom_key')
@CacheTTL(20)
findAll(): string[] {
return [];
}
}
@CacheKey()和@CacheTTL()装饰器从@nestjs/common包导入。
@CacheKey()装饰器可以有或者没有一个对应的@CacheTTL()装饰器,反之亦然。你可以选择仅覆盖@CacheKey()或@CacheTTL()。没有用装饰器覆盖的设置将使用全局注册的默认值(见自定义缓存)。
WebSockets 和 微服务
显然,您可以毫不费力地使用 CacheInterceptor WebSocket 订阅者模式以及 Microservice 的模式(无论使用何种服务间的传输方法)。
译者注: 微服务架构中服务之间的调用需要依赖某种通讯协议介质,在 nest 中不限制你是用消息队列中间件,RPC/gRPC 协议或者对外公开 API 的 HTTP 协议。
@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()装饰器同时或者不同时使用。
不同的存储
服务在底层使用缓存管理器(cache-manager)。cache-manager包支持一个宽范围的可用存储,例如,Redis存储。一个完整的支持存储列表见这里。要设置Redis存储,简单地将该包和相应的选项传递给register()方法。
import * as redisStore from 'cache-manager-redis-store';
import { CacheModule, Module } from '@nestjs/common';
import { AppController } from './app.controller';
@Module({
imports: [
CacheModule.register({
store: redisStore,
host: 'localhost',
port: 6379,
}),
],
controllers: [AppController],
})
export class ApplicationModule {}
调整追踪
默认地,Nest使用请求 URL(在一个HTTPapp 中)或者缓存键(在websockets和microservices应用中,通过@CacheKey()装饰器设置)来联系缓存记录和路径。然而,有时你可能想要根据不同要素设置追踪,例如HTTP headers(比如,确定合适profile路径的Authorization)。
为了达到这个目的,创建一个CacheInterceptor的子类并覆盖trackBy()方法。
@Injectable()
class HttpCacheInterceptor extends CacheInterceptor {
trackBy(context: ExecutionContext): string | undefined {
return 'key';
}
}
异步配置
你可能想异步传递模块选项来代替在编译时静态传递。在这种情况下,可以使用registerAsync()方法,它提供了不同的处理异步配置的方法。
一个方法是使用工厂函数:
CacheModule.registerAsync({
useFactory: () => ({
ttl: 5,
}),
});
我们的工厂行为和其他异步模块工厂一样(它可以使用inject异步注入依赖)。
CacheModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
ttl: configService.getString('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,以代替自己创实例化。
提示: @CacheKey() 装饰器来源于 @nestjs/common 包。
但是, @CacheKey() 需要附加装饰器以指定用于随后存储和检索缓存数据的密钥。此外,请注意,开发者不应该缓存所有内容。缓存数据是用来执行某些业务操作,而一些简单数据查询是不应该被缓存的。
自定义缓存
所有缓存数据都有自己的到期时间(TTL)。要自定义默认值,请将配置选项填写在 register()方法中。
CacheModule.register({
ttl: 5, // seconds
max: 10, // maximum number of items in cache
});
不同的缓存库
我们充分利用了缓存管理器。该软件包支持各种实用的商店,例如Redis 商店(此处列出完整列表)。要设置 Redis 存储,只需将包与 correspoding 选项一起传递给 register() 方法即可。
译者注: 缓存方案库目前可选的有 redis, fs, mongodb, memcached 等。
import * as redisStore from 'cache-manager-redis-store';
import { CacheModule, Module } from '@nestjs/common';
import { AppController } from './app.controller';
@Module({
imports: [
CacheModule.register({
store: redisStore,
host: 'localhost',
port: 6379,
}),
],
controllers: [AppController],
})
export class ApplicationModule {}
调整跟踪
默认情况下, Nest 通过 @CacheKey() 装饰器设置的请求路径(在 HTTP 应用程序中)或缓存中的 key(在 websockets 和微服务中)来缓存记录与您的节点数据相关联。然而有时您可能希望根据不同因素设置跟踪,例如,使用 HTTP 头部字段(例如 Authorization 字段关联身份鉴别节点服务)。
为此,创建 CacheInterceptor 的子类并覆盖 trackBy() 方法。
@Injectable()
class HttpCacheInterceptor extends CacheInterceptor {
trackBy(context: ExecutionContext): string | undefined {
return 'key';
}
}
异步配置
通常,您可能希望异步传递模块选项,而不是事先传递它们。在这种情况下,使用 registerAsync() 方法,提供了几种处理异步数据的方法。
第一种可能的方法是使用工厂函数:
CacheModule.registerAsync({
useFactory: () => ({
ttl: 5,
}),
});
显然,我们的工厂要看起来能让每一个调用用使用。(可以变成顺序执行的同步代码,并且能够通过注入依赖使用)。
CacheModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
ttl: configService.getString('CACHE_TTL'),
}),
inject: [ConfigService],
});
或者,您可以使用类而不是工厂:
CacheModule.registerAsync({
useClass: CacheConfigService,
});
上面的构造将 CacheConfigService 在内部实例化为 CacheModule ,并将利用它来创建选项对象。在 CacheConfigService 中必须实现 CacheOptionsFactory 的接口。
@Injectable()
class CacheConfigService implements CacheOptionsFactory {
createCacheOptions(): CacheModuleOptions {
return {
ttl: 5,
};
}
}
为了防止 CacheConfigService 内部创建 CacheModule 并使用从不同模块导入的提供程序,您可以使用 useExisting 语法。
CacheModule.registerAsync({
imports: [ConfigModule],
useExisting: ConfigService,
});
它和 useClass 的用法有一个关键的相同点: CacheModule 将查找导入的模块以重新使用已创建的 ConfigService 实例,而不是重复实例化。
更多建议: