缓存
本教程共 47 篇 · 第 30 篇 · 更新于 2026-08-09 · 约 11 分钟阅读
本节目标:掌握 NestJS 中的缓存技术,包括内存缓存、自动缓存响应、Redis 集成和缓存策略配置。
有些接口特别耗性能。查数据库、调第三方 API、跑复杂计算,每次都从头来一遍,用户等得急,服务器也扛不住。
缓存就是解决这个问题的。把算好的结果存起来,下次直接取,不用重新算。就像你把常用电话号码存到通讯录,不用每次都翻电话簿。
NestJS 提供了开箱即用的缓存方案。
安装依赖
npm install @nestjs/cache-manager cache-manager
默认是内存缓存,数据存在应用内存里。如果想用 Redis,还得装额外的包,后面会讲。
基础用法
启用缓存模块
在根模块里导入 CacheModule:
import { Module } from '@nestjs/common';
import { CacheModule } from '@nestjs/cache-manager';
@Module({
imports: [CacheModule.register()],
})
export class AppModule {}
这样缓存就启用了,默认配置是内存存储,永不过期。
手动操作缓存
注入 CACHE_MANAGER 就能用了:
import { Injectable, Inject } from '@nestjs/common';
import { CACHE_MANAGER, Cache } from '@nestjs/cache-manager';
@Injectable()
export class AppService {
constructor(
@Inject(CACHE_MANAGER)
private cacheManager: Cache,
) {}
async getData() {
// 先查缓存
const cached = await this.cacheManager.get('my-key');
if (cached) {
return cached;
}
// 缓存没有,去查数据库
const data = await this.fetchFromDatabase();
// 存到缓存,设置 60 秒过期
await this.cacheManager.set('my-key', data, 60000);
return data;
}
async clearCache() {
// 删除某个缓存
await this.cacheManager.del('my-key');
// 清空所有缓存
await this.cacheManager.clear();
}
}
Note
get()方法在缓存不存在时返回undefined(v6 及之前版本返回null)。判断的时候用 falsy 就行。
设置过期时间
TTL(Time To Live)就是缓存的保质期,单位是毫秒:
// 60 秒后过期
await this.cacheManager.set('key', 'value', 60000);
// 永不过期
await this.cacheManager.set('key', 'value', 0);
自动缓存响应
每次手动 get/set 太麻烦。NestJS 提供了 CacheInterceptor,一行代码搞定接口缓存。
单个接口缓存
import { Controller, Get, UseInterceptors } from '@nestjs/common';
import { CacheInterceptor } from '@nestjs/cache-manager';
@Controller('users')
@UseInterceptors(CacheInterceptor)
export class UsersController {
@Get()
findAll() {
// 这个接口的响应会被自动缓存
return this.usersService.findAll();
}
}
Warning只有
GET请求会被缓存。用了@Res()的接口不能用缓存拦截器。
全局缓存
给所有 GET 接口都加上缓存:
import { Module } from '@nestjs/common';
import { CacheModule, CacheInterceptor } from '@nestjs/cache-manager';
import { APP_INTERCEPTOR } from '@nestjs/core';
@Module({
imports: [CacheModule.register()],
providers: [
{
provide: APP_INTERCEPTOR,
useClass: CacheInterceptor,
},
],
})
export class AppModule {}
这样所有 GET 接口的响应都会被缓存。
自定义缓存策略
设置全局 TTL
CacheModule.register({
ttl: 5000, // 全局 5 秒过期
});
自定义缓存键和 TTL
有时候需要更细粒度的控制:
import { Controller, Get } from '@nestjs/common';
import { CacheKey, CacheTTL } from '@nestjs/cache-manager';
@Controller('users')
export class UsersController {
@Get()
@CacheKey('users-list')
@CacheTTL(20000) // 20 秒过期
findAll() {
return this.usersService.findAll();
}
}
也可以在控制器级别设置 TTL:
@Controller('users')
@CacheTTL(5000) // 整个控制器 5 秒
export class UsersController {
@Get()
@CacheTTL(20000) // 这个方法 20 秒,优先级更高
findAll() {
return [];
}
}
Tip方法级别的
@CacheTTL()会覆盖控制器级别的设置。
自定义缓存键生成规则
默认情况下,缓存键是请求的 URL。但有时候你想根据其他因素来区分,比如用户身份:
import { Injectable } from '@nestjs/common';
import { CacheInterceptor } from '@nestjs/cache-manager';
import { ExecutionContext } from '@nestjs/common';
@Injectable()
class CustomCacheInterceptor extends CacheInterceptor {
trackBy(context: ExecutionContext): string | undefined {
const request = context.switchToHttp().getRequest();
const userId = request.user?.id;
// 不同用户缓存不同
return userId
? `${request.url}:user:${userId}`
: request.url;
}
}
WebSockets 和微服务
缓存拦截器也能用在 WebSocket 和微服务里:
@CacheKey('events')
@UseInterceptors(CacheInterceptor)
@SubscribeMessage('events')
handleEvent(client: Client, data: string[]): Observable<string[]> {
return [];
}
NoteWebSocket 和微服务里必须用
@CacheKey()指定缓存键,不然没法缓存。
使用 Redis
内存缓存有个问题:重启应用缓存就没了。生产环境通常用 Redis。
安装 Redis 驱动
npm install @keyv/redis
配置 Redis
import { Module } from '@nestjs/common';
import { CacheModule } from '@nestjs/cache-manager';
import KeyvRedis from '@keyv/redis';
import { Keyv } from 'keyv';
import { KeyvCacheableMemory } from 'cacheable';
@Module({
imports: [
CacheModule.registerAsync({
useFactory: async () => {
return {
stores: [
// 内存缓存作为第一层
new Keyv({
store: new KeyvCacheableMemory({
ttl: 60000,
lruSize: 5000
}),
}),
// Redis 作为第二层
new KeyvRedis('redis://localhost:6379'),
],
};
},
}),
],
})
export class AppModule {}
这种多层缓存的策略很常见。内存缓存速度快,Redis 容量大。先查内存,没有再查 Redis。
异步配置
从配置文件或环境变量读取 Redis 地址:
CacheModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
stores: [
new KeyvRedis(configService.get('REDIS_URL')),
],
}),
inject: [ConfigService],
})
全局模块
不想每个模块都导入 CacheModule?设为全局模块:
CacheModule.register({
isGlobal: true,
});
这样在根模块导入一次,其他地方就能直接用了。
踩坑经验
坑1:缓存了不该缓存的东西
不是所有接口都适合缓存。涉及写操作的接口、实时性要求高的数据,千万别缓存。
坑2:缓存雪崩
大量缓存同时过期,请求全部打到数据库,服务器直接挂掉。解决办法是给 TTL 加个随机值:
const ttl = 60000 + Math.random() * 10000; // 60-70秒随机
await this.cacheManager.set('key', 'value', ttl);
坑3:缓存穿透
查询一个不存在的数据,每次都会穿透到数据库。可以用布隆过滤器或者缓存空值来解决。
坑4:内存泄漏
内存缓存如果不设置 TTL,数据会越来越多,最终内存溢出。一定要设置合理的过期时间。
坑5:缓存一致性
数据更新了,缓存没更新,用户看到的还是旧数据。常见策略:
- 写操作时删除相关缓存
- 设置较短的 TTL
- 使用缓存失效通知机制
async updateUser(id: number, data: UpdateUserDto) {
// 更新数据库
await this.db.update(id, data);
// 删除缓存
await this.cacheManager.del(`user:${id}`);
}
小结
缓存是提升性能的利器。关键知识点回顾:
CacheModule.register()启用缓存CACHE_MANAGER注入手动操作CacheInterceptor自动缓存响应@CacheKey()和@CacheTTL()自定义策略- 生产环境用 Redis 做持久化缓存
缓存虽好,但别滥用。想清楚哪些数据适合缓存、缓存多久、怎么失效,这些比会用 API 更重要。
下一章是教程的最后一章,我们聊聊定时任务。