前言
大家好 我是elk。今天,我们将深入探讨如何在 NestJs 项目中集成和配置 Redis,为我们的应用添加缓存、消息队列等高效的功能。
Redis 是一个非常强大的内存数据结构存储系统,它不仅支持高效的键值存储,还提供了多种数据结构,比如字符串、哈希、列表、集合和有序集合等。这些特性使得 Redis 在缓存、消息队列、实时统计等场景中表现出色。
所需插件
css
pnpm install --save ioredis @nestjs-module/ioredis
目录结构
arduino
-src
-config
redis.config.ts
-module
-commmon
-redis
redis.module.ts
redis.service.ts
-env.development
.env.production
环境配置文件
1、将通用配置定义在.env文件中
实例.env.development
ini
# Redis配置(开发环境)
REDIS_HOST=127.0.0.1 # 本地开发就用这个
REDIS_PORT=6379 # 默认端口
REDIS_PASSWORD=123456 # 生产环境记得用复杂密码!
REDIS_PREFIX=elk_ # 防止多项目冲突
REDIS_DB=0 # 默认用0号库
2、创建redis.config.ts配置文件,编写存放redis通用配置
typescript
// 从@nestjs/config模块导入registerAs函数,用于注册配置命名空间
import { registerAs } from '@nestjs/config';
export interface RedisConfig {
host: string;
port: number;
password: string;
db: number;
prefix: string;
ttl: number;
retryStrategy: (times: number) => number;
}
export default registerAs('redis', () => ({
host: process.env.REDIS_HOST, // Redis服务器地址
port: Number(process.env.REDIS_PORT), // Redis服务器端口
password: process.env.REDIS_PASSWORD, // Redis访问密码
db: process.env.DB, // 使用的数据库编号
prefix: process.env.REDIS_PREFIX, // 键名前缀
ttl: 60 * 60 * 24 * 7, // 默认7天,键值过期时间(秒)
retryStrategy: (times: number) => {
// 重试策略,times为当前重试次数
return Math.min(times * 100, 3000); // 每次重试间隔时间(毫秒),最大不超过3000ms
},
}));
创建redis核心模块
模块定义
- redis.module.ts
typescript
// 从@nestjs/common导入核心模块装饰器
import { Module, Global } from '@nestjs/common';
// 导入Redis服务
import { RedisService } from './redis.service';
// 导入ioredis模块并重命名为IORedisModule
import { RedisModule as IORedisModule } from '@nestjs-modules/ioredis';
// 导入配置模块和服务
import { ConfigModule, ConfigService } from '@nestjs/config';
// 将当前模块标记为全局模块,使其在整个应用中可用
@Global()
@Module({
imports: [
// 异步配置ioredis模块
IORedisModule.forRootAsync({
imports: [ConfigModule], // 依赖配置模块
inject: [ConfigService], // 注入配置服务
useFactory: (configService: ConfigService) => {
// 从配置服务中获取redis配置
const { host, port, password, db, prefix, ttl } =
configService.get('redis');
return {
type: 'single', // 使用单实例模式
options: {
host,
port,
password,
db,
prefix,
ttl,
},
};
},
}),
],
providers: [RedisService], // 注册服务提供者
exports: [RedisService], // 导出服务,使其可被其他模块使用
})
export class RedisModule {}
服务层封装
- redis.service.ts
typescript
import { Injectable } from '@nestjs/common';
import { Redis } from 'ioredis';
import { InjectRedis } from '@nestjs-modules/ioredis';
@Injectable()
export class RedisService {
constructor(@InjectRedis() private readonly redis: Redis) {}
// 根据key获取存储在Redis中的值
async get<T>(key: string): Promise<T | null> {
const data = await this.redis.get(key);
return data ? JSON.parse(data) : null;
}
// 设置Redis键值对,value可以是字符串或数字,可选设置过期时间(秒)
async set(key: string, value: string | number, ttl?: number): Promise<void> {
await this.redis.set(key, value);
if (ttl) {
await this.redis.expire(key, ttl);
}
}
// 删除指定key的Redis数据
async del(key: string): Promise<void> {
await this.redis.del(key);
}
}
上篇文章中的环境配置中,配置了数据库的配置「database.config.ts」当时抛出的配置函数就是使用registerAs抛出,涉及多个配置文件的时候,可以通过这个函数注册一个配置命名空间,到时候可以通过ConfigService.get("database") || ConfigService.get("redis"),分别获取对应的配置信息
应用案例
用户服务中的应用
- user.service.ts
typescript
// 引入redis服务
import { RedisService } from '@/module/common/redis/redis.service';
export class UserService {
constructor(
private redis: RedisService
) {}
}
async findOne(id: string) {
const cacheKey = `user:${id}`;
// 先查缓存
const cachedUser = await this.redis.get<User>(cacheKey);
if (cachedUser) return cachedUser;
// 缓存未命中,查数据库
const user = await this.prisma.sys_user.findUnique({
where: { id }
});
if (!user) throw new NotFoundException();
// 写入缓存(5分钟过期)
await this.redis.set(cacheKey, user, 300);
return user;
}
接口防刷案例
typescript
async function checkRequestFrequency(userId: string) {
const key = `rate_limit:${userId}`;
const count = await this.redis.incr(key);
if (count === 1) {
// 第一次请求设置60秒过期
await this.redis.expire(key, 60);
}
if (count > 100) {
throw new Error('操作过于频繁,请稍后再试');
}
}
常见问题排查
连接失败怎么办?
- 检查Redis服务是否启动:
redis-cli ping
应该返回 PONG - 确认防火墙开放了Redis端口
- 检查密码是否正确(如果有设置)
数据不更新?
- 确保设置了合理的过期时间
- 在数据修改时及时清除旧缓存
性能优化小技巧
Pipeline管道
批量操作提升性能
csharp
const pipeline = this.redis.pipeline();
pipeline.set('key1', 'val1');
pipeline.set('key2', 'val2');
await pipeline.exec();
集群配置
生产环境建议使用Redis集群
yaml
// redis.module.ts
{
config: {
nodes: [
{ host: 'redis1', port: 6379 },
{ host: 'redis2', port: 6380 }
]
}
}
📍 下期预告
《从0搭建NestJS后端服务(五):统一的响应拦截和异常错误过滤》
我们将探讨:
- 如何优雅处理各种异常
- 定制你的专属错误报文
- 全局异常过滤器的妙用
🤝 互动时间
遇到问题?有更好的建议?欢迎留言交流!
- 在你的业务场景中,哪些数据最适合用Redis缓存?欢迎评论区讨论!
一起交流成长,让开发更高效!🚀