在本文中,我们将探讨如何在 NestJS 项目中快速集成和使用 Redis。无论是作为缓存、消息代理(Pub/Sub)还是实现分布式锁,Redis 都是现代微服务架构中不可或缺的利器。
为什么在 NestJS 中使用 Redis?
- 高性能缓存:大幅减少数据库查询负担,提高响应速度。
- 状态共享:在分布式系统或集群部署中共享 Session、Token 等状态。
- 消息队列 / 发布订阅(Pub/Sub):支持微服务之间的异步通信。
- 可靠和丰富的数据结构:支持哈希、列表、集合等,能适应各种复杂的业务场景。
1. 安装依赖
在 NestJS 中,我们通常使用 ioredis 作为客户端,因为它对集群、Sentinel 支持得非常好,并且 API 友好。
1
2
npm install ioredis
npm install -D @types/ioredis
如果我们需要将 Redis 用作 NestJS 原生的缓存管理器,还可以安装:
1
npm install @nestjs/cache-manager cache-manager cache-manager-redis-yet
本篇文章我们将主要展示如何直接封装和使用 ioredis,因为这种方式在实际的复杂业务中更灵活。
2. 封装 Redis 模块
为了在 NestJS 中优雅地使用 Redis,我们将创建一个专用的全局 RedisModule。
2.1 创建基础模块和 Service
使用 CLI 生成模块:
1
2
nest g module redis
nest g service redis
2.2 注入 ioredis 客户端
在 redis.module.ts 中,我们通过自定义 Provider 将 ioredis 初始化,并将其作为全局模块,方便全站注入。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { Global, Module } from '@nestjs/common';
import { RedisService } from './redis.service';
import Redis from 'ioredis';
@Global()
@Module({
providers: [
RedisService,
{
provide: 'REDIS_CLIENT',
useFactory: () => {
// 在实际应用中,这里应该从 ConfigService 读取环境变量
return new Redis({
host: '127.0.0.1',
port: 6379,
// password: 'your_password',
// db: 0,
});
},
},
],
exports: [RedisService, 'REDIS_CLIENT'],
})
export class RedisModule {}
提示:加上
@Global()装饰器后,只需要在AppModule引入一次RedisModule,其他任何模块都可以直接注入RedisService而无需重复引入。
2.3 编写 RedisService 包装操作
在 redis.service.ts 中,我们依赖注入刚才初始化的 REDIS_CLIENT:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import { Injectable, Inject, OnModuleDestroy } from '@nestjs/common';
import Redis from 'ioredis';
@Injectable()
export class RedisService implements OnModuleDestroy {
constructor(@Inject('REDIS_CLIENT') private readonly redisClient: Redis) {}
/**
* 获取原生的 Redis 客户端实例
*/
getClient(): Redis {
return this.redisClient;
}
/**
* 写入字符串型缓存
* @param key 键
* @param value 值
* @param ttl 过期时间(秒),可选
*/
async set(key: string, value: string, ttl?: number): Promise<void> {
if (ttl) {
await this.redisClient.set(key, value, 'EX', ttl);
} else {
await this.redisClient.set(key, value);
}
}
/**
* 读取字符串型缓存
*/
async get(key: string): Promise<string | null> {
return await this.redisClient.get(key);
}
/**
* 删除缓存
*/
async del(key: string): Promise<number> {
return await this.redisClient.del(key);
}
/**
* 模块销毁时断开连接
*/
onModuleDestroy() {
this.redisClient.disconnect();
}
}
3. 在业务模块中使用 Redis
有了全局的 RedisModule,我们就可以在任何业务 Service 中使用它了。例如,在一个处理用户查询的服务中实现缓存:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { Injectable } from '@nestjs/common';
import { RedisService } from '../redis/redis.service';
@Injectable()
export class UserService {
constructor(private readonly redisService: RedisService) {}
async getUserById(id: number) {
const cacheKey = `user:info:${id}`;
// 1. 尝试从 Redis 缓存中获取
const cachedUser = await this.redisService.get(cacheKey);
if (cachedUser) {
console.log('Hitting cache!');
return JSON.parse(cachedUser);
}
// 2. 如果没有缓存,则从数据库读取(模拟 DB 耗时)
console.log('Querying Database...');
const userFromDb = { id, name: 'Alice', age: 25 };
// 3. 将结果写入 Redis 并设置 1 小时过期
await this.redisService.set(cacheKey, JSON.stringify(userFromDb), 3600);
return userFromDb;
}
}
4. 进阶处理:使用 NestJS 拦截器做整页缓存
通常,对于高并发且更新不频繁的查询接口,我们需要非常方便无侵入的缓存。我们可以使用原生的 Cache 模块结合拦截器。
如果你在步骤一中安装了 @nestjs/cache-manager 和 cache-manager-redis-yet,可以直接对路由生效:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { Controller, Get, UseInterceptors } from '@nestjs/common';
import { CacheInterceptor, CacheKey, CacheTTL } from '@nestjs/cache-manager';
@Controller('products')
// 应用拦截器
@UseInterceptors(CacheInterceptor)
export class ProductController {
@Get('hot')
// 覆盖默认键名
@CacheKey('hot_products_cache')
// 设置过期时间为 600 秒
@CacheTTL(600)
async getHotProducts() {
// 耗时逻辑
return ['product_A', 'product_B'];
}
}
5. 小结
在 NestJS 中集成 Redis 的关键就在于如何把原生的 Redis Client(如 ioredis)注册到 IoC 容器中。
- 直接封装
ioredis适合于需要各种数据结构操作、Lua 脚本、事务和分布式锁等深度定制场景。 - 使用
@nestjs/cache-manager与CacheInterceptor适合需要纯粹 API 响应缓存的无侵入场景。
根据实际业务情况选择,或者混合使用。希望本文对你有所帮助!