Skip to content

集成cache-manager缓存和redis

在NestJS中集成cache-managerredis可以通过以下步骤实现:

安装依赖

bash
pnpm install cache-manager cacheable keyv @keyv/redis

INFO

cache-manager 是一个多存储缓存管理器,支持多种存储引擎,包括内存、文件系统和Redis等。
cacheable 是一个用于缓存函数结果的库,可以与 cache-manager 一起使用。
keyv 是一个轻量级的键值存储库,支持多种存储引擎,包括Redis。
@keyv/rediskeyv 的 Redis 存储引擎。

配置缓存模块

新建 redis 模块,目录结构如下:

bash
src/
├── redis
   ├── redis.module.ts
   └── redis.service.ts

redis.module.ts

typescript
import KeyvRedis from '@keyv/redis';
import { Global, Module } from '@nestjs/common';
import { createCache } from 'cache-manager';
import { CacheableMemory } from 'cacheable';
import Keyv from 'keyv';
import { RedisService } from './redis.service';

@Global() // 使用 @Global 装饰器使模块在全局可用
@Module({
  providers: [
    RedisService,
    {
      provide: 'CACHE_MANAGER',
      useFactory: () => {
        const cache = createCache({
          stores: [
            new Keyv({
              store: new CacheableMemory({ ttl: 1000 * 60 * 30 }),
            }),
            new Keyv(new KeyvRedis(`redis://:你的密码@你的地址:你的端口`)),
          ],
        });
        return cache;
      },
    },
  ],
  exports: [RedisService, 'CACHE_MANAGER'],
})
export class RedisModule {}

这里我们创建了一个全局的 RedisModule,它提供了一个名为 CACHE_MANAGER 的缓存管理器实例。这个实例使用了 cache-managerkeyv 来支持 Redis 缓存。

其中实现了一个 useFactory 方法来创建缓存实例,并配置了两个存储引擎:一个是内存存储,另一个是 Redis 存储。 内存存储使用了 cacheable 库来缓存函数结果,默认 TTL 为 30 分钟,Redis 存储使用了 keyv 库。

redis.service.ts

typescript
import { Inject, Injectable } from '@nestjs/common';
import { CreateCache, MyRedisClient } from 'src/types';

@Injectable()
export class RedisService {
  @Inject('CACHE_MANAGER')
  private cacheManager: CreateCache;

  /** 获取 Redis 客户端 */
  async getRedisClient(): Promise<MyRedisClient> {
    const client: MyRedisClient = this.cacheManager.stores[1].store.client;
    // 检测 Redis 客户端是否连接
    if (!client || !client.isOpen) {
      await client.connect();
    }
    return client;
  }

  /**
   * @description: 获取指定模式的 Redis 键
   * @param {string} pattern - 键的匹配模式
   * @return {Promise<string[]>} - 返回匹配的键列表
   */
  async getRedisKeys(pattern: string): Promise<string[]> {
    const client = await this.getRedisClient();
    const keys = await client.keys(pattern);
    return keys;
  }

  /**
   * @description: 向 Redis Set 中添加一个值
   * @param {string} key - Set 的键
   * @param {string} value - 要添加的值
   */
  async sAdd(key: string, value: string) {
    const client = await this.getRedisClient();
    await client.sAdd(key, value);
  }

  /**
   * @description: 获取 Redis Set 中的所有成员
   * @param {string} key - Set 的键
   * @return {Promise<string[]>} - 返回 Set 中的所有成员
   */
  async sMembers(key: string): Promise<string[]> {
    const client = await this.getRedisClient();
    const members = await client.sMembers(key);
    return members;
  }

  /**
   * @description: 删除指定的 Redis Set 中的一个值
   * @param {string} key - 要删除的键
   */
  async sRem(key: string, value: string) {
    const client = await this.getRedisClient();
    await client.sRem(key, value);
  }

  /**
   * @description: 设置 Set 的过期时间
   * @param {string} key - Set 的键
   * @param {number} seconds - 过期时间(秒)
   */
  async expire(key: string, seconds: number) {
    const client = await this.getRedisClient();
    await client.expire(key, seconds);
  }

  /**
   * @description: 向 Redis Set 中添加一个值并设置过期时间
   * @param {string} key - Set 的键
   * @param {string} value - 要添加的值
   * @param {number} seconds - 过期时间(秒)
   */
  async sAddWithExpire(key: string, value: string, seconds: number) {
    const client = await this.getRedisClient();
    await client.sAdd(key, value);
    await client.expire(key, seconds);
  }
}

因为cache-manager和keyv里只有简单的键值对存储和获取方法,所以我们在 RedisService 中封装了一些常用的 Redis 操作方法,如获取 Redis 客户端、获取键列表、添加 Set 成员、获取 Set 成员、删除 Set 成员以及设置过期时间等。

使用 Redis 模块

在app.module.ts中导入 RedisModule

typescript
import { RedisModule } from 'src/redis/redis.module';

@Module({
  imports: [RedisModule],
})
export class AppModule {}

现在,你可以在任何需要使用 Redis 的地方注入 RedisService,并使用它提供的方法来操作 Redis。 在需要使用缓存的地方,你可以通过注入 CACHE_MANAGER 来使用缓存功能。以下是一个示例:

typescript
import { Controller, Get, Inject } from '@nestjs/common';
import { createCache } from 'cache-manager';
import { RedisService } from 'src/redis/redis.service';

type CreateCache = ReturnType<typeof createCache>;

@Controller('example')
export class ExampleController {
  @Inject('CACHE_MANAGER')
  private cacheManager: CreateCache;

  constructor(private readonly redisService: RedisService) {}

  @Get('cached-data')
  async getCachedData() {
    const cachedData = await this.cacheManager.get('my-key');
    if (cachedData) {
      return { data: cachedData, source: 'cache' };
    }

    // 如果缓存中没有数据,则从数据库或其他来源获取数据
    const data = { message: 'Hello, World!' }; // 模拟从数据库获取数据

    // 将数据存入缓存
    await this.cacheManager.set('my-key', data, { ttl: 1000 * 60 * 5 }); // 缓存5分钟

    // 如果需要存储高级数据结构,可以使用 Redis Service 例如 Set
    await this.redisService.sAddWithExpire('my-set', JSON.stringify(data), 1000 * 60 * 5);

    return { data, source: 'database' };
  }
}