Skip to content

好的,我们来探讨一个能让你的应用“飞”起来的性能优化神技:缓存 (Caching)。这就像是为你的应用配备了一位拥有“过目不忘”超能力的私人助理,极大减少重复的辛勤劳动。

为你的应用配备“超级大脑”:NestJS 缓存技术完全解析

想象一下你的应用是一位才华横溢但有点健忘的数学家。

每次有人问他 “135 乘以 789 是多少?” (一个耗时的计算数据库查询),他都会拿出草稿纸,辛辛苦苦地从头算一遍,然后给出答案。如果一秒钟之内有十个人问同一个问题,他就会傻乎乎地重复计算十次,累得满头大汗(服务器资源被大量消耗)。

现在,我们给他配备了一位拥有“过目不忘”超能力的私人助理(缓存系统)。

  • 第一次有人问 “135 乘以 789 是多少?”时,数学家还是会亲自计算,得出结果是 106515
  • 在他给出答案的同时,这位聪明的助理会立刻拿出小本子(缓存存储,如内存或 Redis),记下:“问题:‘135x789’,答案:‘106515’”。
  • 下一次,当再有人问同一个问题时,助理会立刻伸手拦住正要动笔的数学家,笑着说:“这个问题我记下了,答案是 106515!”然后直接给出答案。

数学家完全不用再次计算,节省了大量的时间和精力。这就是缓存的魔力:将耗时操作的结果存储起来,以便在后续相同的请求中能够快速地直接返回,从而降低延迟、减少数据库负载、提升应用性能。

NestJS 提供了一个内置的、与框架无缝集成的 @nestjs/cache-manager 模块,让实现缓存变得异常简单。

1. 第一步:聘请“私人助理” (CacheModule)

第一步:安装依赖

bash
npm install @nestjs/cache-manager

cache-manager 是一个非常流行的 Node.js 缓存库,@nestjs/cache-manager 是 NestJS 官方对它的封装。

第二步:在 AppModule 中注册 CacheModule

和其他模块一样,我们需要在根模块中配置它。

src/app.module.ts

typescript
import { Module } from '@nestjs/common';
import { CacheModule } from '@nestjs/cache-manager';

@Module({
  imports: [
    // 使用 .register() 进行配置
    CacheModule.register({
      isGlobal: true, // 关键!将 CacheModule 设为全局模块
      ttl: 5 * 1000, // 缓存的默认“存活时间”(Time To Live),单位是毫秒。这里是 5 秒。
      max: 100, // 缓存中最多可以存储多少个项目
    }),
  ],
  // ...
})
export class AppModule {}
  • isGlobal: true: 同样,设为全局后,我们可以在任何地方直接注入缓存相关的服务,无需在每个模块中重复导入 CacheModule
  • ttl: 这是非常重要的一个配置。它告诉助理:“记下的答案最多保留 5 秒,5 秒后如果还有人问,就让数学家重新算一遍,以防规则变了。”这保证了数据的“新鲜度”。

默认情况下,cache-manager 使用内存作为缓存存储。这意味着缓存数据会随着应用的重启而丢失。对于生产环境,你通常会配置一个更持久化的缓存存储,比如 Redis

2. 第二步:让助理“自动工作” (使用 CacheInterceptor)

这是实现缓存最简单、最优雅的方式。CacheInterceptor 是一个拦截器,你可以用它来自动缓存整个路由处理函数的响应

src/app.controller.ts

typescript
import { Controller, Get, UseInterceptors } from '@nestjs/common';
import { CacheInterceptor } from '@nestjs/cache-manager';

@Controller()
// 将拦截器应用在整个控制器上
// 这意味着这个控制器下的所有 GET 请求都会被自动缓存
@UseInterceptors(CacheInterceptor)
export class AppController {
  @Get('time-consuming')
  getHello(): string {
    console.log('⏰ 数学家正在进行一次非常耗时的计算...');
    // 模拟一个耗时的操作
    const result = new Date().toLocaleTimeString();
    return `计算结果是:${result}`;
  }
}

现在,让我们来见证奇迹:

  1. 启动应用,然后用浏览器或 Postman 第一次访问 http://localhost:3000/time-consuming

    • 控制台会打印:“⏰ 数学家正在进行一次非常耗时的计算...”。
    • 你会收到响应,比如 计算结果是:10:30:05 AM
    • 此时,CacheInterceptor 助理已经把这个结果记在了小本子上,缓存键通常是请求的 URL (/time-consuming)。
  2. 在 5 秒内,立即再次访问同一个 URL。

    • 控制台什么都不会打印! 数学家根本没有被惊动!
    • 你依然会收到完全相同的响应计算结果是:10:30:05 AM。这是助理直接从他的小本子上读出来的。
  3. 等待超过 5 秒后,再次访问。

    • 你会发现控制台又一次打印了“⏰ 数学家正在进行一次非常耗时的计算...”。
    • 因为之前的缓存已经过期(超过了我们设置的 5 秒 ttl),助理会让数学家重新计算,并记下新的结果。

CacheInterceptor 自动处理了缓存的读取、写入和过期,对于那些不经常变化的 GET 请求接口,它简直是性能优化的神器。

3. 第三步:“手动指挥”助理 (直接使用 CacheManager)

有时候,我们不想缓存整个 HTTP 响应,而是想在服务内部更精细地控制缓存的存取。比如,缓存一个复杂的数据库查询结果。

这时,我们可以直接注入 Cache 对象。

src/app.service.ts

typescript
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Inject, Injectable } from '@nestjs/common';
import { Cache } from 'cache-manager';

@Injectable()
export class AppService {
  // 使用 @Inject(CACHE_MANAGER) 注入缓存管理器实例
  constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}

  async getHeavyCalculationResult(input: number): Promise<string> {
    const cacheKey = `calc_${input}`;

    // 1. 先尝试从缓存中获取结果
    const cachedResult = await this.cacheManager.get<string>(cacheKey);
    if (cachedResult) {
      console.log(`👍 从缓存中命中结果! Key: ${cacheKey}`);
      return `[来自缓存] ${cachedResult}`;
    }

    // 2. 如果缓存中没有,则执行实际的计算
    console.log(`⏰ 缓存未命中,执行实际计算... Key: ${cacheKey}`);
    const result = `结果是 ${input * input}`;
    await new Promise(resolve => setTimeout(resolve, 1000)); // 模拟1秒的耗时计算

    // 3. 将计算结果存入缓存,以便下次使用
    // 第三个参数可以覆盖全局的 ttl
    await this.cacheManager.set(cacheKey, result, 10 * 1000); // 这个缓存存 10 秒
    
    return `[来自计算] ${result}`;
  }
}

Cache 对象的核心方法:

  • cacheManager.get(key): 根据键获取缓存。如果不存在或已过期,返回 undefined
  • cacheManager.set(key, value, ttl): 设置一个缓存项,可以指定一个独立的 ttl
  • cacheManager.del(key): 从缓存中删除一个项。
  • cacheManager.reset(): 清空所有缓存。

这个模式给予了你完全的控制权,让你可以在代码的任何地方实现复杂的缓存逻辑。

总结

缓存是提升应用性能和可伸缩性的关键策略。NestJS 通过 @nestjs/cache-manager 提供了强大而易用的工具。

当你想要...你应该使用...工作模式
快速、自动地缓存整个 GET 接口的响应@UseInterceptors(CacheInterceptor)声明式、自动化。你只需要告诉助理“这个房间的谈话内容需要记下来”,他就会自动处理一切。
在服务层进行精细的、自定义的缓存控制注入 Cache 对象 (@Inject(CACHE_MANAGER))命令式、手动化。你亲自指挥助理:“把这个问题的答案记下来”、“去查查那个问题的答案还在不在”。

何时使用缓存?

  • 读取频繁、写入较少的数据。
  • 计算成本高昂的操作结果。
  • 可以接受一定程度“过时”(非实时)数据返回的场景。

合理地运用缓存,就像是为你应用的核心业务逻辑(数学家)配备了一个得力的助手,能极大地分担他的压力,让他能更专注于处理真正需要创造力的、全新的挑战。