十七、Redis 核心原理与架构详解

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重写触发    持久化            │
└──────────────────────────────────────────────┘

为什么单线程还这么快?

  1. 纯内存操作:所有操作都在内存中完成,CPU 是唯一的性能瓶颈,单线程避免了多线程的锁竞争和上下文切换
  2. 高效数据结构:精心设计的跳表、压缩列表、哈希表等,保证操作时间复杂度为 O(1) 或 O(logN)
  3. IO 多路复用:epoll 机制使单线程能同时监听数千个连接的 IO 事件
  4. RESP 协议简单:文本协议解析开销极低
  5. Pipeline 支持:客户端可以批量发送命令,减少网络 RTT

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 结构体桥接:

c 复制代码
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 逻辑类型与物理编码映射

逻辑类型 底层物理编码 切换条件(Redis 7.x 默认)
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-sizeset-max-listpack-entries 等。

3.3 核心底层数据结构详解

3.3.1 SDS(Simple Dynamic String)简单动态字符串

Redis 没有使用 C 语言原生字符串,而是自己实现了 SDS:

c 复制代码
struct sdshdr {
    int len;      // 已使用长度
    int free;     // 剩余可用长度
    char buf[];   // 字符数组
};

相比 C 字符串的优势

特性 C 字符串 SDS
获取长度 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 个节点。

c 复制代码
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 是专门存储纯整数集合的紧凑结构:

c 复制代码
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 是最基础也是最灵活的类型,可以存储字符串、整数或浮点数:

bash 复制代码
# 基本操作
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:

bash 复制代码
# 基本操作
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 实现:

bash 复制代码
# 基本操作
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 是无序、不重复的元素集合,支持交并差集运算:

bash 复制代码
# 基本操作
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 排序:

bash 复制代码
# 基本操作
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_ratiorss / 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-lfuallkeys-lru
  • 混合存储 → volatile-lruvolatile-lfu
  • 核心数据不丢 → noeviction(配合手动扩容)

六、Redis 事务与高级特性

6.1 事务机制

Redis 事务通过 MULTI/EXEC 实现:

bash 复制代码
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 实现乐观锁:
bash 复制代码
WATCH account:1:balance      # 监视 key
MULTI
# ... 检查余额并扣款
EXEC                         # 如果 WATCH 的 key 被其他客户端修改,EXEC 返回 nil

6.2 Pipeline(管道)

Pipeline 将多个命令打包一次性发送到服务端,减少网络 RTT:

bash 复制代码
# 不用 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 脚本,保证脚本内命令的原子性:

bash 复制代码
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 发布订阅

bash 复制代码
# 订阅频道
SUBSCRIBE news:sports news:tech

# 发布消息
PUBLISH news:sports "国足2:1胜出"

# 模式订阅(支持通配符)
PSUBSCRIBE news:*

Stream(Redis 5.0+) 是对 Pub/Sub 的升级,支持消息持久化、消费者组和 ACK:

bash 复制代码
# 创建消息
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   │
└──────────┘                      │  (从节点)  │
                                  └──────────┘

全量同步(首次连接或断连过久):

  1. Slave 发送 PSYNC ? -1
  2. Master 执行 BGSAVE 生成 RDB 快照
  3. Master 将 RDB 发送给 Slave
  4. Slave 加载 RDB 到内存
  5. Master 将期间的增量命令发送给 Slave 执行

增量同步(正常情况):

  1. Master 维护一个 replication backlog(复制积压缓冲区,默认 1MB)
  2. Slave 发送 PSYNC <runid> <offset>
  3. Master 根据 offset 从 backlog 中发送增量数据

7.2 哨兵模式(Sentinel)

哨兵是主从复制的升级方案,提供自动故障转移:

核心功能

  • 监控:持续检测 Master 和 Slave 是否正常运行
  • 通知:当被监控的实例出现问题时,通过 Pub/Sub 通知管理员
  • 自动故障转移:Master 不可用时,自动将某个 Slave 提升为新的 Master
  • 配置中心:客户端连接 Sentinel 获取当前 Master 地址,故障转移后自动更新

故障判定流程

  1. Sentinel 每秒向所有实例发送 PING
  2. 超过 down-after-milliseconds 未响应 → 标记为主观下线(SDOWN)
  3. 多个 Sentinel 都认为 Master 主观下线 → 标记为客观下线(ODOWN)
  4. Sentinel 选举一个 Leader 执行故障转移
  5. Leader 选择一个 Slave 提升为新 Master(优先级:replica-priority 最高、复制偏移量最大、runid 最小)
  6. 其他 Slave 改为复制新 Master
  7. 通知客户端新 Master 地址
bash 复制代码
# 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 落在同一槽:

bash 复制代码
# 以下两个 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 分布式锁实现

bash 复制代码
# 加锁(原子操作)
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 与其他组件的对比

维度 Redis Memcached MongoDB
数据结构 丰富(String/Hash/List/Set/ZSet/Stream) 简单 KV 文档型(BSON)
持久化 RDB + AOF 不支持 原生支持
线程模型 单线程执行(6.0+ 多线程IO) 多线程 多线程
集群 Cluster(原生分片) 客户端分片 原生分片
内存管理 精细(jemalloc + 多种淘汰策略) 简单 slab 分配 WiredTiger 引擎
事务 MULTI/EXEC(不支持回滚) 不支持 多文档事务(4.0+)
适用场景 缓存/队列/排行榜/分布式锁 简单缓存 文档存储/内容管理

8.4 关键配置调优

bash 复制代码
# 内存配置
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 仓库文件

bash 复制代码
sudo vi /etc/yum.repos.d/CentOS-SCLo-scl.repo

输入以下内容

bash 复制代码
[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 依赖此库)

bash 复制代码
sudo vi /etc/yum.repos.d/CentOS-SCLo-scl-rh.repo

输入以下内容

bash 复制代码
[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 缓存

bash 复制代码
sudo yum clean all
sudo yum makecache

安装C语言编译环境

bash 复制代码
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 版本

bash 复制代码
gcc --version

下载地址:https://download.redis.io/releases/

下载后上传到 hadoop1 的 /opt/software 目录下,并用 tar 命令解压

进入解压后的 redis 目录使用 make 进行编译

编译完成后继续执行 make install 进行安装

安装目录:/usr/local/bin

9.2 后台启动配置

一份redis.conf到 hadoop 家目录下

bash 复制代码
cp /opt/module/redis-6.0.8/redis.conf ~/my_redis.conf

修改 vim ~/my_redis.conf 文件

bash 复制代码
daemonize yes
# bind 127.0.0.1  注释掉这个配置
protected-mode no

9.3 启动和关闭

bash 复制代码
redis-server ~/my_redis.conf   # 启动
redis-cli					   # 客户端访问
redis-cli shutdown             # 关闭

十、关键要点总结

  1. 纯内存 + 单线程执行 是 Redis 高性能的基石,配合 IO 多路复用实现单线程万级并发
  2. 两层架构设计 是 Redis 最精妙的设计之一------对外暴露 5 种逻辑类型,底层自动选择最优物理编码(redisObject 桥接 type 和 encoding)
  3. 跳表 是 ZSet 的排序引擎,相比红黑树实现更简单、范围查询更高效、内存更友好
  4. 持久化选型 直接影响数据安全性和性能------生产推荐混合持久化(RDB + AOF 增量),兼顾恢复速度和数据完整性
  5. 过期清理 采用惰性删除 + 定期删除组合策略,是 CPU 和内存之间的折中设计
  6. 内存淘汰 提供 8 种策略,纯缓存场景推荐 allkeys-lfuallkeys-lru
  7. 高可用演进路径:主从复制(读写分离)→ 哨兵模式(自动故障转移)→ Cluster 集群(水平扩展 + 去中心化分片)
  8. 生产实践 需重点关注缓存穿透/击穿/雪崩的预防、分布式锁的正确实现、危险命令的禁用、以及慢查询的持续监控
相关推荐
copyer_xyf1 小时前
PostgreSQL 做向量检索:一条单库路线
数据库·agent·nestjs
Web3_Daisy1 小时前
如何在Robinhood Chain创建 Uniswap V3 流动性池
大数据·人工智能·web3·区块链
Access开发易登软件1 小时前
Access 怎么做前后端分离?用 Web API 读写 SQL Server
前端·数据库·人工智能·microsoft·excel·access
Juicedata1 小时前
腾讯云 x JuiceFS:基于 FoundationDB 的企业级统一存储实践
数据库·人工智能·科技·云计算·腾讯云
小罗水1 小时前
第13章 Redis 缓存、幂等锁与任务状态
数据库·redis·缓存
长三角活动观察1 小时前
政企线下活动全流程项目管控方案:苏州独石传媒长三角多场景落地实践总结
大数据·人工智能·传媒
小张同学a.1 小时前
LAMP架构2
linux·运维·网络·架构·负载均衡
SelectDB技术团队1 小时前
当 PostgreSQL 面临性能瓶颈:80TB 电商业务迁移至 Apache Doris 的实践思考
数据库·postgresql·apache
热心市民lcj2 小时前
Spring Boot 整合 Caffeine 本地缓存实战
spring boot·后端·缓存