NestJS 整合 Redis 特性详解
本文档详细介绍了 NestJS 项目中 Redis 缓存的整合方式、自动降级机制以及本地内存缓存特性。
📋 目录
- Redis 缓存配置
- 自动降级机制
- 本地内存缓存特性
- 性能对比分析
- 实际应用场景
- 配置优化建议
- 故障排查指南
1. Redis 缓存配置
📁 文件位置
- 配置文件:
src/config/redis.config.ts
- 模块配置:
src/cache/cache.module.ts
- 类型定义:
src/common/types/config.types.ts
🔧 配置内容
Redis 配置接口:
export interface RedisConfig {host: string; // Redis 主机地址port: number; // Redis 端口password?: string; // Redis 密码(可选)db: number; // Redis 数据库索引connectTimeout: number; // 连接超时时间commandTimeout: number; // 命令超时时间
}
环境变量配置:
# Redis 配置 - 开发环境
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_DB=0
REDIS_CONNECT_TIMEOUT=10000
REDIS_COMMAND_TIMEOUT=5000
模块配置:
@Module({imports: [CacheModule.registerAsync<RedisClientOptions>({imports: [ConfigModule],inject: [ConfigService],useFactory: async (configService: ConfigService) => {const redisConfig = configService.get<RedisConfig>('redis');return {store: redisStore, // Redis 存储引擎host: redisConfig?.host || 'localhost',port: redisConfig?.port || 6379,password: redisConfig?.password,db: redisConfig?.db || 0,connectTimeout: redisConfig?.connectTimeout || 10000,commandTimeout: redisConfig?.commandTimeout || 5000,ttl: 60 * 60, // 默认1小时过期max: 1000, // 最大缓存项数// Redis 连接选项socket: {connectTimeout: redisConfig?.connectTimeout || 10000,commandTimeout: redisConfig?.commandTimeout || 5000,},// 错误处理配置retryDelayOnFailover: 100,enableReadyCheck: true,maxRetriesPerRequest: 3,};},isGlobal: true, // 全局模块}),],exports: [CacheModule],
})
export class AppCacheModule {}
2. 自动降级机制
🎯 核心特性
NestJS 的 Redis 缓存模块具有强大的自动降级能力,当 Redis 服务不可用时,会自动切换到本地内存缓存。
🔄 降级流程
// 1. 初始化阶段
CacheModule.registerAsync({store: redisStore, // 尝试使用 Redis 存储// ... 其他配置
});// 2. 连接检测
// cache-manager-redis-store 内部机制
class RedisStore {async connect() {try {// 尝试连接 Redisawait this.redisClient.connect();this.isConnected = true;console.log('✅ Redis 连接成功');} catch (error) {// 连接失败,自动降级到内存存储this.isConnected = false;this.memoryStore = new Map();console.log('⚠️ Redis 连接失败,降级到内存缓存');}}async set(key, value, ttl) {if (this.isConnected) {// 使用 Redis 存储await this.redisClient.setex(key, ttl, value);} else {// 使用内存存储this.memoryStore.set(key, value);setTimeout(() => {this.memoryStore.delete(key);}, ttl * 1000);}}
}
📊 降级触发条件
触发条件 | 描述 | 降级行为 |
---|---|---|
Redis 服务未启动 | ECONNREFUSED 错误 | 自动切换到内存存储 |
网络连接超时 | 连接超时 | 自动切换到内存存储 |
Redis 服务异常 | 服务崩溃或重启 | 自动切换到内存存储 |
认证失败 | 密码错误 | 自动切换到内存存储 |
🛡️ 容错机制
// 错误处理配置
{retryDelayOnFailover: 100, // 故障转移重试延迟enableReadyCheck: true, // 启用就绪检查maxRetriesPerRequest: 3, // 最大重试次数connectTimeout: 10000, // 连接超时时间commandTimeout: 5000, // 命令超时时间
}
3. 本地内存缓存特性
🧠 内存存储机制
当 Redis 不可用时,系统自动使用本地内存作为缓存存储。
内存缓存配置:
{ttl: 60 * 60, // 1小时过期时间max: 1000, // 最大1000个缓存项// 使用 LRU (Least Recently Used) 算法// 当超过1000项时,自动删除最久未使用的项
}
📈 内存占用分析
缓存项数量 | 平均大小 | 总内存占用 | 说明 |
---|---|---|---|
100项 | 1KB | ~100KB | 轻量级缓存 |
500项 | 5KB | ~2.5MB | 中等规模缓存 |
1000项 | 10KB | ~10MB | 最大配置 |
实际使用 | 1-5KB | 几百KB-几MB | 典型应用场景 |
⚡ 性能优势
// 内存缓存性能特点
✅ 极快速度 - 直接内存访问,微秒级响应
✅ 零网络延迟 - 无网络IO开销
✅ 简单部署 - 无需额外服务
✅ 低资源消耗 - 只需几MB内存
✅ 开发友好 - 开发环境无需Redis
🔄 自动清理机制
// LRU 淘汰策略
class MemoryStore {private cache = new Map();private maxSize = 1000;set(key, value, ttl) {// 1. 检查容量限制if (this.cache.size >= this.maxSize) {// 删除最久未使用的项const firstKey = this.cache.keys().next().value;this.cache.delete(firstKey);}// 2. 设置缓存项this.cache.set(key, {value,timestamp: Date.now(),ttl: ttl * 1000});// 3. 设置过期清理setTimeout(() => {this.cache.delete(key);}, ttl * 1000);}
}
4. 性能对比分析
📊 性能指标对比
指标 | Redis 缓存 | 内存缓存 | 优势方 |
---|---|---|---|
响应时间 | 0.1-1ms | 0.001-0.01ms | 内存缓存 |
吞吐量 | 10,000-50,000 ops/sec | 100,000+ ops/sec | 内存缓存 |
网络延迟 | 有网络IO | 无网络IO | 内存缓存 |
内存占用 | 独立进程 | 应用进程内 | 内存缓存 |
数据持久化 | 支持 | 不支持 | Redis |
分布式共享 | 支持 | 不支持 | Redis |
高可用性 | 主从复制 | 单点故障 | Redis |
🎯 适用场景分析
内存缓存适用场景:
✅ 单机应用
✅ 开发环境
✅ 高频读取的临时数据
✅ 对延迟敏感的应用
✅ 简单的缓存需求
Redis 缓存适用场景:
✅ 分布式应用
✅ 多服务实例
✅ 需要数据持久化
✅ 会话存储
✅ 分布式锁
✅ 消息队列
✅ 实时数据统计
5. 实际应用场景
🔐 用户认证缓存
@Injectable()
export class AuthService {constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}async validateUser(username: string, password: string) {const cacheKey = `user:${username}`;// 1. 尝试从缓存获取let user = await this.cacheManager.get(cacheKey);if (!user) {// 2. 从数据库查询user = await this.userRepository.findByUsername(username);// 3. 缓存用户信息(15分钟)await this.cacheManager.set(cacheKey, user, 900);}return user;}
}
📊 数据查询缓存
@Injectable()
export class UserService {constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}async getUserList(page: number, size: number) {const cacheKey = `users:page:${page}:size:${size}`;// 1. 检查缓存let users = await this.cacheManager.get(cacheKey);if (!users) {// 2. 数据库查询users = await this.userRepository.findMany({skip: (page - 1) * size,take: size});// 3. 缓存结果(5分钟)await this.cacheManager.set(cacheKey, users, 300);}return users;}
}
🔄 会话管理
@Injectable()
export class SessionService {constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}async createSession(userId: string, sessionData: any) {const sessionId = generateSessionId();const cacheKey = `session:${sessionId}`;// 缓存会话数据(24小时)await this.cacheManager.set(cacheKey, {userId,...sessionData,createdAt: new Date()}, 86400);return sessionId;}async getSession(sessionId: string) {const cacheKey = `session:${sessionId}`;return await this.cacheManager.get(cacheKey);}
}
6. 配置优化建议
🎯 开发环境优化
// 开发环境:使用内存缓存
CacheModule.register({ttl: 60 * 60, // 1小时max: 1000, // 最大1000项// 不配置 store,默认使用内存存储
});
🚀 生产环境优化
// 生产环境:使用 Redis 集群
CacheModule.registerAsync({useFactory: async (configService: ConfigService) => {return {store: redisStore,host: configService.get('REDIS_HOST'),port: configService.get('REDIS_PORT'),password: configService.get('REDIS_PASSWORD'),// 集群配置cluster: {nodes: [{ host: 'redis-node1', port: 6379 },{ host: 'redis-node2', port: 6379 },{ host: 'redis-node3', port: 6379 },],options: {redisOptions: {password: configService.get('REDIS_PASSWORD'),},},},// 性能优化connectTimeout: 5000,commandTimeout: 3000,retryDelayOnFailover: 100,maxRetriesPerRequest: 3,};},
});
⚙️ 环境变量配置
# 开发环境
NODE_ENV=development
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
CACHE_TTL=3600
CACHE_MAX_ITEMS=1000# 生产环境
NODE_ENV=production
REDIS_HOST=redis-cluster.example.com
REDIS_PORT=6379
REDIS_PASSWORD=your-secure-password
CACHE_TTL=7200
CACHE_MAX_ITEMS=10000
7. 故障排查指南
🔍 常见问题诊断
问题1: Redis 连接失败
# 检查 Redis 服务状态
telnet localhost 6379# 检查 Redis 进程
ps aux | grep redis# 检查 Redis 日志
tail -f /var/log/redis/redis-server.log
问题2: 缓存不生效
// 检查缓存配置
console.log('🔧 Redis 缓存配置:', {host: redisConfig?.host,port: redisConfig?.port,db: redisConfig?.db,
});// 检查缓存操作
const cacheKey = 'test:key';
await cacheManager.set(cacheKey, 'test-value', 60);
const value = await cacheManager.get(cacheKey);
console.log('缓存测试结果:', value);
问题3: 内存占用过高
// 监控内存使用
setInterval(() => {const memUsage = process.memoryUsage();console.log('内存使用情况:', {rss: Math.round(memUsage.rss / 1024 / 1024) + 'MB',heapUsed: Math.round(memUsage.heapUsed / 1024 / 1024) + 'MB',heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024) + 'MB',});
}, 30000);
🛠️ 调试工具
缓存状态检查:
@Controller('debug')
export class DebugController {constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}@Get('cache-status')async getCacheStatus() {try {// 测试缓存写入await this.cacheManager.set('test:key', 'test-value', 60);// 测试缓存读取const value = await this.cacheManager.get('test:key');return {status: 'healthy',testValue: value,timestamp: new Date().toISOString()};} catch (error) {return {status: 'error',error: error.message,timestamp: new Date().toISOString()};}}
}
📈 性能监控
// 缓存性能监控
@Injectable()
export class CacheMonitorService {private metrics = {hits: 0,misses: 0,errors: 0,avgResponseTime: 0};async monitorCacheOperation<T>(operation: () => Promise<T>,operationName: string): Promise<T> {const startTime = Date.now();try {const result = await operation();const responseTime = Date.now() - startTime;this.metrics.hits++;this.metrics.avgResponseTime =(this.metrics.avgResponseTime + responseTime) / 2;console.log(`✅ 缓存操作成功: ${operationName}, 耗时: ${responseTime}ms`);return result;} catch (error) {this.metrics.errors++;console.error(`❌ 缓存操作失败: ${operationName}`, error);throw error;}}getMetrics() {return {...this.metrics,hitRate: this.metrics.hits / (this.metrics.hits + this.metrics.misses)};}
}
📝 总结
NestJS 的 Redis 缓存整合具有以下核心特性:
- 自动降级机制 - Redis 不可用时自动切换到内存缓存
- 透明服务 - 应用代码无需感知底层存储变化
- 高性能 - 内存缓存提供微秒级响应
- 容错能力 - 优雅处理各种故障场景
- 开发友好 - 开发环境无需额外服务
这种设计使得应用既能享受 Redis 的分布式特性,又能在 Redis 不可用时保持高可用性,是现代微服务架构的理想选择。