緩存是一項(xiàng)偉大而簡(jiǎn)單的技術(shù),可以幫助提高應(yīng)用程序的性能。它充當(dāng)臨時(shí)數(shù)據(jù)存儲(chǔ),提供高性能的數(shù)據(jù)訪問。
我們首先需要安裝所需的包:
$ npm install cache-manager
$ npm install -D @types/cache-manager
Nest為各種緩存存儲(chǔ)提供程序提供了統(tǒng)一的 API。內(nèi)置的是內(nèi)存中的數(shù)據(jù)存儲(chǔ)。但是,您可以輕松地切換到更全面的解決方案,比如 Redis 。為了啟用緩存,首先導(dǎo)入 CacheModule 并調(diào)用它的 register() 方法。
import { CacheModule, Module } from '@nestjs/common';
import { AppController } from './app.controller';
@Module({
imports: [CacheModule.register()],
controllers: [AppController],
})
export class ApplicationModule {}
為了和緩存管理器實(shí)例進(jìn)行交互,需要使用CACHE_MANAGER標(biāo)記將其注入到你的類,如下所示:
constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}
Cache類是從cache-manager中導(dǎo)入的,而CACHE_MANAGER則是從@nestjs/common包中導(dǎo)入的。
Cache實(shí)例(來自cache-manager包)上的get方法被用來從緩存中檢索鍵值。如果該鍵在緩存中不存在,則返回null。
const value = await this.cacheManager.get('key');
使用set方法將一個(gè)鍵值對(duì)添加到緩存中:
await this.cacheManager.set('key', 'value');
緩存的默認(rèn)過期時(shí)間是5秒。
你可以為特定的鍵手動(dòng)指定一個(gè)TTL(過期時(shí)間,以秒為單位),如下所示:
await this.cacheManager.set('key', 'value', { ttl: 1000 });
如果要讓緩存永不過期,請(qǐng)將配置的ttl屬性設(shè)置為0。
await this.cacheManager.set('key', 'value', { ttl: 0 });
使用del方法從緩存中刪除一個(gè)鍵值對(duì):
await this.cacheManager.del('key');
使用reset方法清空整個(gè)緩存:
await this.cacheManager.reset();
在 GraphQL 應(yīng)用中,攔截器針對(duì)每個(gè)字段解析器分別運(yùn)行,因此,CacheModule(使用攔截器來緩存響應(yīng))將無法正常工作。
要啟用自動(dòng)緩存響應(yīng),只需在想緩存數(shù)據(jù)的地方綁定CacheInterceptor。
@Controller()
@UseInterceptors(CacheInterceptor)
export class AppController {
@Get()
findAll(): string[] {
return [];
}
}
警告: 只有使用 GET 方式聲明的節(jié)點(diǎn)會(huì)被緩存。此外,注入本機(jī)響應(yīng)對(duì)象( @Res() )的 HTTP 服務(wù)器路由不能使用緩存攔截器。有關(guān)詳細(xì)信息,請(qǐng)參見響應(yīng)映射。
為了減少重復(fù)代碼量,可以將CacheInterceptor全局綁定到每個(gè)端點(diǎn)(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 {}
所有緩存的數(shù)據(jù)有其自己的過期時(shí)間(TTL)。要個(gè)性化不同值,將選項(xiàng)對(duì)象傳遞給register()方法。
CacheModule.register({
ttl: 5, //秒
max: 10, //緩存中最大和最小數(shù)量
});
使能全局緩存后,緩存入口存儲(chǔ)在基于路徑自動(dòng)生成的Cachekey中。你可能需要基于每個(gè)方法重載特定的緩存設(shè)置(@CacheKey()和@CacheTTL()),允許為獨(dú)立控制器方法自定義緩存策略。這在使用不同存儲(chǔ)緩存時(shí)是最有意義的。
@Controller()
export class AppController {
@CacheKey('custom_key')
@CacheTTL(20)
findAll(): string[] {
return [];
}
}
@CacheKey()和@CacheTTL()裝飾器從@nestjs/common包導(dǎo)入。
@CacheKey()裝飾器可以有或者沒有一個(gè)對(duì)應(yīng)的@CacheTTL()裝飾器,反之亦然。你可以選擇僅覆蓋@CacheKey()或@CacheTTL()。沒有用裝飾器覆蓋的設(shè)置將使用全局注冊(cè)的默認(rèn)值(見自定義緩存)。
顯然,您可以毫不費(fèi)力地使用 CacheInterceptor WebSocket 訂閱者模式以及 Microservice 的模式(無論使用何種服務(wù)間的傳輸方法)。
譯者注: 微服務(wù)架構(gòu)中服務(wù)之間的調(diào)用需要依賴某種通訊協(xié)議介質(zhì),在 nest 中不限制你是用消息隊(duì)列中間件,RPC/gRPC 協(xié)議或者對(duì)外公開 API 的 HTTP 協(xié)議。
@CacheKey('events')
@UseInterceptors(CacheInterceptor)
@SubscribeMessage('events')
handleEvent(client: Client, data: string[]): Observable<string[]> {
return [];
}
然而,需要一個(gè)附加的@CacheKey()裝飾器來指定一個(gè)用于依次存儲(chǔ)并獲取緩存數(shù)據(jù)的鍵。注意,你不應(yīng)該緩存所有的內(nèi)容。永遠(yuǎn)也不要去緩存那些用于實(shí)現(xiàn)業(yè)務(wù)邏輯也不是簡(jiǎn)單地查詢數(shù)據(jù)的行為。
此外,你可以使用@CacheTTL()裝飾器來指定一個(gè)緩存過期時(shí)間(TTL),用于覆蓋全局默認(rèn)的 TTL 值。
@CacheTTL(10)
@UseInterceptors(CacheInterceptor)
@SubscribeMessage('events')
handleEvent(client: Client, data: string[]): Observable<string[]> {
return [];
}
@CacheTTL()裝飾器可以@CacheKey()裝飾器同時(shí)或者不同時(shí)使用。
服務(wù)在底層使用緩存管理器(cache-manager)。cache-manager包支持一個(gè)寬范圍的可用存儲(chǔ),例如,Redis存儲(chǔ)。一個(gè)完整的支持存儲(chǔ)列表見這里。要設(shè)置Redis存儲(chǔ),簡(jiǎn)單地將該包和相應(yīng)的選項(xiàng)傳遞給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 {}
默認(rèn)地,Nest使用請(qǐng)求 URL(在一個(gè)HTTPapp 中)或者緩存鍵(在websockets和microservices應(yīng)用中,通過@CacheKey()裝飾器設(shè)置)來聯(lián)系緩存記錄和路徑。然而,有時(shí)你可能想要根據(jù)不同要素設(shè)置追蹤,例如HTTP headers(比如,確定合適profile路徑的Authorization)。
為了達(dá)到這個(gè)目的,創(chuàng)建一個(gè)CacheInterceptor的子類并覆蓋trackBy()方法。
@Injectable()
class HttpCacheInterceptor extends CacheInterceptor {
trackBy(context: ExecutionContext): string | undefined {
return 'key';
}
}
你可能想異步傳遞模塊選項(xiàng)來代替在編譯時(shí)靜態(tài)傳遞。在這種情況下,可以使用registerAsync()方法,它提供了不同的處理異步配置的方法。
一個(gè)方法是使用工廠函數(shù):
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,
});
上述構(gòu)造器將在CacheModule內(nèi)部實(shí)例化CacheConfigService并用它來得到選項(xiàng)對(duì)象,CacheConfigService需要使用CacheOptionsFactory接口來提供配置選項(xiàng):
@Injectable()
class CacheConfigService implements CacheOptionsFactory {
createCacheOptions(): CacheModuleOptions {
return {
ttl: 5,
};
}
}
如果你希望使用在其他不同模塊中導(dǎo)入的現(xiàn)有的配置提供者,使用useExisting語法:
CacheModule.registerAsync({
imports: [ConfigModule],
useExisting: ConfigService,
});
這和useClass工作模式相同,但有一個(gè)根本區(qū)別——CacheModule將查找導(dǎo)入的模塊來重用任何已經(jīng)創(chuàng)建的ConfigService,以代替自己創(chuàng)實(shí)例化。
提示: @CacheKey() 裝飾器來源于 @nestjs/common 包。
但是, @CacheKey() 需要附加裝飾器以指定用于隨后存儲(chǔ)和檢索緩存數(shù)據(jù)的密鑰。此外,請(qǐng)注意,開發(fā)者不應(yīng)該緩存所有內(nèi)容。緩存數(shù)據(jù)是用來執(zhí)行某些業(yè)務(wù)操作,而一些簡(jiǎn)單數(shù)據(jù)查詢是不應(yīng)該被緩存的。
所有緩存數(shù)據(jù)都有自己的到期時(shí)間(TTL)。要自定義默認(rèn)值,請(qǐng)將配置選項(xiàng)填寫在 register()方法中。
CacheModule.register({
ttl: 5, // seconds
max: 10, // maximum number of items in cache
});
我們充分利用了緩存管理器。該軟件包支持各種實(shí)用的商店,例如Redis 商店(此處列出完整列表)。要設(shè)置 Redis 存儲(chǔ),只需將包與 correspoding 選項(xiàng)一起傳遞給 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 {}
默認(rèn)情況下, Nest 通過 @CacheKey() 裝飾器設(shè)置的請(qǐng)求路徑(在 HTTP 應(yīng)用程序中)或緩存中的 key(在 websockets 和微服務(wù)中)來緩存記錄與您的節(jié)點(diǎn)數(shù)據(jù)相關(guān)聯(lián)。然而有時(shí)您可能希望根據(jù)不同因素設(shè)置跟蹤,例如,使用 HTTP 頭部字段(例如 Authorization 字段關(guān)聯(lián)身份鑒別節(jié)點(diǎn)服務(wù))。
為此,創(chuàng)建 CacheInterceptor 的子類并覆蓋 trackBy() 方法。
@Injectable()
class HttpCacheInterceptor extends CacheInterceptor {
trackBy(context: ExecutionContext): string | undefined {
return 'key';
}
}
通常,您可能希望異步傳遞模塊選項(xiàng),而不是事先傳遞它們。在這種情況下,使用 registerAsync() 方法,提供了幾種處理異步數(shù)據(jù)的方法。
第一種可能的方法是使用工廠函數(shù):
CacheModule.registerAsync({
useFactory: () => ({
ttl: 5,
}),
});
顯然,我們的工廠要看起來能讓每一個(gè)調(diào)用用使用。(可以變成順序執(zhí)行的同步代碼,并且能夠通過注入依賴使用)。
CacheModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
ttl: configService.getString('CACHE_TTL'),
}),
inject: [ConfigService],
});
或者,您可以使用類而不是工廠:
CacheModule.registerAsync({
useClass: CacheConfigService,
});
上面的構(gòu)造將 CacheConfigService 在內(nèi)部實(shí)例化為 CacheModule ,并將利用它來創(chuàng)建選項(xiàng)對(duì)象。在 CacheConfigService 中必須實(shí)現(xiàn) CacheOptionsFactory 的接口。
@Injectable()
class CacheConfigService implements CacheOptionsFactory {
createCacheOptions(): CacheModuleOptions {
return {
ttl: 5,
};
}
}
為了防止 CacheConfigService 內(nèi)部創(chuàng)建 CacheModule 并使用從不同模塊導(dǎo)入的提供程序,您可以使用 useExisting 語法。
CacheModule.registerAsync({
imports: [ConfigModule],
useExisting: ConfigService,
});
它和 useClass 的用法有一個(gè)關(guān)鍵的相同點(diǎn): CacheModule 將查找導(dǎo)入的模塊以重新使用已創(chuàng)建的 ConfigService 實(shí)例,而不是重復(fù)實(shí)例化。
更多建議: