Redis缓存技术实战教程

教程简介

零基础Redis缓存技术实战教程,涵盖数据类型、持久化机制、主从复制、哨兵模式、集群搭建、缓存策略、分布式锁、消息队列等核心技能,配有秒杀系统缓存设计实战项目,适合后端开发者学习。

Redis 缓存技术实战教程

面向零基础用户的完整 Redis 学习指南,从入门到实战


目录

  1. Redis 是什么
  2. 安装与配置
  3. 五大基本数据类型
  4. 持久化机制
  5. 主从复制
  6. 哨兵模式
  7. 集群搭建
  8. 缓存策略
  9. 分布式锁
  10. 消息队列
  11. 实战:秒杀系统缓存设计
  12. 性能优化与最佳实践

1. Redis 是什么

Redis(Remote Dictionary Server)是一个开源的、基于内存的键值对数据库。与传统数据库不同,Redis 将数据存储在内存中,因此读写速度极快,单机即可达到每秒数万甚至数十万次操作。

Redis 的核心特点

  • 极速:数据存储在内存中,读写延迟通常在微秒级别
  • 丰富的数据结构:不仅支持简单的 Key-Value,还支持 String、List、Hash、Set、Sorted Set 等
  • 持久化:支持 RDB 和 AOF 两种方式将内存数据保存到磁盘
  • 高可用:支持主从复制、哨兵模式和集群模式
  • 原子操作:所有操作都是原子性的,支持事务和 Lua 脚本

Redis 的典型应用场景

场景 说明
缓存 热点数据缓存,减轻数据库压力
会话管理 存储用户 Session
排行榜 使用 Sorted Set 实现实时排行
计数器 点赞数、浏览量等原子计数
分布式锁 跨服务的互斥访问控制
消息队列 使用 List 或 Stream 实现简单消息队列
限流 接口访问频率限制
社交关系 关注/粉丝/共同好友

2. 安装与配置

2.1 在 Linux 上安装(以 Ubuntu 为例)

# 更新包管理器
sudo apt update

# 安装 Redis
sudo apt install redis-server -y

# 启动 Redis 服务
sudo systemctl start redis-server

# 设置开机自启
sudo systemctl enable redis-server

# 检查运行状态
sudo systemctl status redis-server

2.2 使用 Docker 安装(推荐开发环境)

# 拉取并运行 Redis 容器
docker run -d \
  --name redis \
  -p 6379:6379 \
  -v /data/redis:/data \
  redis:7-alpine

# 进入 Redis CLI
docker exec -it redis redis-cli

2.3 从源码编译安装

# 下载源码
wget https://download.redis.io/redis-stable.tar.gz
tar xzf redis-stable.tar.gz
cd redis-stable

# 编译
make

# 安装到系统路径
sudo make install

# 启动
redis-server

2.4 核心配置文件详解

Redis 配置文件通常位于 /etc/redis/redis.conf,以下是关键配置项:

# ========== 基础配置 ==========

# 绑定地址(默认只允许本地访问)
bind 127.0.0.1

# 保护模式(生产环境建议开启)
protected-mode yes

# 监听端口
port 6379

# 后台运行
daemonize yes

# 日志级别:debug / verbose / notice / warning
loglevel notice
logfile /var/log/redis/redis-server.log

# 数据库数量(0-15)
databases 16

# ========== 内存配置 ==========

# 最大内存限制
maxmemory 256mb

# 内存淘汰策略
maxmemory-policy allkeys-lru

# ========== 安全配置 ==========

# 访问密码
requirepass your_strong_password_here

# ========== 网络配置 ==========

# 客户端超时时间(秒),0 表示不超时
timeout 300

# 最大客户端连接数
maxclients 10000

2.5 连接与测试

# 连接本地 Redis
redis-cli

# 连接远程 Redis
redis-cli -h 192.168.1.100 -p 6379 -a your_password

# 测试连通性
127.0.0.1:6379> PING
PONG

# 查看服务器信息
127.0.0.1:6379> INFO server

# 查看当前数据库 key 数量
127.0.0.1:6379> DBSIZE
(integer) 0

3. 五大基本数据类型

Redis 的强大之处在于其丰富的数据类型。下面逐一讲解每种类型的用法和适用场景。

3.1 String(字符串)

String 是 Redis 最基础的数据类型,可以存储字符串、整数或浮点数。一个 String 类型的 value 最大可存储 512MB。

基本操作

# 设置键值
SET name "张三"

# 获取值
GET name
# 输出: "张三"

# 设置带过期时间的键(秒)
SET token "abc123" EX 3600

# 设置带过期时间的键(毫秒)
SET token "abc123" PX 3600000

# 仅当 key 不存在时设置(分布式锁常用)
SETNX lock "holder_1"

# 设置值并返回旧值
SET counter 100
GETSET counter 200
# 输出: "100",此时 counter 的值为 200

# 批量设置
MSET k1 "v1" k2 "v2" k3 "v3"

# 批量获取
MGET k1 k2 k3

# 删除键
DEL name

# 检查键是否存在
EXISTS name

# 设置过期时间(秒)
EXPIRE name 60

# 查看剩余过期时间(秒)
TTL name
# 输出: (integer) 55(剩余秒数)
# 输出: (integer) -1(永不过期)
# 输出: (integer) -2(已过期/不存在)

数值操作

# 初始化计数器
SET views 0

# 自增
INCR views
# 输出: (integer) 1

INCR views
# 输出: (integer) 2

# 指定增量
INCRBY views 10
# 输出: (integer) 12

# 自减
DECR views
# 输出: (integer) 11

# 指定减量
DECRBY views 5
# 输出: (integer) 6

# 浮点数增加
SET price 10.5
INCRBYFLOAT price 0.5
# 输出: "11"

字符串操作

# 追加字符串
SET greeting "Hello"
APPEND greeting " World"
# 输出: (integer) 11
# greeting 的值为 "Hello World"

# 获取字符串长度
STRLEN greeting
# 输出: (integer) 11

# 截取子串(闭区间,从0开始)
GETRANGE greeting 0 4
# 输出: "Hello"

# 替换子串
SETRANGE greeting 6 "Redis"
# greeting 的值为 "Hello Redis"

实战场景

# 场景1:接口限流(每分钟最多100次请求)
SET rate_limit:user:1001 0 EX 60 NX
INCR rate_limit:user:1001

# 场景2:分布式ID生成
INCR global:order_id
# 每次调用都会返回一个递增的唯一数字

# 场景3:缓存用户信息(JSON格式)
SET user:1001 '{"name":"张三","age":25,"city":"北京"}' EX 7200

3.2 List(列表)

List 是一个双向链表,支持从两端插入和弹出元素,非常适合实现队列和栈。

基本操作

# 从左侧(头部)插入
LPUSH queue "task1" "task2" "task3"
# 列表顺序: task3 -> task2 -> task1

# 从右侧(尾部)插入
RPUSH queue "task4"
# 列表顺序: task3 -> task2 -> task1 -> task4

# 查看列表长度
LLEN queue
# 输出: (integer) 4

# 获取指定范围的元素(闭区间)
LRANGE queue 0 -1
# 输出: 1) "task3" 2) "task2" 3) "task1" 4) "task4"

# 获取指定索引的元素(从0开始)
LINDEX queue 0
# 输出: "task3"

# 设置指定索引的值
LSET queue 0 "task_new"

# 从左侧弹出(取头部并移除)
LPOP queue
# 输出: "task3"

# 从右侧弹出(取尾部并移除)
RPOP queue
# 输出: "task4"

# 阻塞弹出(列表为空时等待,最多等30秒)
BLPOP queue 30

# 裁剪列表(只保留指定范围)
LTRIM queue 0 1

高级操作

# 在指定元素前插入
LPUSHX queue "urgent_task"

# 移除指定元素(LREM key count value)
# count > 0: 从头到尾移除 count 个值为 value 的元素
# count < 0: 从尾到头移除 |count| 个值为 value 的元素
# count = 0: 移除所有值为 value 的元素
RPUSH mylist "a" "b" "a" "c" "a"
LREM mylist 2 "a"
# 移除从头开始的 2 个 "a"

# 将元素从一个列表移到另一个(原子操作)
RPOPLPUSH source_list dest_list

# 阻塞版的 RPOPLPUSH(用于可靠队列)
BRPOPLPUSH source_list dest_list 30

实战场景

# 场景1:简单消息队列(生产者-消费者模式)
# 生产者
LPUSH mq:orders '{"orderId":"1001","amount":99.9}'

# 消费者(阻塞等待)
BRPOP mq:orders 0

# 场景2:最近浏览记录(只保留最近10条)
LPUSH recent:views:user1001 "product:5001"
LTRIM recent:views:user1001 0 9

# 场景3:任务队列(可靠处理)
# 将任务放入处理队列
LPUSH tasks:pending '{"task":"send_email","to":"user@example.com"}'
# 处理时先移到进行中队列
RPOPLPUSH tasks:pending tasks:processing
# 处理完成后从进行中队列移除
LREM tasks:processing 1 '{"task":"send_email","to":"user@example.com"}'

3.3 Hash(哈希)

Hash 是一个键值对集合,特别适合存储对象。相比用 String 存储 JSON,Hash 可以只修改某个字段而无需读取整个对象。

基本操作

# 设置单个字段
HSET user:1001 name "张三"
HSET user:1001 age 25
HSET user:1001 city "北京"

# 批量设置多个字段
HMSET user:1002 name "李四" age 30 city "上海"

# 获取单个字段
HGET user:1001 name
# 输出: "张三"

# 获取多个字段
HMGET user:1001 name age city
# 输出: 1) "张三" 2) "25" 3) "北京"

# 获取所有字段和值
HGETALL user:1001
# 输出: 1) "name" 2) "张三" 3) "age" 4) "25" 5) "city" 6) "北京"

# 获取所有字段名
HKEYS user:1001

# 获取所有值
HVALS user:1001

# 获取字段数量
HLEN user:1001
# 输出: (integer) 3

# 检查字段是否存在
HEXISTS user:1001 name
# 输出: (integer) 1

# 删除字段
HDEL user:1001 city

数值操作

# 字段值自增(整数)
HSET stats:article:1001 views 0
HINCRBY stats:article:1001 views 1
# 输出: (integer) 1

HINCRBY stats:article:1001 views 100
# 输出: (integer) 101

# 字段值自增(浮点数)
HSET product:1001 price 99.9
HINCRBYFLOAT product:1001 price -10.0
# 输出: "89.9"

实战场景

# 场景1:存储用户信息(比 String JSON 更灵活)
HMSET user:1001 \
  name "张三" \
  email "zhangsan@example.com" \
  phone "13800138000" \
  vip_level 3 \
  register_time "2024-01-15"

# 只更新 VIP 等级,不影响其他字段
HSET user:1001 vip_level 4

# 场景2:购物车(用户ID -> 商品ID -> 数量)
HSET cart:user1001 product:5001 2
HSET cart:user1001 product:5002 1

# 增加商品数量
HINCRBY cart:user1001 product:5001 1

# 查看购物车所有商品
HGETALL cart:user1001

# 删除某个商品
HDEL cart:user1001 product:5002

# 场景3:文章计数器
HMSET article:1001:stats \
  views 0 \
  likes 0 \
  comments 0 \
  shares 0

3.4 Set(集合)

Set 是无序且不重复的元素集合,支持集合运算(交集、并集、差集)。

基本操作

# 添加元素
SADD tags:article:1001 "Redis" "缓存" "数据库" "性能"

# 查看所有成员
SMEMBERS tags:article:1001
# 输出: 1) "Redis" 2) "缓存" 3) "数据库" 4) "性能"

# 检查元素是否存在
SISMEMBER tags:article:1001 "Redis"
# 输出: (integer) 1

# 获取集合大小
SCARD tags:article:1001
# 输出: (integer) 4

# 随机获取元素
SRANDMEMBER tags:article:1001 2
# 随机返回 2 个元素(不删除)

# 随机弹出元素
SPOP tags:article:1001
# 随机弹出并移除 1 个元素

# 移除元素
SREM tags:article:1001 "性能"

集合运算

# 准备数据
SADD user:1001:followers "1002" "1003" "1004" "1005"
SADD user:1002:followers "1001" "1003" "1005" "1006"

# 交集(共同关注)
SINTER user:1001:followers user:1002:followers
# 输出: 1) "1003" 2) "1005"

# 并集(所有关注的人)
SUNION user:1001:followers user:1002:followers

# 差集(user:1001 关注但 user:1002 没关注的)
SDIFF user:1001:followers user:1002:followers
# 输出: 1) "1002" 2) "1004"

# 将交集结果存储到新集合
SINTERSTORE common:1001:1002 user:1001:followers user:1002:followers

实战场景

# 场景1:点赞功能
SADD likes:article:1001 "user:1001"
SADD likes:article:1001 "user:1002"

# 检查是否已点赞
SISMEMBER likes:article:1001 "user:1001"

# 取消点赞
SREM likes:article:1001 "user:1001"

# 获取点赞数
SCARD likes:article:1001

# 场景2:标签系统(查找共同标签的文章)
SADD tag:Redis "article:1001" "article:1003"
SADD tag:缓存 "article:1001" "article:1002"
SINTER tag:Redis tag:缓存
# 输出: "article:1001"(同时有 Redis 和缓存标签的文章)

# 场景3:抽奖系统
SADD lottery:2024 "user:1001" "user:1002" "user:1003" "user:1004"

# 随机抽取 2 个中奖者(不重复)
SRANDMEMBER lottery:2024 2

# 抽取并移除(确保不重复中奖)
SPOP lottery:2024

3.5 Sorted Set(有序集合)

Sorted Set(ZSet)在 Set 的基础上为每个元素关联一个分数(score),元素按分数排序。这是 Redis 最强大的数据类型之一,广泛用于排行榜、延迟队列等场景。

基本操作

# 添加元素(带分数)
ZADD leaderboard 100 "player:A"
ZADD leaderboard 85 "player:B"
ZADD leaderboard 92 "player:C"
ZADD leaderboard 78 "player:D"
ZADD leaderboard 95 "player:E"

# 批量添加
ZADD leaderboard 88 "player:F" 76 "player:G"

# 按分数从低到高排序
ZRANGE leaderboard 0 -1 WITHSCORES
# 输出:
# 1) "player:D"
# 2) "78"
# 3) "player:G"
# 4) "76"
# ...

# 按分数从高到低排序(排行榜常用)
ZREVRANGE leaderboard 0 2 WITHSCORES
# 输出前3名:
# 1) "player:A" 2) "100"
# 3) "player:E" 4) "95"
# 5) "player:C" 6) "92"

# 获取元素的分数
ZSCORE leaderboard "player:A"
# 输出: "100"

# 获取元素的排名(从0开始,按分数从低到高)
ZRANK leaderboard "player:A"
# 输出: (integer) 4(第5名)

# 获取元素的排名(按分数从高到低)
ZREVRANK leaderboard "player:A"
# 输出: (integer) 0(第1名)

# 获取集合大小
ZCARD leaderboard
# 输出: (integer) 7

# 分数自增
ZINCRBY leaderboard 5 "player:B"
# player:B 的分数从 85 变为 90

# 删除元素
ZREM leaderboard "player:G"

范围查询

# 按分数范围查询(80-100分的玩家)
ZRANGEBYSCORE leaderboard 80 100 WITHSCORES

# 按分数范围查询(从高到低)
ZREVRANGEBYSCORE leaderboard 100 80 WITHSCORES

# 统计分数范围内的元素数量
ZCOUNT leaderboard 80 100
# 输出: (integer) 5

# 按字典序范围查询(分数相同时)
ZRANGEBYLEX leaderboard [player:A [player:C

实战场景

# 场景1:实时排行榜
ZADD rank:game:daily 1500 "user:1001"
ZADD rank:game:daily 2300 "user:1002"
ZADD rank:game:daily 1800 "user:1003"

# 获取 Top 10
ZREVRANGE rank:game:daily 0 9 WITHSCORES

# 获取用户排名
ZREVRANK rank:game:daily "user:1001"

# 场景2:延迟队列(定时任务)
# 添加延迟任务,score 为执行时间戳
ZADD delay_queue 1706246400 '{"task":"send_reminder","userId":1001}'
ZADD delay_queue 1706246500 '{"task":"expire_order","orderId":"ORD001"}'

# 获取到期任务(当前时间戳之前的所有任务)
ZRANGEBYSCORE delay_queue 0 1706246450

# 场景3:滑动窗口限流
# 记录请求时间戳
ZADD rate:user:1001 1706246400.123 "req1"
ZADD rate:user:1001 1706246400.456 "req2"

# 统计最近60秒的请求数
ZREMRANGEBYSCORE rate:user:1001 0 1706246340
ZCARD rate:user:1001

4. 持久化机制

Redis 数据存储在内存中,一旦进程退出,数据就会丢失。持久化机制就是将内存数据保存到磁盘,以便重启后恢复数据。

4.1 RDB 快照

RDB(Redis Database)通过生成内存数据的快照文件(dump.rdb)来持久化数据。

工作原理

Redis fork 出一个子进程,子进程将内存中的数据写入一个临时 RDB 文件,写入完成后替换旧的 RDB 文件。主进程在 fork 期间继续处理请求,不影响服务。

配置方式

# 在 redis.conf 中配置

# 自动触发条件(满足任意一个即触发 BGSAVE)
# 格式: save <seconds> <changes>
save 900 1      # 900秒内至少1次修改
save 300 10     # 300秒内至少10次修改
save 60 10000   # 60秒内至少10000次修改

# 禁用 RDB(如果只用 AOF)
# save ""

# RDB 文件名
dbfilename dump.rdb

# RDB 文件保存目录
dir /var/lib/redis

# 压缩 RDB 文件
rdbcompression yes

# RDB 文件校验
rdbchecksum yes

手动触发

# 同步保存(阻塞主线程,生产环境慎用)
SAVE

# 后台异步保存(推荐)
BGSAVE

# 查看最后一次保存的时间
LASTSAVE

优缺点

优点 缺点
文件紧凑,适合备份和灾难恢复 可能丢失最后一次快照后的数据
恢复速度快 fork 子进程时内存占用翻倍
对性能影响较小 数据量大时 fork 耗时较长

4.2 AOF 追加日志

AOF(Append Only File)以日志追加的方式记录每一个写操作命令。

工作原理

每当有写命令执行时,Redis 将该命令追加到 AOF 文件末尾。重启时 Redis 重新执行 AOF 文件中的命令来恢复数据。

配置方式

# 开启 AOF
appendonly yes

# AOF 文件名
appendfilename "appendonly.aof"

# 同步策略
# always: 每次写命令都同步(最安全,性能最差)
# everysec: 每秒同步一次(推荐,平衡安全与性能)
# no: 由操作系统决定何时同步(性能最好,安全性最低)
appendfsync everysec

# AOF 重写时不执行 fsync(避免阻塞)
no-appendfsync-on-rewrite no

# 自动重写触发条件
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

手动重写

# 手动触发 AOF 重写(压缩 AOF 文件)
BGREWRITEAOF

优缺点

优点 缺点
数据安全性高,最多丢失1秒数据 AOF 文件通常比 RDB 文件大
可读性好,可手动修改 恢复速度比 RDB 慢
自动重写机制控制文件大小 重写期间可能影响性能

4.3 混合持久化

Redis 4.0+ 支持混合持久化,结合 RDB 和 AOF 的优势。

# 开启混合持久化
aof-use-rdb-preamble yes

AOF 重写时,前半部分写入 RDB 格式的全量数据,后半部分追加 AOF 格式的增量命令。恢复时先加载 RDB 部分(速度快),再重放 AOF 部分(数据完整)。


5. 主从复制

主从复制(Master-Slave Replication)是 Redis 实现高可用的基础。主节点负责写操作,从节点复制主节点的数据并提供读服务。

5.1 架构说明

客户端 ──写──> Master
                 │
           ┌─────┼─────┐
           │     │     │
           ▼     ▼     ▼
        Slave1 Slave2 Slave3
         读     读     读

5.2 配置方式

方式一:配置文件

在从节点的 redis.conf 中添加:

# 指定主节点地址和端口
replicaof 192.168.1.100 6379

# 如果主节点设置了密码
masterauth your_master_password

# 从节点只读(默认开启)
replica-read-only yes

方式二:命令行动态配置

# 在从节点上执行
127.0.0.1:6380> REPLICAOF 192.168.1.100 6379
OK

# 取消复制,提升为主节点
127.0.0.1:6380> REPLICAOF NO ONE
OK

5.3 查看复制状态

# 在主节点查看
127.0.0.1:6379> INFO replication
# 输出关键信息:
# role:master
# connected_slaves:2
# slave0:ip=192.168.1.101,port=6380,state=online,offset=12345,lag=0
# slave1:ip=192.168.1.102,port=6381,state=online,offset=12345,lag=1

# 在从节点查看
127.0.0.1:6380> INFO replication
# 输出关键信息:
# role:slave
# master_host:192.168.1.100
# master_port:6379
# master_link_status:up

5.4 复制原理

  1. 全量复制:从节点首次连接主节点时,主节点生成 RDB 快照发送给从节点,同时将期间的写命令缓存在缓冲区中,RDB 传输完成后发送缓冲区的命令
  2. 增量复制:全量复制后,主节点持续将写命令发送给从节点,保持数据同步
  3. 断线重连:Redis 2.8+ 支持部分重同步(PSYNC),通过复制偏移量和积压缓冲区避免全量复制

6. 哨兵模式

哨兵(Sentinel)模式在主从复制的基础上实现了自动故障转移。当主节点宕机时,哨兵会自动将一个从节点提升为新的主节点。

6.1 架构说明

       Sentinel1  Sentinel2  Sentinel3
           │          │          │
           └────┬─────┘──────────┘
                │ 监控
                ▼
            Master (6379)
           ┌────┼────┐
           ▼    ▼    ▼
        Slave1 Slave2 Slave3

6.2 配置哨兵

创建 sentinel.conf 文件:

# 哨兵监听端口
port 26379

# 后台运行
daemonize yes

# 日志文件
logfile "/var/log/redis/sentinel.log"

# 监控的主节点
# 格式: sentinel monitor <master-name> <ip> <port> <quorum>
# quorum: 需要多少个哨兵同意才能执行故障转移
sentinel monitor mymaster 192.168.1.100 6379 2

# 主节点密码
sentinel auth-pass mymaster your_master_password

# 主节点无响应超时时间(毫秒)
sentinel down-after-milliseconds mymaster 5000

# 故障转移超时时间(毫秒)
sentinel failover-timeout mymaster 60000

# 同时进行复制的从节点数量
sentinel parallel-syncs mymaster 1

6.3 启动与管理

# 启动哨兵
redis-sentinel /path/to/sentinel.conf

# 或者使用 redis-server
redis-server /path/to/sentinel.conf --sentinel

# 查看哨兵状态
redis-cli -p 26379 SENTINEL master mymaster

# 查看主节点下的从节点
redis-cli -p 26379 SENTINEL replicas mymaster

# 查看当前的主节点地址
redis-cli -p 26379 SENTINEL get-master-addr-by-name mymaster

# 手动触发故障转移
redis-cli -p 26379 SENTINEL failover mymaster

6.4 客户端连接方式

应用程序不直接连接主节点,而是先询问哨兵获取当前主节点地址:

# Python 示例(使用 redis-py)
from redis.sentinel import Sentinel

sentinel = Sentinel([
    ('192.168.1.101', 26379),
    ('192.168.1.102', 26379),
    ('192.168.1.103', 26379),
])

# 获取主节点连接(自动故障转移)
master = sentinel.master_for('mymaster', socket_timeout=0.5)
master.set('key', 'value')

# 获取从节点连接(读操作)
slave = sentinel.slave_for('mymaster', socket_timeout=0.5)
value = slave.get('key')

7. 集群搭建

Redis Cluster 是 Redis 的分布式解决方案,支持数据分片和高可用。

7.1 架构说明

Redis Cluster 将所有数据划分为 16384 个哈希槽(Hash Slot),分布在多个节点上。

Client
  │
  ├── Node A (slots 0-5460)      ← Master
  │     └── Node A1              ← Slave
  │
  ├── Node B (slots 5461-10922)  ← Master
  │     └── Node B1              ← Slave
  │
  └── Node C (slots 10923-16383) ← Master
        └── Node C1              ← Slave

7.2 搭建集群(6 节点,3主3从)

准备配置文件

为每个节点创建配置文件(以节点1为例):

# node1.conf
port 7001
cluster-enabled yes
cluster-config-file nodes-7001.conf
cluster-node-timeout 5000
appendonly yes
daemonize yes
logfile "redis-7001.log"
dir /data/redis/7001

依次创建 7001-7006 共 6 个配置文件。

启动所有节点

redis-server node1.conf
redis-server node2.conf
redis-server node3.conf
redis-server node4.conf
redis-server node5.conf
redis-server node6.conf

创建集群

# Redis 5+ 使用 redis-cli 创建集群
# --cluster-replicas 1 表示每个主节点配一个从节点
redis-cli --cluster create \
  127.0.0.1:7001 \
  127.0.0.1:7002 \
  127.0.0.1:7003 \
  127.0.0.1:7004 \
  127.0.0.1:7005 \
  127.0.0.1:7006 \
  --cluster-replicas 1

7.3 集群操作

# 连接集群(-c 表示集群模式)
redis-cli -c -p 7001

# 查看集群信息
127.0.0.1:7001> CLUSTER INFO

# 查看集群节点
127.0.0.1:7001> CLUSTER NODES

# 查看 key 属于哪个槽
127.0.0.1:7001> CLUSTER KEYSLOT mykey

# 使用 Hash Tag 确保相关 key 在同一个槽
# 只有 {} 中的部分用于计算槽位
SET {user:1001}:name "张三"
SET {user:1001}:age 25
# 这两个 key 会在同一个槽中

7.4 集群扩容与缩容

# 添加新主节点
redis-cli --cluster add-node 127.0.0.1:7007 127.0.0.1:7001

# 为新节点分配槽位(从现有节点迁移部分槽)
redis-cli --cluster reshard 127.0.0.1:7001

# 添加从节点
redis-cli --cluster add-node 127.0.0.1:7008 127.0.0.1:7001 \
  --cluster-slave --cluster-master-id <master-node-id>

# 移除节点(先迁走槽位)
redis-cli --cluster del-node 127.0.0.1:7001 <node-id>

8. 缓存策略

合理使用缓存策略可以最大化 Redis 的价值,避免常见问题。

8.1 缓存穿透

问题:查询一个一定不存在的数据,缓存和数据库都没有,每次请求都打到数据库。

解决方案

# 方案1:缓存空值
# 查询数据库未命中时,将空值缓存并设置较短过期时间
SET user:99999 "" EX 60

# 方案2:布隆过滤器(Redis 模块支持)
# 在缓存前加一层布隆过滤器,快速判断 key 是否可能存在
BF.ADD user_filter "user:1001"
BF.EXISTS user_filter "user:99999"
# 返回 0 表示一定不存在,直接返回

8.2 缓存击穿

问题:某个热点 key 过期的瞬间,大量请求同时打到数据库。

解决方案

# 方案1:互斥锁
# 用 SETNX 实现分布式锁,只允许一个请求重建缓存
SET lock:hot_key "1" NX EX 10
# 获取锁成功 → 查数据库 → 写缓存 → 释放锁
# 获取锁失败 → 等待后重试读缓存

# 方案2:逻辑过期
# 不设置物理过期时间,在 value 中记录逻辑过期时间
SET hot_key '{"data":"...","expire_at":1706246400}' 
# 读取时检查逻辑过期时间
# 已过期 → 异步更新缓存,同时返回旧数据

8.3 缓存雪崩

问题:大量缓存同时过期或 Redis 宕机,请求全部打到数据库。

解决方案

# 方案1:随机过期时间(避免同时过期)
# 基础过期时间 + 随机偏移
SET key1 "value1" EX 3600   # 基础3600秒
SET key2 "value2" EX 3623   # 3600 + 23
SET key3 "value3" EX 3587   # 3600 - 13

# 方案2:多级缓存
# L1: 本地缓存(进程内,如 Caffeine/Guava Cache)
# L2: Redis 分布式缓存
# L3: 数据库

# 方案3:熔断降级
# 缓存不可用时,直接返回默认值或降级页面

8.4 缓存更新策略

策略 说明 适用场景
Cache Aside 先更新数据库,再删除缓存 通用场景,推荐使用
Read/Write Through 缓存层统一代理读写 有缓存中间件时
Write Behind 只更新缓存,异步写数据库 写多读少,允许少量数据丢失
# Cache Aside 模式伪代码
# 读操作:
# 1. 先读缓存
# 2. 缓存命中 → 直接返回
# 3. 缓存未命中 → 读数据库 → 写入缓存 → 返回

# 写操作:
# 1. 先更新数据库
# 2. 再删除缓存(而非更新缓存)

9. 分布式锁

在分布式系统中,多个服务实例需要互斥访问共享资源时,需要分布式锁。

9.1 基于 SETNX 实现

# 加锁(原子操作:设置值 + 设置过期时间)
SET lock:order:1001 "holder_id_12345" NX EX 10

# NX: 只有 key 不存在时才设置(互斥)
# EX 10: 10秒后自动过期(防止死锁)

9.2 安全释放锁(Lua 脚本)

直接 DEL 删除锁可能误删别人的锁。使用 Lua 脚本保证只有持有者才能释放:

# 释放锁的 Lua 脚本
EVAL "
  if redis.call('GET', KEYS[1]) == ARGV[1] then
    return redis.call('DEL', KEYS[1])
  else
    return 0
  end
" 1 lock:order:1001 "holder_id_12345"

9.3 锁续期(看门狗机制)

对于耗时较长的任务,需要在锁过期前自动续期:

# 使用 Redisson(Java)等客户端自带的看门狗机制
# 手动续期的 Lua 脚本
EVAL "
  if redis.call('GET', KEYS[1]) == ARGV[1] then
    return redis.call('PEXPIRE', KEYS[1], ARGV[2])
  else
    return 0
  end
" 1 lock:order:1001 "holder_id_12345" 10000

9.4 Redlock 算法

单个 Redis 实例的分布式锁在主从切换时可能失效。Redlock 算法通过多个独立 Redis 实例来提高可靠性:

1. 获取当前时间 T1
2. 依次向 N 个独立 Redis 实例请求加锁(设置较短超时)
3. 计算加锁耗时 = 当前时间 T2 - T1
4. 如果在 N/2+1 个以上实例加锁成功,且耗时 < 锁的有效期 → 加锁成功
5. 锁的有效期 = 初始有效期 - 加锁耗时
6. 如果加锁失败,向所有实例发送释放锁请求

10. 消息队列

Redis 可以作为轻量级消息队列使用,适合对可靠性要求不那么极端的场景。

10.1 基于 List 的消息队列

# 生产者:发送消息
LPUSH queue:email '{"to":"user@example.com","subject":"Hello","body":"..."}'
LPUSH queue:email '{"to":"admin@example.com","subject":"Alert","body":"..."}'

# 消费者:阻塞读取(BRPOP 会阻塞直到有消息或超时)
BRPOP queue:email 30
# 输出: 1) "queue:email"
#       2) "{\"to\":\"admin@example.com\",\"subject\":\"Alert\",\"body\":\"...\"}"

10.2 基于 Pub/Sub 的发布订阅

# 订阅者(在终端1执行)
SUBSCRIBE channel:news

# 发布者(在终端2执行)
PUBLISH channel:news "Redis 7.0 正式发布!"
PUBLISH channel:news "新功能:Function 支持"

# 订阅模式匹配
PSUBSCRIBE channel:*

# 取消订阅
UNSUBSCRIBE channel:news

10.3 基于 Stream 的消息队列(Redis 5.0+)

Stream 是 Redis 5.0 引入的专门用于消息队列的数据结构,支持消费者组和消息确认。

# 添加消息
XADD mystream * name "张三" action "login"
# 输出: "1706246400000-0"(消息ID)

XADD mystream * name "李四" action "purchase" amount "99.9"

# 读取消息(从头开始)
XRANGE mystream - +

# 读取最新消息(阻塞等待)
XREAD COUNT 10 BLOCK 5000 STREAMS mystream $

# 创建消费者组
XGROUP CREATE mystream mygroup $ MKSTREAM

# 消费者组消费消息
XREADGROUP GROUP mygroup consumer1 COUNT 1 BLOCK 5000 STREAMS mystream >

# 确认消息已处理
XACK mystream mygroup 1706246400000-0

# 查看消费者组信息
XINFO GROUPS mystream

# 查看待处理消息(未确认的消息)
XPENDING mystream mygroup

# 查看 Stream 信息
XINFO STREAM mystream

# 设置 Stream 最大长度(自动裁剪)
XADD mystream MAXLEN 1000 * name "王五" action "logout"

11. 实战:秒杀系统缓存设计

下面以一个电商秒杀系统为例,展示如何综合运用 Redis 解决实际问题。

11.1 需求分析

  • 秒杀商品库存有限(如 100 件)
  • 瞬时并发极高(数万请求/秒)
  • 要求:不超卖、高性能、用户体验好

11.2 整体架构

用户请求 → Nginx → 应用服务 → Redis(库存扣减 + 用户去重)
                                       │
                                       └→ 消息队列 → 订单服务 → 数据库

11.3 Redis 缓存设计

库存预热

# 秒杀开始前,将库存加载到 Redis
SET seckill:stock:1001 100

# 记录商品信息
HMSET seckill:info:1001 \
  name "iPhone 15 Pro" \
  original_price "8999" \
  seckill_price "6999" \
  start_time "1706332800" \
  stock 100

原子库存扣减(Lua 脚本)

# 使用 Lua 脚本保证原子性:检查库存 + 检查重复购买 + 扣减库存
EVAL "
  local stock_key = 'seckill:stock:' .. ARGV[1]
  local users_key = 'seckill:users:' .. ARGV[1]
  local user_id = ARGV[2]

  -- 检查用户是否已购买
  if redis.call('SISMEMBER', users_key, user_id) == 1 then
    return -1  -- 已购买,返回-1
  end

  -- 检查库存
  local stock = tonumber(redis.call('GET', stock_key))
  if stock == nil or stock <= 0 then
    return 0  -- 库存不足,返回0
  end

  -- 扣减库存
  redis.call('DECR', stock_key)
  -- 记录用户已购买
  redis.call('SADD', users_key, user_id)
  -- 设置用户记录过期时间(24小时)
  redis.call('EXPIRE', users_key, 86400)

  return 1  -- 成功,返回1
" 0 "1001" "user:1001"

投递消息到队列

# 抢购成功后,发送消息到订单队列
XADD seckill:orders * \
  user_id "user:1001" \
  product_id "1001" \
  seckill_price "6999" \
  timestamp "1706332801"

11.4 完整流程代码(Python 伪代码)

import redis
import time

r = redis.Redis(host='localhost', port=6379, decode_responses=True)

# Lua 脚本:原子扣减库存
STOCK_LUA = """
local stock_key = 'seckill:stock:' .. ARGV[1]
local users_key = 'seckill:users:' .. ARGV[1]
local user_id = ARGV[2]

if redis.call('SISMEMBER', users_key, user_id) == 1 then
    return -1
end

local stock = tonumber(redis.call('GET', stock_key))
if stock == nil or stock <= 0 then
    return 0
end

redis.call('DECR', stock_key)
redis.call('SADD', users_key, user_id)
redis.call('EXPIRE', users_key, 86400)
return 1
"""

stock_script = r.register_script(STOCK_LUA)

def seckill(product_id, user_id):
    # 1. 原子扣减库存
    result = stock_script(args=[product_id, user_id])
    
    if result == -1:
        return {"code": 400, "msg": "您已抢购过该商品"}
    elif result == 0:
        return {"code": 400, "msg": "库存不足,已被抢光"}
    else:
        # 2. 发送订单消息到 Stream
        r.xadd('seckill:orders', {
            'user_id': user_id,
            'product_id': product_id,
            'timestamp': str(time.time())
        })
        return {"code": 200, "msg": "抢购成功,请在30分钟内完成支付"}


def order_consumer():
    """订单消费者:从 Stream 消费消息,创建订单"""
    while True:
        messages = r.xreadgroup(
            'order_group', 'consumer1',
            {'seckill:orders': '>'},
            count=1, block=5000
        )
        if messages:
            for stream, entries in messages:
                for msg_id, fields in entries:
                    try:
                        # 创建订单(写入数据库)
                        create_order(fields)
                        # 确认消息
                        r.xack('seckill:orders', 'order_group', msg_id)
                    except Exception as e:
                        print(f"处理失败: {e}")

11.5 关键设计要点

  1. 库存预热:秒杀开始前将库存加载到 Redis,避免穿透到数据库
  2. 原子操作:使用 Lua 脚本保证"检查库存 + 用户去重 + 扣减库存"的原子性
  3. 用户去重:用 Set 记录已购买用户,防止同一用户重复抢购
  4. 异步下单:抢购成功后通过消息队列异步创建订单,快速返回结果
  5. 限流保护:在 Nginx 层或应用层做请求限流,过滤无效请求
  6. 兜底方案:即使 Redis 不可用,也要有降级策略(如直接返回"系统繁忙")

12. 性能优化与最佳实践

12.1 Key 命名规范

# 推荐格式: 业务:对象:ID:属性
user:1001:profile
user:1001:session
order:2024:1001:detail
product:1001:stock

# 避免的命名
userinfo          # 太模糊
user1001profile   # 无分隔符,难以阅读
my.key.name       # 使用了错误的分隔符(点号可能被部分客户端特殊处理)

12.2 大 Key 问题

# 查找大 Key
redis-cli --bigkeys

# 扫描指定类型的大 Key
redis-cli --bigkeys --i 0.1

# 使用 MEMORY USAGE 查看单个 key 的内存占用
MEMORY USAGE user:1001

# 大 Key 拆分策略:
# Hash 太大 → 按字段分片: user:1001:basic, user:1001:extra
# Set/ZSet 太大 → 按范围分片: tags:page:1, tags:page:2
# String 太大 → 压缩或拆分

12.3 Pipeline 批量操作

# 使用 Pipeline 减少网络往返
# 普通模式:N 次请求 = N 次网络往返
# Pipeline 模式:N 次请求 = 1 次网络往返

# redis-cli 中使用 Pipeline
echo -e "SET key1 val1\nSET key2 val2\nSET key3 val3" | redis-cli --pipe

12.4 内存优化

# 查看内存使用情况
INFO memory

# 使用 OBJECT 命令查看 key 的编码
OBJECT ENCODING mykey

# 小对象使用 ziplist 编码更省内存
# 配置 ziplist 的阈值
hash-max-ziplist-entries 128
hash-max-ziplist-value 64
list-max-ziplist-size -2
zset-max-ziplist-entries 128
zset-max-ziplist-value 64

# 整数集合(intset)优化
set-max-intset-entries 512

12.5 连接池配置

# Python redis-py 连接池示例
import redis

pool = redis.ConnectionPool(
    host='localhost',
    port=6379,
    password='your_password',
    max_connections=50,       # 最大连接数
    decode_responses=True,
    socket_timeout=5,         # 读写超时
    socket_connect_timeout=2, # 连接超时
    retry_on_timeout=True,    # 超时时重试
)

r = redis.Redis(connection_pool=pool)

12.6 监控与运维

# 实时监控命令(生产环境慎用,影响性能)
MONITOR

# 查看慢查询日志
SLOWLOG GET 10
SLOWLOG LEN
SLOWLOG RESET

# 配置慢查询阈值(微秒)
CONFIG SET slowlog-log-slower-than 10000  # 10ms

# 查看客户端连接
CLIENT LIST

# 查看实时统计
INFO stats

# 查看键空间命中率
INFO keyspace
# 关注: keyspace_hits 和 keyspace_misses
# 命中率 = hits / (hits + misses),应保持在 90% 以上

附录:常用命令速查表

操作 命令 说明
通用 KEYS pattern 查找匹配的 key(生产环境慎用)
通用 SCAN cursor MATCH pattern COUNT 100 安全的迭代扫描
通用 TYPE key 查看 key 的类型
通用 TTL key 查看剩余过期时间
通用 PERSIST key 移除过期时间
通用 RENAME key newkey 重命名 key
通用 DBSIZE 当前数据库 key 数量
通用 FLUSHDB 清空当前数据库(危险!)
通用 FLUSHALL 清空所有数据库(危险!)
服务器 INFO [section] 查看服务器信息
服务器 CONFIG GET parameter 获取配置
服务器 CONFIG SET parameter value 修改配置(运行时)
服务器 CONFIG REWRITE 将运行时配置写回文件
服务器 BGSAVE 后台保存 RDB
服务器 BGREWRITEAOF 后台重写 AOF
服务器 DEBUG SLEEP 0 测试连接延迟

总结

本教程从零基础出发,系统地讲解了 Redis 的核心知识:

  1. 基础:Redis 的特点、安装配置、CLI 使用
  2. 数据类型:String、List、Hash、Set、Sorted Set 五大类型的用法和适用场景
  3. 持久化:RDB、AOF、混合持久化的原理和配置
  4. 高可用:主从复制、哨兵模式、集群搭建
  5. 实战技巧:缓存策略(穿透/击穿/雪崩)、分布式锁、消息队列
  6. 综合项目:秒杀系统的完整缓存设计

Redis 是一个功能强大且灵活的工具,关键在于根据业务场景选择合适的数据类型和策略。建议在本地搭建 Redis 环境,动手实践每一个命令和示例,加深理解。

持续学习:Redis 官方文档 https://redis.io/docs/ 是最好的参考资料,建议收藏并经常查阅。

内容声明

本文内容为AI技术学习教程,仅供学习参考。如涉及技术问题,欢迎通过 xurj005@163.com 与我们交流。

目录