缓存机制与优化
在高并发场景下,合理使用缓存可以极大提升系统性能、降低数据库压力、优化用户体验。NestJS 提供了灵活的缓存机制,支持内存缓存和 Redis 等分布式缓存方案。
缓存的作用与常见方案
- 作用:减少数据库/第三方接口访问频率,加速热点数据响应,提升系统吞吐量。
- 常见方案:
- 本地内存缓存(适合单机、数据量小场景)
- Redis 分布式缓存(适合多节点、数据共享、持久化需求)
NestJS 集成缓存模块
NestJS 提供了 @nestjs/cache-manager 官方模块,支持多种缓存后端(如 memory、redis)。
1. 安装依赖
npm install --save cache-manager @nestjs/cache-manager cache-manager-redis-store redis
2. 配置缓存模块
// app.module.ts
import { Module } from '@nestjs/common';
import { CacheModule } from '@nestjs/cache-manager';
import * as redisStore from 'cache-manager-redis-store';
@Module({
imports: [
CacheModule.register({
isGlobal: true, // 全局可用
store: redisStore,
host: 'localhost',
port: 6379,
ttl: 60, // 默认缓存 60 秒
}),
],
})
export class AppModule {}
缓存装饰器与手动缓存
1. 使用 @Cacheable 装饰器自动缓存接口
// user.controller.ts
import { Controller, Get, CacheKey, CacheTTL, CacheInterceptor, UseInterceptors } from '@nestjs/common';
import { UserService } from './user.service';
@Controller('user')
@UseInterceptors(CacheInterceptor) // 启用缓存拦截器
export class UserController {
constructor(private readonly userService: UserService) {}
@Get('hot')
@CacheKey('hot_users') // 自定义缓存 key
@CacheTTL(120) // 缓存 120 秒
async getHotUsers() {
return this.userService.findHotUsers();
}
}
2. 手动读写缓存
// user.service.ts
import { Injectable, Inject } from '@nestjs/common';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
@Injectable()
export class UserService {
constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}
async getUserProfile(userId: string) {
// 先查缓存
const cacheKey = `user_profile_${userId}`;
let profile = await this.cacheManager.get(cacheKey);
if (!profile) {
// 缓存未命中,查数据库
profile = await this.findUserById(userId);
await this.cacheManager.set(cacheKey, profile, 300); // 缓存5分钟
}
return profile;
}
}
典型实战场景
- 接口数据缓存:如首页推荐、排行榜、热点新闻等高频只读接口。
- 热点数据优化:如用户信息、商品详情等,减少数据库压力。
- 分布式会话/Token 缓存:如单点登录、验证码等。
- 防止缓存击穿/雪崩:可用互斥锁、预热、过期分散等手段优化。
最佳实践与常见坑点
- 缓存 key 要唯一且有业务含义,避免覆盖和冲突。
- 缓存时间(TTL)要合理,过短失去意义,过长易数据不一致。
- 缓存失效要有降级方案,如回源数据库。
- 分布式部署建议用 Redis,保证多节点数据一致。
- 敏感数据不建议缓存,防止泄露。
- 缓存穿透/击穿/雪崩要防护,可用互斥锁、预热、限流等手段。
- 定期清理无用缓存,防止内存/Redis 膨胀。
缓存淘汰与清理策略
缓存空间有限,必须有合理的淘汰策略来保证热点数据常驻、冷数据及时清理。常见策略有:
- LRU(最近最少使用):优先淘汰最近最少被访问的数据,适合大多数通用场景。
- LFU(最不经常使用):优先淘汰访问频率最低的数据,适合热点数据极少变动的场景。
- TTL(过期时间):每条缓存设置生存时间,到期自动清除,适合数据时效性强的场景。
- 手动清理:业务代码中主动删除或刷新缓存,适合数据变更频繁、需强一致性的场景。
1. Redis 缓存淘汰策略配置
Redis 支持多种淘汰策略,可在 redis.conf 或启动参数中配置:
volatile-lru:对设置了过期时间的 key 使用 LRUallkeys-lru:对所有 key 使用 LRUvolatile-ttl:对设置了过期时间的 key 使用 TTLallkeys-random:对所有 key 随机淘汰
示例:
maxmemory 256mb
maxmemory-policy allkeys-lru
2. cache-manager 内存缓存策略
cache-manager 的内存存储默认采用 LRU 策略,自动清理最久未使用的数据。
import { CacheModule } from '@nestjs/cache-manager';
CacheModule.register({
max: 100, // 最大缓存条数,超出后自动淘汰最久未用数据
ttl: 60, // 每条缓存默认 60 秒
})
3. 手动清除缓存
// 清除指定 key
await this.cacheManager.del('user_profile_123');
// 清除所有缓存(慎用,可能影响性能)
await this.cacheManager.reset();
4. 结合 TTL + LRU 提升效果
- 推荐为热点数据设置合理 TTL,结合 LRU 策略,既保证数据新鲜度,又防止缓存膨胀。
- 对于极易变动的数据,建议主动删除或更新缓存,避免脏读。
选择合适的淘汰策略需结合业务场景、数据访问模式和系统资源,建议多查阅 Redis 淘汰策略文档 和 cache-manager 文档。
缓存是提升系统性能的利器,建议多查阅 NestJS 缓存文档 和 cache-manager 文档 掌握更多高级用法。