好的,我们来探讨一个能让你的应用“飞”起来的性能优化神技:缓存 (Caching)。这就像是为你的应用配备了一位拥有“过目不忘”超能力的私人助理,极大减少重复的辛勤劳动。
为你的应用配备“超级大脑”:NestJS 缓存技术完全解析
想象一下你的应用是一位才华横溢但有点健忘的数学家。
每次有人问他 “135 乘以 789 是多少?” (一个耗时的计算或数据库查询),他都会拿出草稿纸,辛辛苦苦地从头算一遍,然后给出答案。如果一秒钟之内有十个人问同一个问题,他就会傻乎乎地重复计算十次,累得满头大汗(服务器资源被大量消耗)。
现在,我们给他配备了一位拥有“过目不忘”超能力的私人助理(缓存系统)。
- 第一次有人问 “135 乘以 789 是多少?”时,数学家还是会亲自计算,得出结果是
106515
。 - 在他给出答案的同时,这位聪明的助理会立刻拿出小本子(缓存存储,如内存或 Redis),记下:“问题:‘135x789’,答案:‘106515’”。
- 下一次,当再有人问同一个问题时,助理会立刻伸手拦住正要动笔的数学家,笑着说:“这个问题我记下了,答案是
106515
!”然后直接给出答案。
数学家完全不用再次计算,节省了大量的时间和精力。这就是缓存的魔力:将耗时操作的结果存储起来,以便在后续相同的请求中能够快速地直接返回,从而降低延迟、减少数据库负载、提升应用性能。
NestJS 提供了一个内置的、与框架无缝集成的 @nestjs/cache-manager
模块,让实现缓存变得异常简单。
1. 第一步:聘请“私人助理” (CacheModule
)
第一步:安装依赖
npm install @nestjs/cache-manager
cache-manager
是一个非常流行的 Node.js 缓存库,@nestjs/cache-manager
是 NestJS 官方对它的封装。
第二步:在 AppModule
中注册 CacheModule
和其他模块一样,我们需要在根模块中配置它。
src/app.module.ts
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
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}`;
}
}
现在,让我们来见证奇迹:
启动应用,然后用浏览器或 Postman 第一次访问
http://localhost:3000/time-consuming
。- 控制台会打印:“⏰ 数学家正在进行一次非常耗时的计算...”。
- 你会收到响应,比如
计算结果是:10:30:05 AM
。 - 此时,
CacheInterceptor
助理已经把这个结果记在了小本子上,缓存键通常是请求的 URL (/time-consuming
)。
在 5 秒内,立即再次访问同一个 URL。
- 控制台什么都不会打印! 数学家根本没有被惊动!
- 你依然会收到完全相同的响应:
计算结果是:10:30:05 AM
。这是助理直接从他的小本子上读出来的。
等待超过 5 秒后,再次访问。
- 你会发现控制台又一次打印了“⏰ 数学家正在进行一次非常耗时的计算...”。
- 因为之前的缓存已经过期(超过了我们设置的 5 秒
ttl
),助理会让数学家重新计算,并记下新的结果。
CacheInterceptor
自动处理了缓存的读取、写入和过期,对于那些不经常变化的 GET 请求接口,它简直是性能优化的神器。
3. 第三步:“手动指挥”助理 (直接使用 CacheManager
)
有时候,我们不想缓存整个 HTTP 响应,而是想在服务内部更精细地控制缓存的存取。比如,缓存一个复杂的数据库查询结果。
这时,我们可以直接注入 Cache
对象。
src/app.service.ts
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) ) | 命令式、手动化。你亲自指挥助理:“把这个问题的答案记下来”、“去查查那个问题的答案还在不在”。 |
何时使用缓存?
- 读取频繁、写入较少的数据。
- 计算成本高昂的操作结果。
- 可以接受一定程度“过时”(非实时)数据返回的场景。
合理地运用缓存,就像是为你应用的核心业务逻辑(数学家)配备了一个得力的助手,能极大地分担他的压力,让他能更专注于处理真正需要创造力的、全新的挑战。