Redis 入门教程

28 阅读9分钟

目录

  1. Redis 基础入门
  2. Redis 数据类型与命令
  3. Node.js 操作 Redis
  4. Nest.js 集成 Redis
  5. cache-manager vs 直接操作 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 的用途

  1. 数据库缓存:将 MySQL 查询结果缓存到 Redis,减少数据库查询
  2. 直接存储:某些场景下直接用 Redis 存储数据(成本较高)
  3. 分布式锁:实现分布式系统的并发控制
  4. 消息队列:使用 List 或 Stream 实现简单消息队列
  5. 会话管理:存储用户 Session 信息

1.4 启动 Redis

使用 Docker 启动

docker run -d --name redis \
  -p 6379:6379 \
  -v /path/to/data:/data \
  redis:latest

使用 redis-cli 连接

redis-cli

1.5 GUI 工具 - RedisInsight

Redis 官方提供的可视化工具:RedisInsight

功能:

  • 可视化查看所有 key 和 value
  • 执行 Redis 命令
  • 监控 Redis 性能
  • 支持多种数据类型的展示

二、Redis 数据类型与命令

2.1 String 字符串

最基本的数据类型,可以存储字符串、数字。

# 设置值
SET name "张三"
SET age 25

# 获取值
GET name
GET age

# 递增(常用于计数器:阅读量、点赞量)
INCR age
INCRBY age 5

# 查询所有 key
KEYS *

# 删除 key
DEL name

使用场景: 缓存、计数器、分布式锁

2.2 List 列表

有序的字符串列表,支持从两端操作。

# 从左边添加
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 集合

无序、不重复的字符串集合。

# 添加元素(自动去重)
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),按分数排序。

# 添加元素(带分数)
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 的结构,存储字段-值的映射。

# 设置字段值
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 地理位置

存储地理位置信息,支持距离计算和范围查询。

# 添加地理位置(经度 纬度 名称)
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 过期时间

# 设置 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 包

安装

npm install redis

基本用法

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 包

安装

npm install ioredis

基本用法

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 对比

特性redisioredis
维护者NodeRedis 官方社区
Promise 支持
Cluster 支持
Sentinel 支持
Pipeline
Lua 脚本

四、Nest.js 集成 Redis

4.1 方式一:自定义 Provider(推荐)

安装依赖

npm install redis

配置 Provider

// 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 {}

注入使用

// 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 模块

// 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 服务

// 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);
  }
}

在模块中使用

// app.module.ts
import { Module } from '@nestjs/common';
import { RedisModule } from './redis/redis.module';

@Module({
  imports: [RedisModule],
  // ...
})
export class AppModule {}
// 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 封装动态模块(支持配置)

// 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]
    };
  }
}

使用:

@Module({
  imports: [
    RedisModule.forRoot({
      host: 'localhost',
      port: 6379
    })
  ],
})
export class AppModule {}

五、cache-manager vs 直接操作 Redis

5.1 cache-manager 简介

Nest.js 官方文档推荐使用 cache-manager 来操作 Redis。

npm install @nestjs/cache-manager cache-manager

基本用法

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 自动缓存

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

如果需要接口缓存功能,可以自己实现:

// 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));
    }
  }
}

使用:

@Get('aaa')
@UseInterceptors(MyCacheInterceptor)
aaa(@Query('a') a: string) {
  console.log('handler executed');
  return { data: 'aaa' };
}

六、最佳实践

6.1 Key 命名规范

业务名:对象名:id:[属性]

示例:

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实现并发控制

七、命令速查

字符串操作

SET key value          # 设置值
GET key                # 获取值
MSET k1 v1 k2 v2      # 批量设置
MGET k1 k2             # 批量获取
INCR key               # 自增
DECR key               # 自减
APPEND key value       # 追加
STRLEN key             # 获取长度

列表操作

LPUSH key v1 v2        # 左边添加
RPUSH key v1 v2        # 右边添加
LPOP key               # 左边弹出
RPOP key               # 右边弹出
LRANGE key 0 -1        # 获取全部
LLEN key               # 获取长度
LINDEX key index       # 获取指定位置

集合操作

SADD key v1 v2         # 添加元素
SREM key v1            # 删除元素
SMEMBERS key           # 获取所有元素
SISMEMBER key v1       # 判断是否存在
SCARD key              # 获取元素个数
SINTER key1 key2       # 交集
SUNION key1 key2       # 并集
SDIFF key1 key2        # 差集

有序集合操作

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   # 增加分数

哈希操作

HSET key f1 v1         # 设置字段
HGET key f1            # 获取字段
HGETALL key            # 获取所有字段
HDEL key f1            # 删除字段
HEXISTS key f1         # 判断字段存在
HINCRBY key f1 1       # 字段递增

参考文档: