NestJS 高速緩存

2023-09-08 17:41 更新

緩存是一項偉大而簡單的技術(shù),可以幫助提高應(yīng)用程序的性能。它充當(dāng)臨時數(shù)據(jù)存儲,提供高性能的數(shù)據(jù)訪問。

安裝

我們首先需要安裝所需的包:

$ npm install cache-manager
$ npm install -D @types/cache-manager

內(nèi)存緩存

Nest為各種緩存存儲提供程序提供了統(tǒng)一的 API。內(nèi)置的是內(nèi)存中的數(shù)據(jù)存儲。但是,您可以輕松地切換到更全面的解決方案,比如 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 {}

與緩存存儲的交互

為了和緩存管理器實例進行交互,需要使用CACHE_MANAGER標(biāo)記將其注入到你的類,如下所示:

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

Cache類是從cache-manager中導(dǎo)入的,而CACHE_MANAGER則是從@nestjs/common包中導(dǎo)入的。

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屬性設(shè)置為0。

await this.cacheManager.set('key', 'value', { ttl: 0 });

使用del方法從緩存中刪除一個鍵值對:

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

使用reset方法清空整個緩存:

await this.cacheManager.reset();

自動緩存響應(yīng)

在 GraphQL 應(yīng)用中,攔截器針對每個字段解析器分別運行,因此,CacheModule(使用攔截器來緩存響應(yīng))將無法正常工作。

要啟用自動緩存響應(yīng),只需在想緩存數(shù)據(jù)的地方綁定CacheInterceptor。

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

警告: 只有使用 GET 方式聲明的節(jié)點會被緩存。此外,注入本機響應(yīng)對象( @Res() )的 HTTP 服務(wù)器路由不能使用緩存攔截器。有關(guān)詳細信息,請參見響應(yīng)映射

全局緩存

為了減少重復(fù)代碼量,可以將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 {}

定制緩存

所有緩存的數(shù)據(jù)有其自己的過期時間(TTL)。要個性化不同值,將選項對象傳遞給register()方法。

CacheModule.register({
  ttl: 5, //秒
  max: 10, //緩存中最大和最小數(shù)量
});

全局緩存重載

使能全局緩存后,緩存入口存儲在基于路徑自動生成的Cachekey中。你可能需要基于每個方法重載特定的緩存設(shè)置(@CacheKey()和@CacheTTL()),允許為獨立控制器方法自定義緩存策略。這在使用不同存儲緩存時是最有意義的。

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

@CacheKey()和@CacheTTL()裝飾器從@nestjs/common包導(dǎo)入。

@CacheKey()裝飾器可以有或者沒有一個對應(yīng)的@CacheTTL()裝飾器,反之亦然。你可以選擇僅覆蓋@CacheKey()或@CacheTTL()。沒有用裝飾器覆蓋的設(shè)置將使用全局注冊的默認值(見自定義緩存)。

WebSockets 和 微服務(wù)

顯然,您可以毫不費力地使用 CacheInterceptor WebSocket 訂閱者模式以及 Microservice 的模式(無論使用何種服務(wù)間的傳輸方法)。

譯者注: 微服務(wù)架構(gòu)中服務(wù)之間的調(diào)用需要依賴某種通訊協(xié)議介質(zhì),在 nest 中不限制你是用消息隊列中間件,RPC/gRPC 協(xié)議或者對外公開 API 的 HTTP 協(xié)議。

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

然而,需要一個附加的@CacheKey()裝飾器來指定一個用于依次存儲并獲取緩存數(shù)據(jù)的鍵。注意,你不應(yīng)該緩存所有的內(nèi)容。永遠也不要去緩存那些用于實現(xiàn)業(yè)務(wù)邏輯也不是簡單地查詢數(shù)據(jù)的行為。

此外,你可以使用@CacheTTL()裝飾器來指定一個緩存過期時間(TTL),用于覆蓋全局默認的 TTL 值。

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

@CacheTTL()裝飾器可以@CacheKey()裝飾器同時或者不同時使用。

不同的存儲

服務(wù)在底層使用緩存管理器(cache-manager)。cache-manager包支持一個寬范圍的可用存儲,例如,Redis存儲。一個完整的支持存儲列表見這里。要設(shè)置Redis存儲,簡單地將該包和相應(yī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 {}

調(diào)整追蹤

默認地,Nest使用請求 URL(在一個HTTPapp 中)或者緩存鍵(在websockets和microservices應(yīng)用中,通過@CacheKey()裝飾器設(shè)置)來聯(lián)系緩存記錄和路徑。然而,有時你可能想要根據(jù)不同要素設(shè)置追蹤,例如HTTP headers(比如,確定合適profile路徑的Authorization)。

為了達到這個目的,創(chuàng)建一個CacheInterceptor的子類并覆蓋trackBy()方法。

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

異步配置

你可能想異步傳遞模塊選項來代替在編譯時靜態(tài)傳遞。在這種情況下,可以使用registerAsync()方法,它提供了不同的處理異步配置的方法。

一個方法是使用工廠函數(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)部實例化CacheConfigService并用它來得到選項對象,CacheConfigService需要使用CacheOptionsFactory接口來提供配置選項:

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

如果你希望使用在其他不同模塊中導(dǎo)入的現(xiàn)有的配置提供者,使用useExisting語法:

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

這和useClass工作模式相同,但有一個根本區(qū)別——CacheModule將查找導(dǎo)入的模塊來重用任何已經(jīng)創(chuàng)建的ConfigService,以代替自己創(chuàng)實例化。

提示: @CacheKey() 裝飾器來源于 @nestjs/common 包。

但是, @CacheKey() 需要附加裝飾器以指定用于隨后存儲和檢索緩存數(shù)據(jù)的密鑰。此外,請注意,開發(fā)者不應(yīng)該緩存所有內(nèi)容。緩存數(shù)據(jù)是用來執(zhí)行某些業(yè)務(wù)操作,而一些簡單數(shù)據(jù)查詢是不應(yīng)該被緩存的。

自定義緩存

所有緩存數(shù)據(jù)都有自己的到期時間(TTL)。要自定義默認值,請將配置選項填寫在 register()方法中。

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

不同的緩存庫

我們充分利用了緩存管理器。該軟件包支持各種實用的商店,例如Redis 商店(此處列出完整列表)。要設(shè)置 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 {}

調(diào)整跟蹤

默認情況下, Nest 通過 @CacheKey() 裝飾器設(shè)置的請求路徑(在 HTTP 應(yīng)用程序中)或緩存中的 key(在 websockets 和微服務(wù)中)來緩存記錄與您的節(jié)點數(shù)據(jù)相關(guān)聯(lián)。然而有時您可能希望根據(jù)不同因素設(shè)置跟蹤,例如,使用 HTTP 頭部字段(例如 Authorization 字段關(guān)聯(lián)身份鑒別節(jié)點服務(wù))。

為此,創(chuàng)建 CacheInterceptor 的子類并覆蓋 trackBy() 方法。

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

異步配置

通常,您可能希望異步傳遞模塊選項,而不是事先傳遞它們。在這種情況下,使用 registerAsync() 方法,提供了幾種處理異步數(shù)據(jù)的方法。

第一種可能的方法是使用工廠函數(shù):

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

顯然,我們的工廠要看起來能讓每一個調(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)部實例化為 CacheModule ,并將利用它來創(chuàng)建選項對象。在 CacheConfigService 中必須實現(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 的用法有一個關(guān)鍵的相同點: CacheModule 將查找導(dǎo)入的模塊以重新使用已創(chuàng)建的 ConfigService 實例,而不是重復(fù)實例化。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號