「项目实战」从0搭建NestJS后端服务(四):Redis的集成和配置

前言

大家好 我是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('操作过于频繁,请稍后再试');
  }
}

常见问题排查

连接失败怎么办?

  1. 检查Redis服务是否启动:redis-cli ping 应该返回 PONG
  2. 确认防火墙开放了Redis端口
  3. 检查密码是否正确(如果有设置)

数据不更新?

  • 确保设置了合理的过期时间
  • 在数据修改时及时清除旧缓存

性能优化小技巧

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缓存?欢迎评论区讨论!

一起交流成长,让开发更高效!🚀

相关推荐
孤╮独的美几秒前
CSS3:深度解析与实战应用
前端·css·css3
一城烟雨_9 分钟前
Vue3 实现pdf预览
前端·vue.js·pdf
易xingxing13 分钟前
探索HTML5 Canvas:创造动态与交互性网页内容的强大工具
前端·html·html5
好_快22 分钟前
Lodash源码阅读-arrayPush
前端·javascript·源码阅读
好_快24 分钟前
Lodash源码阅读-equalByTag
前端·javascript·源码阅读
大土豆的bug记录5 小时前
鸿蒙进行视频上传,使用 request.uploadFile方法
开发语言·前端·华为·arkts·鸿蒙·arkui
数据潜水员5 小时前
跨域,前端
node.js
maybe02095 小时前
前端表格数据导出Excel文件方法,列自适应宽度、增加合计、自定义文件名称
前端·javascript·excel·js·大前端
HBR666_5 小时前
菜单(路由)权限&按钮权限&路由进度条
前端·vue
A-Kamen6 小时前
深入理解 HTML5 Web Workers:提升网页性能的关键技术解析
前端·html·html5