Redis 是一个开源的、基于内存的高性能键值存储数据库,支持丰富的数据结构类型,单节点 QPS 可达 10 万+,读写延迟低至亚毫秒级别。本文从 Redis 的核心架构、数据结构、持久化机制、内存管理、高可用方案等维度进行系统性的深度剖析。
一、Redis 概述
1.1 什么是 Redis
Redis(Remote Dictionary Server)是一个基于内存的键值存储系统,由 Salvatore Sanfilippo(antirez)于 2009 年使用 C 语言编写。它属于 NoSQL 数据库中的键值存储类,但远不止是一个简单的 KV 缓存——Redis 提供了 String、List、Hash、Set、ZSet、Stream、JSON、Bitmap、HyperLogLog、Geospatial 等丰富的数据结构,使其可以同时作为数据库、缓存、消息中间件和流式引擎使用。
1.2 Redis 的核心定位
| 内存数据库 | 所有数据存储在内存中,提供亚毫秒级读写延迟 |
| 缓存引擎 | 作为关系数据库前的缓存层,大幅降低 DB 压力 |
| 消息中间件 | 支持 Pub/Sub 发布订阅和 Stream 消息流 |
| 流式引擎 | Stream 数据类型支持消息持久化、消费组和 ACK 机制 |
| 向量数据库 | Redis 7.4+ 支持向量集(Vector Sets),可用于语义搜索和 RAG |
1.3 Redis 为什么快
Redis 的高性能是五个层面协同作用的结果:
| 存储介质 | 纯内存操作 | 消除磁盘 IO 瓶颈,延迟从 ms 级降到 μs 级 |
| 数据结构 | 精心设计的底层编码 | 同一 API 根据数据规模自动切换最优编码 |
| IO 模型 | epoll 多路复用 | 单线程处理数万并发连接,避免线性扫描 |
| 线程模型 | 核心单线程 | 无锁设计,消除上下文切换和竞争开销 |
| 协议设计 | RESP 协议 | 文本协议解析快速,支持 Pipeline 批量操作 |
二、Redis 核心架构
2.1 整体架构分层
Redis 的架构可以分为四层:
客户端层:客户端通过 RESP(Redis Serialization Protocol)协议与服务端通信。RESP 是一种文本协议,格式简单、解析快速,支持五种数据类型:简单字符串(+)、错误(-)、整数(:)、批量字符串($)和数组(*)。
网络层:Redis 使用基于 epoll 的 IO 多路复用模型,单线程即可处理数万个并发连接。核心组件 aeEventLoop 负责监听文件描述符事件(可读/可写/超时),并将事件分发给对应的处理器。
核心层:包含命令处理器、数据库字典、过期字典和内存分配器。每个 Redis 实例默认有 16 个数据库(编号 0-15),每个数据库维护两个字典:
- 键空间字典(key space dict):存储所有键值对,key 为字符串,value 为 redisObject
- 过期字典(expires dict):存储键的过期时间戳,key 指向键空间中的同一个键,value 为 Unix 时间戳
持久化层:RDB 快照和 AOF 日志两种机制确保数据持久性。
2.2 单线程模型解析
Redis 的核心命令处理是单线程的(Redis 6.0 之前完全单线程,6.0 引入多线程仅用于网络 IO 解析):
┌──────────────────────────────────────────────┐
│ Redis 事件循环 │
│ ┌─────────┐ ┌──────────┐ ┌─────────────┐ │
│ │ 文件事件 │ │ 时间事件 │ │ 命令执行器 │ │
│ │ (epoll) │ │(serverCron)│ │ (processCmd)│ │
│ └────┬────┘ └────┬─────┘ └──────┬──────┘ │
│ │ │ │ │
│ 读写客户端数据 定时任务调度 执行具体命令 │
│ 协议解析/编码 过期key清理 数据操作 │
│ 复制同步 AOF重写触发 持久化 │
└──────────────────────────────────────────────┘
为什么单线程还这么快?
Redis 6.0 多线程改动:Redis 6.0 引入了多线程网络 IO(默认关闭),将协议解析和响应编码从主线程卸载到 IO 线程,但命令执行仍然是单线程的,保证原子性不变。
2.3 RESP 协议
RESP 协议是 Redis 客户端与服务端通信的基础:
# 客户端请求(RESP 数组格式)
*3\\r\\n$3\\r\\nSET\\r\\n$5\\r\\nmykey\\r\\n$7\\r\\nmyvalue\\r\\n
# 服务端响应
+OK\\r\\n # 简单字符串
-Error message\\r\\n # 错误
:1000\\r\\n # 整数
$5\\r\\nhello\\r\\n # 批量字符串
*2\\r\\n$3\\r\\nfoo\\r\\n$3\\r\\nbar\\r\\n # 数组
# RESP3 新增类型(Redis 6.0+)
%2\\r\\n # Map 类型
~3\\r\\n # Set 类型
=15\\r\\n…txt\\r\\n # Verbatim 字符串
三、Redis 数据结构
3.1 两层架构设计
Redis 最精妙的设计之一是"两层架构"——对外暴露 5 种核心逻辑数据类型,底层根据数据量和元素大小自动选择最优的物理编码。这两层通过 redisObject 结构体桥接:
typedef struct redisObject {
unsigned type:4; // 逻辑类型(STRING/LIST/HASH/SET/ZSET)
unsigned encoding:4; // 物理编码(int/embstr/raw/hashtable/skiplist…)
unsigned lru:24; // LRU 淘汰信息(高16位为时间,低8位为频率)
int refcount; // 引用计数
void *ptr; // 指向底层数据结构的指针
} robj;
3.2 逻辑类型与物理编码映射
| String | int | 值为 64 位整数 |
| embstr | 字符串长度 ≤ 44 字节 | |
| raw(SDS) | 字符串长度 > 44 字节 | |
| List | listpack | 元素数 ≤ 128 且每个元素 ≤ 64 字节 |
| quicklist | 超出上述阈值 | |
| Hash | listpack | 键值对数 ≤ 128 且每个值 ≤ 64 字节 |
| hashtable | 超出上述阈值 | |
| Set | intset | 所有元素均为整数且元素数 ≤ 512 |
| listpack | 元素数 ≤ 128 且每个元素 ≤ 64 字节 | |
| hashtable | 超出上述阈值 | |
| ZSet | listpack | 元素数 ≤ 128 且每个元素 ≤ 64 字节 |
| skiplist + hashtable | 超出上述阈值 |
以上阈值均可通过配置调整,如 list-max-listpack-size、set-max-listpack-entries 等。
3.3 核心底层数据结构详解
3.3.1 SDS(Simple Dynamic String)简单动态字符串
Redis 没有使用 C 语言原生字符串,而是自己实现了 SDS:
struct sdshdr {
int len; // 已使用长度
int free; // 剩余可用长度
char buf[]; // 字符数组
};
相比 C 字符串的优势:
| 获取长度 | O(N) 需遍历 | O(1) 直接读取 len 字段 |
| 二进制安全 | 不安全(\\0 为终止符) | 安全(用 len 判断长度) |
| 修改追加 | 可能溢出 | 自动扩容(先检查空间不足再分配) |
| 内存分配 | 每次修改都重新分配 | 空间预分配 + 惰性释放,减少分配次数 |
扩容策略:
- 修改后长度 < 1MB:分配 2 倍空间 + 1 字节
- 修改后长度 ≥ 1MB:分配当前长度 + 1MB + 1 字节
3.3.2 跳表(Skip List)
跳表是 ZSet 底层的核心排序引擎,采用"空间换时间"思想,通过多层索引将链表 O(N) 的查找优化到 O(logN):
Level 3: 1 ──────────────────────────────────> 9
Level 2: 1 ────────────> 4 ──────────────────> 9
Level 1: 1 ───> 3 ────> 4 ───> 6 ───────────> 9
Level 0: 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9
查找元素 7 的路径:Level 3: 1 → 9(9>7,下降)→ Level 2: 1 → 4 → 9(9>7,下降)→ Level 1: 4 → 6 → 9(9>7,下降)→ Level 0: 6 → 7(找到!),只访问了 7 个节点。
typedef struct zskiplistNode {
sds ele; // 成员值
double score; // 排序分数
struct zskiplistNode *backward; // 后退指针
struct zskiplistLevel {
struct zskiplistNode *forward; // 前进指针
unsigned long span; // 跨度(用于排名计算)
} level[]; // 柔性数组,层数随机
} zskiplistNode;
为什么选跳表不选红黑树?
| 代码复杂度 | 约 300 行,实现简单 | 500+ 行,旋转操作复杂 |
| 范围查询 | 找到起点后沿链表遍历即可 | 需要中序遍历,实现复杂 |
| 内存开销 | 平均每个节点 1.33 个指针(p=0.25) | 固定 3 个指针 |
| 并发友好 | 局部修改不影响全局结构 | 旋转操作影响全局 |
3.3.3 压缩列表(Ziplist / Listpack)
压缩列表是一段连续内存块,去除了指针开销,通过极致紧凑的编码方式节省内存:
┌──────────┬──────────┬──────────┬──────────┬──────┐
│ zlbytes │ zltail │ zllen │ entry1 │ … │
│ 4 bytes │ 4 bytes │ 2 bytes │ 变长编码 │ │
└──────────┴──────────┴──────────┴──────────┴──────┘
- zlbytes:整个列表占用的字节数
- zltail:尾节点的偏移量,方便从尾部遍历
- zllen:节点数量
- entry:每个节点包含 prevlen(前一节点长度)+ encoding(编码方式)+ data(数据)
缺点:连锁更新问题——当插入/删除节点导致前一节点长度变化时,后续所有节点的 prevlen 都需要重新编码,最坏情况 O(N)。Redis 7.0 引入 listpack 替代 ziplist,通过在每个节点中记录总长度来解决连锁更新问题。
3.3.4 整数集合(IntSet)
IntSet 是专门存储纯整数集合的紧凑结构:
typedef struct intset {
uint32_t encoding; // 编码方式(INTSET_ENC_INT16/32/64)
uint32_t length; // 元素个数
int8_t contents[]; // 有序整数数组
} intset;
- 所有元素均为整数时才使用 intset
- 元素按值排序存储,支持二分查找
- 自动升级编码:当插入更大范围的整数时,从 int16 升级到 int32 或 int64
3.4 五种核心逻辑类型详解
3.4.1 String(字符串)
String 是最基础也是最灵活的类型,可以存储字符串、整数或浮点数:
# 基本操作
SET user:1:name "张三" # 设置值
GET user:1:name # 获取值 → "张三"
INCR page:views # 原子递增(计数器)
SET session:token "abc" EX 3600 # 设置值并指定过期时间(秒)
SETNX lock:order "1" # 仅当 key 不存在时设置(分布式锁)
典型应用:缓存热点数据、分布式锁、计数器、Session 共享、分布式 ID 生成
3.4.2 Hash(哈希)
Hash 适合存储对象,一个 key 映射多个 field-value:
# 基本操作
HSET user:1 name "张三" age 28 city "北京" # 设置多个字段
HGET user:1 name # 获取单个字段 → "张三"
HMGET user:1 name age city # 获取多个字段
HINCRBY user:1 age 1 # 字段值原子递增
HGETALL user:1 # 获取所有字段和值
vs String 存 JSON:Hash 可以单独获取/修改某个字段,无需序列化/反序列化整个对象,内存更省(小对象用 listpack 编码时尤其明显)。
3.4.3 List(列表)
List 是有序的元素集合,底层用双向链表或 quicklist 实现:
# 基本操作
LPUSH queue:task "task1" "task2" # 从左侧压入
RPUSH queue:task "task3" # 从右侧压入
LPOP queue:task # 从左侧弹出 → "task2"
RPOP queue:task # 从右侧弹出
LRANGE queue:task 0 -1 # 获取所有元素
BLPOP queue:task 30 # 阻塞式弹出(超时30秒)
典型应用:消息队列、最新列表(Timeline)、文章列表、LRU 淘汰近似实现
3.4.4 Set(集合)
Set 是无序、不重复的元素集合,支持交并差集运算:
# 基本操作
SADD tags:article:1 "Redis" "缓存" "数据库"
SADD tags:article:2 "Redis" "高可用" "集群"
SMEMBERS tags:article:1 # 获取所有成员
SISMEMBER tags:article:1 "Redis" # 判断是否存在 → 1
SINTER tags:article:1 tags:article:2 # 交集 → {"Redis"}
SUNION tags:article:1 tags:article:2 # 并集 → {"Redis","缓存","数据库","高可用","集群"}
SDIFF tags:article:1 tags:article:2 # 差集 → {"缓存","数据库"}
SRANDMEMBER tags:article:1 2 # 随机获取2个
典型应用:标签系统、共同好友、抽奖、去重、推荐(交集/并集运算)
3.4.5 ZSet(有序集合)
ZSet 在 Set 的基础上为每个元素关联一个 score,按 score 排序:
# 基本操作
ZADD leaderboard 100 "player1" 200 "player2" 150 "player3"
ZINCRBY leaderboard 50 "player1" # player1 分数+50 → 150
ZRANGE leaderboard 0 -1 WITHSCORES # 按分数升序
ZREVRANGE leaderboard 0 2 WITHSCORES # 按分数降序,取前3
ZRANGEBYSCORE leaderboard 100 200 # 按分数范围查询
ZRANK leaderboard "player1" # 获取排名(从0开始)
ZCARD leaderboard # 获取元素数量
典型应用:排行榜、延迟队列(score 为执行时间)、带权重的任务队列、滑动窗口限流
四、Redis 持久化机制
4.1 RDB 快照(Redis Database)
RDB 是 Redis 默认的持久化方式,将某一时刻的全量数据以二进制快照的形式保存到磁盘:
触发方式:
| 手动触发 | SAVE / BGSAVE | SAVE 阻塞主线程,BGSAVE fork 子进程 |
| 配置触发 | save 900 1 | 900 秒内至少有 1 次写操作则触发 |
| save 300 10 | 300 秒内至少有 10 次写操作则触发 | |
| save 60 10000 | 60 秒内至少有 10000 次写操作则触发 | |
| 自动触发 | 主从复制全量同步时 | 主节点自动生成 RDB 发送给从节点 |
BGSAVE 工作流程:
1. Redis 主进程 fork 一个子进程
2. 子进程将内存数据写入临时 RDB 文件
3. 期间主进程继续处理客户端请求(利用 COW 机制)
4. 子进程完成写入后,用临时文件替换旧 RDB 文件
5. 父进程收到子进程完成信号
Copy-On-Write(COW)机制:fork 时父子进程共享同一物理内存页,当主进程修改某个内存页时,操作系统会复制该页给主进程,子进程仍持有原始数据。这使得 BGSAVE 期间对内存的额外开销取决于写操作的多少。
优缺点:
- ✅ 文件紧凑,恢复速度快(直接加载到内存)
- ✅ 对 Redis 性能影响小(子进程处理,主进程不阻塞)
- ❌ 可能丢失最后一次快照之后的数据
- ❌ 数据量大时 fork 可能阻塞(fork 本身是阻塞操作)
4.2 AOF(Append Only File)
AOF 以日志形式记录每一条写命令,通过重放日志恢复数据:
三种刷盘策略:
| 每次写入都 fsync | always | 最高,几乎不丢数据 | 最差 |
| 每秒 fsync 一次 | everysec(默认) | 较高,最多丢 1 秒数据 | 平衡 |
| 由操作系统决定 | no | 最低,丢失时长由 OS 策略决定 | 最好 |
AOF 重写(Rewrite):
AOF 文件会不断膨胀(记录了大量历史写命令),需要定期重写压缩:
# 触发条件(默认配置)
auto-aof-rewrite-min-size 64mb # AOF 文件至少 64MB 才触发重写
auto-aof-rewrite-percentage 100 # AOF 文件比上次重写后增长了 100% 时触发
# 重写流程
1. fork 子进程
2. 子进程根据当前内存数据生成新 AOF 文件(只写最终状态,忽略中间命令)
3. 期间新的写命令同时追加到旧 AOF 和重写缓冲区
4. 子进程完成新文件后,将缓冲区中的增量命令追加到新文件
5. 原子替换旧 AOF 文件
4.3 混合持久化(Redis 4.0+)
Redis 4.0 引入了混合持久化模式,将 RDB 和 AOF 结合:
# 开启混合持久化
aof-use-rdb-preamble yes
# 文件格式
┌─────────────────┬─────────────────────┐
│ RDB 格式的全量数据 │ AOF 格式的增量命令 │
│ (最近一次快照) │ (快照后的新写操作) │
└─────────────────┴─────────────────────┘
优点:
- 加载速度快(前半部分是 RDB 格式,直接加载到内存)
- 数据丢失少(后半部分 AOF 记录了增量命令)
- 兼顾了恢复速度和数据完整性
4.4 持久化选型建议
| 纯缓存,允许丢数据 | 关闭持久化或仅 RDB | 性能最优,恢复快 |
| 一般缓存 | RDB + AOF(everysec) | 数据安全且性能平衡 |
| 数据不能丢失 | AOF(always) | 最高安全性,牺牲性能 |
| 生产推荐 | 混合持久化 | 加载快 + 数据完整 |
五、Redis 内存管理
5.1 内存模型
Redis 的内存占用由五个部分组成:
┌──────────────────────────────────────────┐
│ Redis 进程总内存 │
│ ┌────────────┐ ┌────────────────────┐ │
│ │ 数据内存 │ │ 进程运行内存 │ │
│ │(键值对数据)│ │(代码/常量/堆栈) │ │
│ └────────────┘ └────────────────────┘ │
│ ┌────────────────────────────────────┐ │
│ │ 缓冲区内存 │ │
│ │ ┌──────────┬──────────┬────────┐ │ │
│ │ │ 客户端 │ 复制 │ Pub/Sub│ │ │
│ │ │ 输入输出 │ 积压缓冲 │ 缓冲区 │ │ │
│ │ └──────────┴──────────┴────────┘ │ │
│ └────────────────────────────────────┘ │
│ ┌────────────────────────────────────┐ │
│ │ Lua 脚本内存(脚本+临时数据) │ │
│ └────────────────────────────────────┘ │
└──────────────────────────────────────────┘
5.2 内存分配器
Redis 不直接向操作系统申请/释放内存,而是通过内存分配器统一管理:
| jemalloc(默认) | 内存碎片率极低,将内存划分为不同大小的块,按申请大小匹配最合适的块 |
| tcmalloc | Google 开发,在高并发场景下性能略优 |
| libc malloc | 系统默认,碎片率较高 |
通过 INFO memory 命令可以查看内存指标,关键指标:
- used_memory:Redis 分配器分配的内存总量
- used_memory_rss:操作系统分配给 Redis 的物理内存
- mem_fragmentation_ratio:rss / used_memory,碎片率指标,一般 1.0-1.5 为正常
5.3 过期删除策略
Redis 采用 惰性删除 + 定期删除 组合策略清理过期 key:
惰性删除:
- 每次客户端访问某个 key 时,Redis 先检查该 key 是否已过期
- 已过期则立即删除并返回空值
- 优点:CPU 零额外开销
- 缺点:过期 key 若长期不被访问,会永久占用内存
定期删除:
- 定时任务 serverCron(默认每 100ms 执行一次)调用 activeExpireCycle 函数
- 遍历过期键哈希槽抽样检查,单个槽位一轮最多 20 次采样
- 抽样检测到过期 key 立即删除;样本过期占比≥25% 则持续循环抽样
- 存在执行时长上限,默认最多运行 25ms,避免阻塞主线程
- 优点:主动清理冷数据,平衡 CPU 与内存
5.4 内存淘汰策略
当已使用内存达到 maxmemory 阈值时,Redis 根据 maxmemory-policy 配置执行淘汰:
| noeviction | 不淘汰 | 写操作返回 OOM 错误 | 核心数据不可丢失 |
| allkeys-lru | 所有 key | 淘汰最近最少使用的 key | 纯缓存场景首选 |
| allkeys-lfu | 所有 key | 淘汰访问频次最低的 key(Redis 4.0+) | 纯缓存,有明确热点 |
| allkeys-random | 所有 key | 随机淘汰 | 极少使用 |
| volatile-lru | 仅过期 key | 淘汰过期 key 中最近最少使用的 | 混合存储(永久+临时) |
| volatile-lfu | 仅过期 key | 淘汰过期 key 中访问频次最低的 | 混合存储 |
| volatile-random | 仅过期 key | 随机淘汰过期 key | 过期 key 无明显热点 |
| volatile-ttl | 仅过期 key | 淘汰剩余 TTL 最短的 key | 短期临时缓存 |
LRU vs LFU:
- LRU(最近最少使用):基于时间维度,淘汰最久未访问的 key。缺点:曾经的热点 key 长期不访问后仍占内存(缓存污染)
- LFU(最不经常使用):基于频次维度,利用 redisObject 的 24 位 lru 字段(高 16 位记录时间,低 8 位用 Morris 概率计数器记录频次),配合时间衰减机制(lfu-decay-time)
生产选型建议:
- 纯缓存 → allkeys-lfu 或 allkeys-lru
- 混合存储 → volatile-lru 或 volatile-lfu
- 核心数据不丢 → noeviction(配合手动扩容)
六、Redis 事务与高级特性
6.1 事务机制
Redis 事务通过 MULTI/EXEC 实现:
MULTI # 开启事务
SET account:1:balance 100
INCRBY account:1:balance -50
SET account:2:balance 50
INCRBY account:2:balance 50
EXEC # 执行事务(按顺序原子执行)
特性:
- 命令入队时不做执行,只返回 QUEUED
- EXEC 时按顺序依次执行所有命令
- 不支持回滚——如果某条命令执行出错,后续命令仍然执行
- 通过 WATCH 实现乐观锁:
WATCH account:1:balance # 监视 key
MULTI
# … 检查余额并扣款
EXEC # 如果 WATCH 的 key 被其他客户端修改,EXEC 返回 nil
6.2 Pipeline(管道)
Pipeline 将多个命令打包一次性发送到服务端,减少网络 RTT:
# 不用 Pipeline(4次网络往返)
SET key1 value1
SET key2 value2
SET key3 value3
GET key1
# 使用 Pipeline(1次网络往返)
Pipeline:
SET key1 value1
SET key2 value2
SET key3 value3
GET key1
性能差异:单条命令 0.1ms RTT,1000 条命令不用 Pipeline 需要 100ms,使用 Pipeline 约 10ms。
6.3 Lua 脚本
Redis 支持执行 Lua 脚本,保证脚本内命令的原子性:
EVAL "
local current = redis.call('GET', KEYS[1])
if current == false then
redis.call('SET', KEYS[1], ARGV[1])
return 1
end
return 0
" 1 mykey myvalue
Lua 脚本的原子性保证:整个脚本作为一个命令执行,执行期间不会被其他命令插入。适合实现分布式锁、限流器等需要原子性的复杂操作。
6.4 Pub/Sub 发布订阅
# 订阅频道
SUBSCRIBE news:sports news:tech
# 发布消息
PUBLISH news:sports "国足2:1胜出"
# 模式订阅(支持通配符)
PSUBSCRIBE news:*
Stream(Redis 5.0+) 是对 Pub/Sub 的升级,支持消息持久化、消费者组和 ACK:
# 创建消息
XADD mystream * sensor_id 1234 temperature 19.8
# 消费者组
XGROUP CREATE mystream mygroup $ MKSTREAM
XREADGROUP GROUP mygroup consumer1 COUNT 10 BLOCK 5000 STREAMS mystream >
XACK mystream mygroup 1526985685-0
七、Redis 高可用架构
7.1 主从复制
核心原理:
┌──────────┐ 全量同步(首次) ┌──────────┐
│ Master │ ──────────────────> │ Slave1 │
│ (主节点) │ 增量同步(后续) │ (从节点) │
│ │ ──────────────────> │ │
│ │ └──────────┘
│ │ 全量/增量同步 ┌──────────┐
│ │ ──────────────────> │ Slave2 │
└──────────┘ │ (从节点) │
└──────────┘
全量同步(首次连接或断连过久):
增量同步(正常情况):
7.2 哨兵模式(Sentinel)
哨兵是主从复制的升级方案,提供自动故障转移:
核心功能:
- 监控:持续检测 Master 和 Slave 是否正常运行
- 通知:当被监控的实例出现问题时,通过 Pub/Sub 通知管理员
- 自动故障转移:Master 不可用时,自动将某个 Slave 提升为新的 Master
- 配置中心:客户端连接 Sentinel 获取当前 Master 地址,故障转移后自动更新
故障判定流程:
# Sentinel 配置示例
sentinel monitor mymaster 127.0.0.1 6379 2 # 监控主节点,2个sentinel同意即故障转移
sentinel down-after-milliseconds mymaster 5000 # 5秒无响应视为下线
sentinel parallel-syncs mymaster 1 # 故障转移后一次只同步一个从节点
sentinel failover-timeout mymaster 60000 # 故障转移超时时间
7.3 Redis Cluster(集群)
Redis Cluster 是 Redis 官方的分布式方案(Redis 3.0+),提供数据分片、高可用和水平扩展:
核心设计:
- 数据分片:使用哈希槽(Hash Slot)机制,共 16384 个槽
- 槽分配:CRC16(key) % 16384 决定 key 属于哪个槽
- 节点分配:每个 Master 负责一部分槽,Slave 作为对应 Master 的备份
架构示例(3 主 3 从):
Master A: slot 0-5460 ←→ Slave A': 备份 Master A
Master B: slot 5461-10922 ←→ Slave B': 备份 Master B
Master C: slot 10923-16383 ←→ Slave C': 备份 Master C
关键特性:
| 去中心化 | 所有节点通过 Gossip 协议通信,无中心代理 |
| 客户端重定向 | 访问错误的节点时返回 MOVED 重定向到正确的节点 |
| 在线扩缩容 | 通过迁移槽实现节点的添加和移除,服务不中断 |
| 故障转移 | 集群中多数 Master 认为某 Master 不可达时,自动提升其 Slave |
| 异步复制 | 主从之间异步复制,极端情况下可能丢数据 |
MOVED 与 ASK:
- MOVED slot ip:port:槽已永久迁移,客户端更新本地路由表
- ASK slot ip:port:槽正在迁移中(临时状态),客户端本次重定向,不更新路由表
Hash Tag:通过 {} 强制相关 key 落在同一槽:
# 以下两个 key 会落在同一个槽(因为 hash tag 都是 user:1)
SET {user:1}:name "张三"
SET {user:1}:age 28
八、Redis 生产实践
8.1 缓存三大问题
| 缓存穿透 | 查询不存在的数据,每次都穿透到 DB | 布隆过滤器;空值缓存(设短 TTL) |
| 缓存击穿 | 热点 key 过期瞬间,大量请求打到 DB | 互斥锁(SETNX);热点 key 永不过期;逻辑过期 |
| 缓存雪崩 | 大量 key 同时过期或 Redis 宕机 | TTL 加随机值;多级缓存;集群高可用 |
8.2 分布式锁实现
# 加锁(原子操作)
SET lock:order:123 uuid NX PX 30000
# 解锁(Lua 脚本保证原子性)
EVAL "
if redis.call('GET', KEYS[1]) == ARGV[1] then
return redis.call('DEL', KEYS[1])
else
return 0
end
" 1 lock:order:123 uuid
生产建议:单机锁用 Redis 即可;多节点强一致性场景推荐使用 RedLock 或 ZooKeeper。
8.3 Redis 与其他组件的对比
| 数据结构 | 丰富(String/Hash/List/Set/ZSet/Stream) | 简单 KV | 文档型(BSON) |
| 持久化 | RDB + AOF | 不支持 | 原生支持 |
| 线程模型 | 单线程执行(6.0+ 多线程IO) | 多线程 | 多线程 |
| 集群 | Cluster(原生分片) | 客户端分片 | 原生分片 |
| 内存管理 | 精细(jemalloc + 多种淘汰策略) | 简单 slab 分配 | WiredTiger 引擎 |
| 事务 | MULTI/EXEC(不支持回滚) | 不支持 | 多文档事务(4.0+) |
| 适用场景 | 缓存/队列/排行榜/分布式锁 | 简单缓存 | 文档存储/内容管理 |
8.4 关键配置调优
# 内存配置
maxmemory 8gb # 最大内存(建议预留 30% 给系统)
maxmemory-policy allkeys-lfu # 淘汰策略
# 持久化配置
save 900 1 # RDB 触发条件
save 300 10
save 60 10000
aof-use-rdb-preamble yes # 混合持久化
auto-aof-rewrite-percentage 100 # AOF 重写触发
# 网络配置
tcp-backlog 511 # TCP 全连接队列
timeout 300 # 空闲连接超时(0为不超时)
tcp-keepalive 300 # TCP 保活时间
# 安全配置
requirepass your-strong-password # 设置密码
rename-command FLUSHALL "" # 禁用危险命令
rename-command CONFIG ""
# 性能配置
hz 10 # serverCron 执行频率(默认10,高负载可调至100)
slowlog-log-slower-than 10000 # 慢查询阈值(微秒)
slowlog-max-len 128 # 慢查询日志最大条数
九、 安装配置
9.1 下载安装
手动创建 SCLo-scl 仓库文件
sudo vi /etc/yum.repos.d/CentOS-SCLo-scl.repo
输入以下内容
[centos-sclo-sclo]
name=CentOS-7 – SCLo sclo
baseurl=https://mirrors.aliyun.com/centos/7/sclo/$basearch/sclo/
gpgcheck=1
enabled=1
gpgkey=https://mirrors.aliyun.com/centos/RPM-GPG-KEY-CentOS-7
[centos-sclo-sclo-source]
name=CentOS-7 – SCLo sclo Sources
baseurl=https://mirrors.aliyun.com/centos/7/sclo/Source/sclo/
gpgcheck=1
enabled=0
gpgkey=https://mirrors.aliyun.com/centos/RPM-GPG-KEY-CentOS-7
[centos-sclo-sclo-debuginfo]
name=CentOS-7 – SCLo sclo Debug
baseurl=https://mirrors.aliyun.com/centos/7/sclo/$basearch/debug/sclo/
gpgcheck=1
enabled=0
gpgkey=https://mirrors.aliyun.com/centos/RPM-GPG-KEY-CentOS-7
手动创建 SCLo-rh 仓库文件(devtoolset 依赖此库)
sudo vi /etc/yum.repos.d/CentOS-SCLo-scl-rh.repo
输入以下内容
[centos-sclo-rh]
name=CentOS-7 – SCLo rh
baseurl=https://mirrors.aliyun.com/centos/7/sclo/$basearch/rh/
gpgcheck=1
enabled=1
gpgkey=https://mirrors.aliyun.com/centos/RPM-GPG-KEY-CentOS-7
[centos-sclo-rh-source]
name=CentOS-7 – SCLo rh Sources
baseurl=https://mirrors.aliyun.com/centos/7/sclo/Source/rh/
gpgcheck=1
enabled=0
gpgkey=https://mirrors.aliyun.com/centos/RPM-GPG-KEY-CentOS-7
[centos-sclo-rh-debuginfo]
name=CentOS-7 – SCLo rh Debug
baseurl=https://mirrors.aliyun.com/centos/7/sclo/$basearch/debug/rh/
gpgcheck=1
enabled=0
gpgkey=https://mirrors.aliyun.com/centos/RPM-GPG-KEY-CentOS-7
重建 yum 缓存
sudo yum clean all
sudo yum makecache
安装C语言编译环境
sudo yum install centos-release-scl scl-utils-build
sudo yum install -y –nogpgcheck devtoolset-8-toolchain
# 注意: 执行此命令会自动切换到 root 用户
sudo scl enable devtoolset-8 bash
测试 gcc 版本
gcc –version
下载地址:https://download.redis.io/releases/ 
下载后上传到 hadoop1 的 /opt/software 目录下,并用 tar 命令解压 
进入解压后的 redis 目录使用 make 进行编译 
编译完成后继续执行 make install 进行安装 
安装目录:/usr/local/bin
9.2 后台启动配置
一份redis.conf到 hadoop 家目录下
cp /opt/module/redis-6.0.8/redis.conf ~/my_redis.conf
修改 vim ~/my_redis.conf 文件
daemonize yes
# bind 127.0.0.1 注释掉这个配置
protected-mode no
9.3 启动和关闭
redis-server ~/my_redis.conf # 启动
redis-cli # 客户端访问
redis-cli shutdown # 关闭

网硕互联帮助中心




评论前必须登录!
注册