目录
- [Redis 基础入门](#Redis 基础入门 "#%E4%B8%80redis-%E5%9F%BA%E7%A1%80%E5%85%A5%E9%97%A8")
- [Redis 数据类型与命令](#Redis 数据类型与命令 "#%E4%BA%8Credis-%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E4%B8%8E%E5%91%BD%E4%BB%A4")
- [Node.js 操作 Redis](#Node.js 操作 Redis "#%E4%B8%89nodejs-%E6%93%8D%E4%BD%9C-redis")
- [Nest.js 集成 Redis](#Nest.js 集成 Redis "#%E5%9B%9Bnestjs-%E9%9B%86%E6%88%90-redis")
- [cache-manager vs 直接操作 Redis](#cache-manager vs 直接操作 Redis "#%E4%BA%94cache-manager-vs-%E7%9B%B4%E6%8E%A5%E6%93%8D%E4%BD%9C-redis")
一、Redis 基础入门
1.1 什么是 Redis?
Redis 是一个开源的内存数据库,采用 key-value 键值对的形式存储数据。
1.2 为什么需要 Redis?
MySQL 的问题:
- 数据存储在硬盘,读写速度慢
- 需要解析和执行 SQL 语句,有性能开销
- 高并发场景下容易成为性能瓶颈
Redis 的优势:
- 数据存储在内存,读写速度极快
- 支持丰富的数据结构
- 支持数据持久化
| 存储介质 | 读取速度 |
|---|---|
| 内存 (RAM) | ~100 ns |
| SSD | ~100 μs |
| HDD | ~10 ms |
1.3 Redis 的用途
- 数据库缓存:将 MySQL 查询结果缓存到 Redis,减少数据库查询
- 直接存储:某些场景下直接用 Redis 存储数据(成本较高)
- 分布式锁:实现分布式系统的并发控制
- 消息队列:使用 List 或 Stream 实现简单消息队列
- 会话管理:存储用户 Session 信息
1.4 启动 Redis
使用 Docker 启动
bash
docker run -d --name redis \
-p 6379:6379 \
-v /path/to/data:/data \
redis:latest
使用 redis-cli 连接
bash
redis-cli
1.5 GUI 工具 - RedisInsight
Redis 官方提供的可视化工具:RedisInsight
功能:
- 可视化查看所有 key 和 value
- 执行 Redis 命令
- 监控 Redis 性能
- 支持多种数据类型的展示
二、Redis 数据类型与命令
2.1 String 字符串
最基本的数据类型,可以存储字符串、数字。
bash
# 设置值
SET name "张三"
SET age 25
# 获取值
GET name
GET age
# 递增(常用于计数器:阅读量、点赞量)
INCR age
INCRBY age 5
# 查询所有 key
KEYS *
# 删除 key
DEL name
使用场景: 缓存、计数器、分布式锁
2.2 List 列表
有序的字符串列表,支持从两端操作。
bash
# 从左边添加
LPUSH list1 111
LPUSH list1 222
LPUSH list1 333
# 从右边添加
RPUSH list1 444
RPUSH list1 555
# 从左边取出
LPOP list1
# 从右边取出
RPOP list1
# 查询列表(0 到 -1 表示全部)
LRANGE list1 0 -1
# 获取列表长度
LLEN list1
使用场景: 消息队列、最新消息列表
2.3 Set 集合
无序、不重复的字符串集合。
bash
# 添加元素(自动去重)
SADD set1 111
SADD set1 111 # 重复,不会添加
SADD set1 222
SADD set1 333
# 判断是否是集合中的元素
SISMEMBER set1 111 # 返回 1(存在)
SISMEMBER set1 444 # 返回 0(不存在)
# 获取所有元素
SMEMBERS set1
# 获取元素个数
SCARD set1
# 删除元素
SREM set1 111
使用场景: 标签、好友关系、去重
2.4 Sorted Set (ZSet) 有序集合
每个元素关联一个分数(score),按分数排序。
bash
# 添加元素(带分数)
ZADD zset1 5 feng
ZADD zset1 4 dong
ZADD zset1 3 xxx
ZADD zset1 6 yyyy
# 获取排名前 N 的元素(按分数升序)
ZRANGE zset1 0 2
# 获取排名前 N 的元素(按分数降序)
ZREVRANGE zset1 0 2
# 获取元素的分数
ZSCORE zset1 feng
# 获取元素的排名
ZRANK zset1 feng
# 删除元素
ZREM zset1 xxx
使用场景: 排行榜、优先级队列
2.5 Hash 哈希表
类似 Map 的结构,存储字段-值的映射。
bash
# 设置字段值
HSET hash1 key1 1
HSET hash1 key2 2
HSET hash1 key3 3
# 获取字段值
HGET hash1 key1
# 获取所有字段和值
HGETALL hash1
# 删除字段
HDEL hash1 key1
# 判断字段是否存在
HEXISTS hash1 key1
使用场景: 存储对象、用户信息
2.6 Geo 地理位置
存储地理位置信息,支持距离计算和范围查询。
bash
# 添加地理位置(经度 纬度 名称)
GEOADD loc 13.361389 38.115556 "fengfeng"
GEOADD loc 15.087269 37.502669 "dongdong"
# 计算两个位置的距离
GEODIST loc fengfeng dongdong
# 搜索某半径内的位置
GEORADIUS loc 15 37 100 km
GEORADIUS loc 15 37 200 km
使用场景: 附近的人、附近店铺
2.7 过期时间
bash
# 设置 key 的过期时间(秒)
EXPIRE key 30
# 查看剩余过期时间
TTL key
# 取消过期时间
PERSIST key
使用场景: 缓存过期、验证码有效期、Session 管理
2.8 常用命令速查表
| 命令 | 说明 |
|---|---|
SET key value |
设置字符串值 |
GET key |
获取字符串值 |
DEL key |
删除 key |
EXPIRE key seconds |
设置过期时间 |
TTL key |
查看剩余过期时间 |
KEYS pattern |
查找 key(pattern 如 *) |
EXISTS key |
判断 key 是否存在 |
INCR key |
数值递增 |
LPUSH/RPUSH |
列表左/右添加 |
LPOP/RPOP |
列表左/右弹出 |
LRANGE key start stop |
获取列表范围 |
SADD |
集合添加 |
SISMEMBER |
判断是否是集合元素 |
ZADD |
有序集合添加 |
ZRANGE |
获取有序集合范围 |
HSET/HGET |
哈希表设置/获取 |
三、Node.js 操作 Redis
3.1 客户端选择
Node.js 操作 Redis 的流行包:
| 包名 | 特点 |
|---|---|
| redis | 官方推荐,API 现代化 |
| ioredis | 社区流行,功能丰富 |
3.2 使用 redis 包
安装
bash
npm install redis
基本用法
typescript
import { createClient } from 'redis';
// 创建客户端
const client = createClient({
socket: {
host: 'localhost',
port: 6379
}
});
// 监听错误
client.on('error', err => console.log('Redis Client Error', err));
// 连接
await client.connect();
// 执行命令
const keys = await client.keys('*');
console.log(keys);
// 设置值
await client.set('name', '张三');
// 获取值
const name = await client.get('name');
console.log(name);
// 操作 Hash
await client.hSet('user:1', 'name', '张三');
await client.hSet('user:1', 'age', '25');
const user = await client.hGetAll('user:1');
// 断开连接
await client.disconnect();
3.3 使用 ioredis 包
安装
bash
npm install ioredis
基本用法
typescript
import Redis from 'ioredis';
// 创建客户端(默认连接 localhost:6379)
const redis = new Redis();
// 执行命令
const keys = await redis.keys('*');
console.log(keys);
// 操作 Hash
await redis.hset('user:1', 'name', '张三');
const name = await redis.hget('user:1', 'name');
// 断开连接
redis.disconnect();
3.4 redis vs ioredis 对比
| 特性 | redis | ioredis |
|---|---|---|
| 维护者 | NodeRedis 官方 | 社区 |
| Promise 支持 | ✓ | ✓ |
| Cluster 支持 | ✓ | ✓ |
| Sentinel 支持 | ✓ | ✓ |
| Pipeline | ✓ | ✓ |
| Lua 脚本 | ✓ | ✓ |
四、Nest.js 集成 Redis
4.1 方式一:自定义 Provider(推荐)
安装依赖
bash
npm install redis
配置 Provider
typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { createClient } from 'redis';
@Module({
imports: [],
controllers: [AppController],
providers: [
AppService,
{
provide: 'REDIS_CLIENT',
async useFactory() {
const client = createClient({
socket: {
host: 'localhost',
port: 6379
},
database: 0 // 指定使用的数据库
});
await client.connect();
return client;
}
}
],
})
export class AppModule {}
注入使用
typescript
// app.service.ts
import { Inject, Injectable } from '@nestjs/common';
import { RedisClientType } from 'redis';
@Injectable()
export class AppService {
@Inject('REDIS_CLIENT')
private redisClient: RedisClientType;
async getHello() {
// 获取所有 key
const keys = await this.redisClient.keys('*');
console.log(keys);
// 设置值
await this.redisClient.set('key', 'value');
// 获取值
const value = await this.redisClient.get('key');
return 'Hello World!';
}
}
4.2 方式二:封装 RedisModule(推荐生产使用)
创建 Redis 模块
typescript
// redis/redis.module.ts
import { Module, Global } from '@nestjs/common';
import { createClient } from 'redis';
import { RedisService } from './redis.service';
@Global()
@Module({
providers: [
RedisService,
{
provide: 'REDIS_CLIENT',
async useFactory() {
const client = createClient({
socket: {
host: 'localhost',
port: 6379
}
});
await client.connect();
return client;
}
}
],
exports: [RedisService]
})
export class RedisModule {}
创建 Redis 服务
typescript
// redis/redis.service.ts
import { Inject, Injectable } from '@nestjs/common';
import { RedisClientType } from 'redis';
@Injectable()
export class RedisService {
@Inject('REDIS_CLIENT')
private client: RedisClientType;
async get(key: string) {
return await this.client.get(key);
}
async set(key: string, value: string, ttl?: number) {
if (ttl) {
await this.client.setEx(key, ttl, value);
} else {
await this.client.set(key, value);
}
}
async del(key: string) {
await this.client.del(key);
}
async hSet(key: string, field: string, value: string) {
await this.client.hSet(key, field, value);
}
async hGet(key: string, field: string) {
return await this.client.hGet(key, field);
}
async hGetAll(key: string) {
return await this.client.hGetAll(key);
}
}
在模块中使用
typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { RedisModule } from './redis/redis.module';
@Module({
imports: [RedisModule],
// ...
})
export class AppModule {}
typescript
// app.service.ts
import { Inject, Injectable } from '@nestjs/common';
import { RedisService } from './redis/redis.service';
@Injectable()
export class AppService {
@Inject(RedisService)
private redisService: RedisService;
async getHello() {
await this.redisService.set('name', '张三');
const name = await this.redisService.get('name');
return `Hello ${name}!`;
}
}
4.3 封装动态模块(支持配置)
typescript
// redis/redis.module.ts
import { Module, DynamicModule, Global } from '@nestjs/common';
import { createClient } from 'redis';
import { RedisService } from './redis.service';
@Global()
@Module({})
export class RedisModule {
static forRoot(options: { host: string; port: number }): DynamicModule {
return {
module: RedisModule,
providers: [
RedisService,
{
provide: 'REDIS_CLIENT',
async useFactory() {
const client = createClient({
socket: options
});
await client.connect();
return client;
}
}
],
exports: [RedisService]
};
}
}
使用:
typescript
@Module({
imports: [
RedisModule.forRoot({
host: 'localhost',
port: 6379
})
],
})
export class AppModule {}
五、cache-manager vs 直接操作 Redis
5.1 cache-manager 简介
Nest.js 官方文档推荐使用 cache-manager 来操作 Redis。
bash
npm install @nestjs/cache-manager cache-manager
基本用法
typescript
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
@Controller()
export class AppController {
@Inject(CACHE_MANAGER)
private cacheManager: Cache;
@Get('set')
async set(@Query('value') value: string) {
await this.cacheManager.set('key', value);
return 'done';
}
@Get('get')
async get() {
return this.cacheManager.get('key');
}
@Get('del')
async del() {
await this.cacheManager.del('key');
return 'done';
}
}
CacheInterceptor 自动缓存
typescript
import { CacheInterceptor } from '@nestjs/cache-manager';
@Get('aaa')
@UseInterceptors(CacheInterceptor)
aaa(@Query('a') a: string) {
return 'aaa';
}
5.2 为什么不用 cache-manager?
| 特性 | cache-manager | 直接用 redis |
|---|---|---|
| get/set | ✓ | ✓ |
| List 操作 | ✗ | ✓ |
| Hash 操作 | ✗ | ✓ |
| Set 操作 | ✗ | ✓ |
| ZSet 操作 | ✗ | ✓ |
| Geo 操作 | ✗ | ✓ |
| 所有 Redis 命令 | ✗ | ✓ |
| CacheInterceptor | ✓ | 需自己实现 |
结论: cache-manager 只支持 get/set,无法使用 Redis 丰富的数据结构和命令。实际项目中,大多数场景需要使用 List、Hash、ZSet 等,所以建议直接操作 Redis。
5.3 自己实现 CacheInterceptor
如果需要接口缓存功能,可以自己实现:
typescript
// my-cache.interceptor.ts
import { CallHandler, ExecutionContext, Inject, Injectable, NestInterceptor } from '@nestjs/common';
import { HttpAdapterHost } from '@nestjs/core';
import { RedisClientType } from 'redis';
import { of, tap } from 'rxjs';
@Injectable()
export class MyCacheInterceptor implements NestInterceptor {
@Inject('REDIS_CLIENT')
private redisClient: RedisClientType;
@Inject(HttpAdapterHost)
private httpAdapterHost: HttpAdapterHost;
async intercept(context: ExecutionContext, next: CallHandler) {
const request = context.switchToHttp().getRequest();
// 生成缓存 key(使用请求 URL)
const key = this.httpAdapterHost.httpAdapter.getRequestUrl(request);
// 查询缓存
const value = await this.redisClient.get(key);
if (!value) {
// 没有缓存,执行 handler 并缓存结果
return next.handle().pipe(tap((res) => {
this.redisClient.set(key, typeof res === 'object' ? JSON.stringify(res) : res);
}));
} else {
// 有缓存,直接返回
return of(JSON.parse(value));
}
}
}
使用:
typescript
@Get('aaa')
@UseInterceptors(MyCacheInterceptor)
aaa(@Query('a') a: string) {
console.log('handler executed');
return { data: 'aaa' };
}
六、最佳实践
6.1 Key 命名规范
bash
业务名:对象名:id:[属性]
示例:
less
user:token:123
order:detail:456
product:list:page:1
6.2 过期时间策略
- 缓存数据:设置合理的过期时间(如 30 分钟)
- 会话数据:根据业务需求设置(如 24 小时)
- 计数器:不设置过期时间或设置较长
6.3 内存管理
- 定期清理过期 key
- 监控内存使用情况
- 设置 maxmemory 和淘汰策略
6.4 常见使用场景
| 场景 | 数据类型 | 说明 |
|---|---|---|
| 缓存 | String | 缓存数据库查询结果 |
| 计数器 | String | 阅读量、点赞量 |
| 排行榜 | ZSet | 用户积分排名 |
| 消息队列 | List | 简单的消息队列 |
| 用户信息 | Hash | 存储对象属性 |
| 标签 | Set | 文章标签、用户标签 |
| 附近的人 | Geo | 地理位置查询 |
| 分布式锁 | String | 实现并发控制 |
七、命令速查
字符串操作
bash
SET key value # 设置值
GET key # 获取值
MSET k1 v1 k2 v2 # 批量设置
MGET k1 k2 # 批量获取
INCR key # 自增
DECR key # 自减
APPEND key value # 追加
STRLEN key # 获取长度
列表操作
bash
LPUSH key v1 v2 # 左边添加
RPUSH key v1 v2 # 右边添加
LPOP key # 左边弹出
RPOP key # 右边弹出
LRANGE key 0 -1 # 获取全部
LLEN key # 获取长度
LINDEX key index # 获取指定位置
集合操作
bash
SADD key v1 v2 # 添加元素
SREM key v1 # 删除元素
SMEMBERS key # 获取所有元素
SISMEMBER key v1 # 判断是否存在
SCARD key # 获取元素个数
SINTER key1 key2 # 交集
SUNION key1 key2 # 并集
SDIFF key1 key2 # 差集
有序集合操作
bash
ZADD key score v1 # 添加元素
ZREM key v1 # 删除元素
ZRANGE key 0 -1 # 获取范围
ZREVRANGE key 0 -1 # 逆序获取
ZSCORE key v1 # 获取分数
ZRANK key v1 # 获取排名
ZINCRBY key score v1 # 增加分数
哈希操作
bash
HSET key f1 v1 # 设置字段
HGET key f1 # 获取字段
HGETALL key # 获取所有字段
HDEL key f1 # 删除字段
HEXISTS key f1 # 判断字段存在
HINCRBY key f1 1 # 字段递增
参考文档:
- Redis 官方文档:redis.io/documentati...
- Redis 命令参考:redis.io/commands/