云计算百科
云计算领域专业知识百科平台

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

为什么单线程还这么快?

  • 纯内存操作:所有操作都在内存中完成,CPU 是唯一的性能瓶颈,单线程避免了多线程的锁竞争和上下文切换
  • 高效数据结构:精心设计的跳表、压缩列表、哈希表等,保证操作时间复杂度为 O(1) 或 O(logN)
  • IO 多路复用:epoll 机制使单线程能同时监听数千个连接的 IO 事件
  • RESP 协议简单:文本协议解析开销极低
  • 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 结构体桥接:

    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-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 字符串的优势:

    特性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 个节点。

    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 │
    └──────────┘ │ (从节点) │
    └──────────┘

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

  • Slave 发送 PSYNC ? -1
  • Master 执行 BGSAVE 生成 RDB 快照
  • Master 将 RDB 发送给 Slave
  • Slave 加载 RDB 到内存
  • Master 将期间的增量命令发送给 Slave 执行
  • 增量同步(正常情况):

  • Master 维护一个 replication backlog(复制积压缓冲区,默认 1MB)
  • Slave 发送 PSYNC <runid> <offset>
  • Master 根据 offset 从 backlog 中发送增量数据
  • 7.2 哨兵模式(Sentinel)

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

    核心功能:

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

    故障判定流程:

  • Sentinel 每秒向所有实例发送 PING
  • 超过 down-after-milliseconds 未响应 → 标记为主观下线(SDOWN)
  • 多个 Sentinel 都认为 Master 主观下线 → 标记为客观下线(ODOWN)
  • Sentinel 选举一个 Leader 执行故障转移
  • Leader 选择一个 Slave 提升为新 Master(优先级:replica-priority 最高、复制偏移量最大、runid 最小)
  • 其他 Slave 改为复制新 Master
  • 通知客户端新 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 与其他组件的对比

    维度RedisMemcachedMongoDB
    数据结构 丰富(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 # 关闭

    在这里插入图片描述


    十、关键要点总结

  • 纯内存 + 单线程执行 是 Redis 高性能的基石,配合 IO 多路复用实现单线程万级并发
  • 两层架构设计 是 Redis 最精妙的设计之一——对外暴露 5 种逻辑类型,底层自动选择最优物理编码(redisObject 桥接 type 和 encoding)
  • 跳表 是 ZSet 的排序引擎,相比红黑树实现更简单、范围查询更高效、内存更友好
  • 持久化选型 直接影响数据安全性和性能——生产推荐混合持久化(RDB + AOF 增量),兼顾恢复速度和数据完整性
  • 过期清理 采用惰性删除 + 定期删除组合策略,是 CPU 和内存之间的折中设计
  • 内存淘汰 提供 8 种策略,纯缓存场景推荐 allkeys-lfu 或 allkeys-lru
  • 高可用演进路径:主从复制(读写分离)→ 哨兵模式(自动故障转移)→ Cluster 集群(水平扩展 + 去中心化分片)
  • 生产实践 需重点关注缓存穿透/击穿/雪崩的预防、分布式锁的正确实现、危险命令的禁用、以及慢查询的持续监控
  • 赞(0)
    未经允许不得转载:网硕互联帮助中心 » 十七、Redis 核心原理与架构详解
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!