当前位置: 首页 > news >正文

分布式缓存:三万字详解Redis

文章目录

  • 缓存全景图
  • Pre
  • Redis 整体认知框架
    • 一、Redis 简介
    • 二、核心特性
    • 三、性能模型
    • 四、持久化详解
    • 五、复制与高可用
    • 六、集群与分片方案
  • Redis 核心数据类型
    • 概述
    • 1. String
    • 2. List
    • 3. Set
    • 4. Sorted Set(有序集合)
    • 5. Hash
    • 6. Bitmap
    • 7. Geo
    • 8. HyperLogLog
  • Redis 协议分析
    • 1. RESP 设计原则
    • 2. 三种响应模型与特殊模式
    • 3. 两种请求格式
      • 3.1 Inline 命令格式
      • 3.2 Array(数组)格式
    • 4. 五种响应格式详解
    • 5. 协议分类概览
    • 6. Redis Client 选型与改进建议
  • Redis 的核心组件
    • 一、系统架构概览
    • 二、事件处理机制
    • 三、数据管理
    • 四、功能扩展(Module System)
    • 五、系统扩展(Replication & Cluster)
  • Redis的事件驱动模型
    • 一、事件驱动模型概述
    • 二、文件事件处理详解
      • 2.1 Reactor 模式四部分
      • 2.2 IO 多路复用的四种实现及选型逻辑
      • 2.3 aeProcessEvents:事件收集与派发流程
      • 2.4 三类文件事件处理函数
    • 三、时间事件机制剖析
  • Redis 协议解析及处理
    • 一、协议解析
    • 二、协议执行
  • Redis 内部数据结构
    • 一、RedisDb 结构
    • 二、redisObject 抽象
    • 三、dict 哈希表
    • 四、sds 简单动态字符串
    • 五、压缩列表(ziplist)
    • 六、快速列表(quicklist)
    • 七、跳跃表(zskiplist)
    • 八、数据类型与内部结构映射
  • Redis 淘汰策略
    • 一、淘汰原理
    • 二、淘汰方式
    • 三、淘汰策略与 Eviction Pool
    • 四、八种淘汰策略详解
  • Redis 的三种持久化方案及崩溃后数据恢复流程
    • 一、RDB 持久化
    • 二、AOF 持久化
    • 三、混合持久化
  • Redis 后台异步 IO(BIO)
    • 一、BIO 线程设计动机
    • 二、BIO 线程模型
    • 三、BIO 任务类型
    • 四、BIO 处理流程
  • Redis 多线程架构
    • 一、主线程职责
    • 二、IO 线程设计
    • 三、命令处理完整流程
    • 四、多线程方案优劣
  • 复制架构原理
    • 一、复制架构原理
    • 二、同步方式对比
    • 三、psync 与 psync2 优化
    • 四、复制连接与授权流程
    • 五、复制过程详析
      • 5.1 增量同步流程
      • 5.2 全量同步流程
    • 六、注意事项
  • Redis 集群的分布式方案
    • 1. Client 端分区
      • 1.1 原理与哈希算法
      • 1.2 DNS 动态管理
      • 1.3 优缺点
    • 2. Proxy 分区方案
      • 2.1 架构概览
      • 2.2 典型实现
      • 2.3 优缺点
    • 3. 原生 Redis Cluster
      • 3.1 Slot 与 Gossip 架构
      • 3.2 读写与重定向
      • 3.3 在线扩缩容与数据迁移
      • 3.4 优缺点
    • 4. 对比与选型建议

在这里插入图片描述


缓存全景图

在这里插入图片描述


Pre

分布式缓存:缓存设计三大核心思想

分布式缓存:缓存的三种读写模式及分类

分布式缓存:缓存架构设计的“四步走”方法

分布式缓存:缓存设计中的 7 大经典问题_缓存失效、缓存穿透、缓存雪崩

分布式缓存:缓存设计中的 7 大经典问题_数据不一致与数据并发竞争

分布式缓存:缓存设计中的 7 大经典问题_Hot Key和Big Key

Redis 整体认知框架

在这里插入图片描述

一、Redis 简介

  • 实现与授权:Redis 基于 ANSI C 语言编写,采用 BSD 许可,代码轻量、易于嵌入。
  • 内存存储:所有数据均保存在内存中,因此具有极低的读写延迟,可做缓存、数据库、消息中间件等多种角色。
  • 多库支持:Redis 即 Remote Dictionary Server,实例内部维护多个逻辑数据库(默认为 16 个),通过 SELECT 命令切换操作目标。

二、核心特性

  1. 丰富的数据类型:除基本的字符串(String)外,Redis 还原生支持 List、Set、Sorted Set(ZSet)、Hash;以及 Bitmap、HyperLogLog、Geo 等特殊结构,一机多用。

  2. 双重持久化

    • RDB 快照:定时或达到修改阈值时,将内存全量快照写入 .rdb 文件,适合冷备份;
    • AOF 追加:将每条写命令追加到 .aof 文件,可配置同步频率,保障最小数据丢失。
      线上系统常用“RDB+ AOF 混合”策略:平时频繁追加 AOF,低峰期触发 BGSAVE 生成新快照;遇到 AOF 文件过大时,用 BGREWRITEAOF 重写精简。
  3. 读写分离:一主多从架构,将写请求指向 Master,读请求分发至多个 Slave,显著提高读吞吐。

  4. Lua 脚本与事务

    • Lua 脚本:从 Redis 2.6 起支持,脚本内多命令打包,可实现原子性操作并减少网络往返;
    • 事务:通过 MULTI/EXEC 打包命令,确保命令序列原子执行,中途出错则全部丢弃。
  5. 集群支持:Redis Cluster 原生实现分布式,基于 Slot 哈希机制,无中心节点,实现自动扩缩容与故障转移。

三、性能模型

  • 单线程+事件驱动:网络 IO 与命令处理均在主线程中完成,基于 epoll(或 kqueue、evport)无阻塞多路复用,避免锁竞争与上下文切换。

  • 高 QPS:单实例可轻松突破 100k QPS,得益于纯内存操作与无锁设计。

  • 后台子进程/线程

    • BGSAVE/BGREWRITEAOF/全量复制:主进程遇到重负荷持久化或复制任务时,fork 子进程执 行,主进程继续提供服务;

    • BIO 线程池:三个后台线程负责文件关闭、AOF 缓冲刷盘、对象释放,进一步减轻主线程压力。

在这里插入图片描述

四、持久化详解

在这里插入图片描述

  • RDB:快速生成紧凑快照,恢复速度快;适合冷备份,但数据持久性依赖触发频率。
  • AOF:按命令追加,能做到每秒或每次写入同步,重放日志恢复更完整;但文件体积随命令量增长,需定期重写。
  • 混合策略:推荐生产环境开启 AOF 并定期重写,同时在低峰期执行 RDB 快照,以兼顾恢复速度与数据完整性。

五、复制与高可用

  • 全量同步:Slave 首次连接或复制缓冲不足时,Master fork 子进程生成 RDB 快照并传输,Slave 接收后加载;
  • 增量复制:Slave 重连且累积命令量在缓冲区可承载范围内时,仅传输缺失命令,降低复制开销。
  • 故障切换:当 Master 宕机,可手动或通过哨兵(Sentinel)将任意 Slave 提升为 Master,保障业务连续性。

六、集群与分片方案

  1. Client 分片:客户端根据一致性哈希或取模自行路由到不同实例,简单但扩缩容麻烦;
  2. Proxy 层:如 Twemproxy,在前端做路由与健康检查,后端实际节点变动只需更新 Proxy 配置;
  3. Redis Cluster:官方原生集群,使用 16384 个 Slot 管理键空间,支持在线迁移、故障转移与自动均衡。

Redis 核心数据类型

概述

Redis 共支持以下 及 种核心数据类型:

  1. String:二进制安全的字符串类型
  2. List:按插入顺序排列的双向链表
  3. Set:无序且元素唯一的集合
  4. Sorted Set(ZSet):带分值的有序集合
  5. Hash:字段–值映射表
  6. Bitmap:基于 String 的位图封装
  7. Geo:地理位置类型,基于 ZSet 实现
  8. HyperLogLog:基数统计的近似算法

1. String

  • 存储方式

    • 小于 1 MB 时,采用 raw encoding,预分配两倍长度来减少频繁扩容;
    • 超过 1 MB 时,每次额外预分配 1 MB。
  • 整型编码:对于纯数字字符串,使用整型编码,以节省内存并加速算术运算。

  • 常用指令SETGETMSETINCRDECR 等。

  • 典型场景

    • 缓存普通文本、序列化对象;

    • 计数器(PV、UV、限流);

    • 分布式锁的简单实现。

      SET user:1001:token "abcd1234"
      INCR page:views
      

2. List

  • 底层实现:快速双向链表,支持头尾 O(1) 插入/弹出。

  • 指令摘要

    • 插入:LPUSHRPUSHLINSERT
    • 弹出:LPOPRPOP、阻塞式 BLPOPBRPOP
    • 范围查询:LRANGE(支持负索引)
  • 时间复杂度:对头/尾操作为 O(1),随机访问或插入为 O(N)。

  • 典型场景

    • 消息队列(工作队列、发布/订阅前端缓冲);

    • Feed Timeline(用户动态按时间顺序追加);

    • 简易栈/队列。

      RPUSH queue:tasks task1 task2
      BLPOP queue:tasks 0   # 阻塞直到有新任务
      LRANGE queue:tasks 0 9  # 获取前 10 个元素
      

3. Set

  • 底层实现:哈希表,保证元素唯一且无序。

  • 指令摘要SADDSREMSISMEMBERSDIFFSINTERSUNIONSPOPSRANDMEMBER

  • 时间复杂度:插入、删除、查找均为 O(1)。

  • 典型场景

    • 好友关注列表、互关判断;

    • 推荐系统中的离线/在线标签去重;

    • 来源 IP 白名单/黑名单。

      SADD user:1001:friends 1002 1003
      SISMEMBER user:1001:friends 1003  # 返回 1
      

4. Sorted Set(有序集合)

  • 底层实现:跳表 + 哈希,按分值升序排列。

  • 指令摘要ZADDZREMZSCOREZRANGEZINCRBYZINTERSTOREZUNIONSTORE

  • 特点:元素唯一,分值可重复;快速算分与排名。

  • 典型场景

    • 实时排行榜(游戏分数、热度榜单);

    • 按权重排序的数据展示;

    • 定时任务系统(利用分值表示时间戳)。

      ZADD leaderboard 100 user:1001
      ZRANGE leaderboard 0 9 WITHSCORES  # TOP10
      

5. Hash

  • 底层实现:field–value 映射,内部也是哈希表。

  • 指令摘要HSET/HMSETHGET/HMGETHEXISTSHINCRBYHGETALL

  • 时间复杂度:单 field 操作为 O(1)。

  • 典型场景

    • 存储对象属性,如用户资料、商品信息;

    • 实现类似关系型数据库表的一行;

    • 业务统计字段聚合。

      HMSET user:1001 name "Alice" age 30
      HINCRBY user:1001:stats login_count 1
      

6. Bitmap

  • 底层实现:基于 String 的位操作。

  • 指令摘要SETBITGETBITBITCOUNTBITOPBITFIELDBITPOS

  • 特点:按位存储,内存占用极低;位运算高效。

  • 典型场景

    • 用户活跃打卡(N 天登录);

    • 标签属性存储与多维统计;

    • 简易布隆过滤器原型。

      SETBIT login:20250525 1001 1
      BITCOUNT login:20250525  # 当天活跃用户数
      

7. Geo

  • 底层实现:封装于 Sorted Set,通过 GeoHash 将经纬度映射为分值。

  • 指令摘要GEOADDGEOPOSGEODISTGEORADIUSGEORADIUSBYMEMBER

  • 特点:支持范围查询与距离计算。

  • 典型场景

    • 附近的人/店铺/车辆搜索;

    • 地理围栏告警;

    • 实时位置服务(LBS)。

      GEOADD restaurants 116.397128 39.916527 "PekingDuck"
      GEORADIUS restaurants 116.40 39.92 5 km WITHDIST
      

8. HyperLogLog

  • 底层实现:近似基数统计算法,稀疏与稠密两种存储,自适应切换。

  • 指令摘要PFADDPFCOUNTPFMERGE

  • 特点:固定 ≈12KB 内存;误差率 ≈0.81%。

  • 典型场景

    • 大规模 UV 统计;

    • 海量搜索词汇去重;

    • 日志中的独立源 IP 计数。

      PFADD uv:202505 user:1001 user:1002
      PFCOUNT uv:202505  # 当月独立访客数(近似)
      

Redis 协议分析

在这里插入图片描述


1. RESP 设计原则

Redis 序列化协议 RESP 的设计坚持三条原则:

  1. 实现简单:协议格式直观,便于不同语言的客户端快速实现。
  2. 可快速解析:结构清晰、前缀标记,使得解析器能够以最低开销完成读写。
  3. 便于阅读:即便用 Telnet 交互,也能通过简单的符号轻松定位请求与响应边界。

2. 三种响应模型与特殊模式

Redis 默认使用“Ping-Pong”模型:客户端发起一个请求,服务端立即返回一个响应,实现一问一答。
此外还有两种特殊模式:

  • Pipeline 模式:客户端一次性发送多条命令,不等待中间响应,待全部发送完后再按序接收服务端响应,减少网络往返。
  • Pub/Sub 模式:客户端通过 SUBSCRIBE 进入订阅状态,此后无需再次发起请求,即可持续接收服务端基于频道推送的消息;除订阅相关命令,其他命令均失效。

3. 两种请求格式

3.1 Inline 命令格式

适用于交互式会话(如 Telnet),命令与参数以空格分隔,结尾以 \r\n

mget key1 key2\r\n

3.2 Array(数组)格式

更规范的二进制安全格式,也是生产环境客户端默认使用:

*3\r\n$4\r\nMGET\r\n$4\r\nkey1\r\n$4\r\nkey2\r\n

其中 *3 表示数组长度为 3,每个元素前以 $<字节数> 声明。


4. 五种响应格式详解

Redis 响应客户端请求时,基于 RESP 定义了 5 类格式:

  1. Simple String(简单字符串)

    • 前缀 +,不可包含 \r\n,以 \r\n 结束。
    • 用于返回 OK、PONG 等简短状态。
    +OK\r\n
    
  2. Error(错误)

    • 前缀 -,后跟错误类型(ERR/WRONGTYPE 等)及描述,以 \r\n 结束。
    -ERR unknown command 'foo'\r\n
    
  3. Integer(整数)

    • 前缀 :,后跟整数字符串,以 \r\n 结束。
    • 代表计数、长度或布尔(0/1)等。
    :1000\r\n
    
  4. Bulk String(字符串块)

    • 前缀 $,后跟内容字节长度,再 \r\n;随后是真实内容,再 \r\n
    • 支持二进制安全,最大可达 512MB。
    $6\r\nfoobar\r\n
    
    • 空字符串:$0\r\n\r\n;NULL:$-1\r\n
  5. Array(数组)

    • 前缀 *,后跟元素个数,再 \r\n;随后依次是各元素(可嵌套上述任何格式)。
    *2\r\n$3\r\nGET\r\n$3\r\nkey\r\n
    
    • 空数组:*0\r\n;NULL 数组:*-1\r\n

5. 协议分类概览

除了与 8 种数据结构直接对应的命令协议,Redis 还定义了以下 8 类协议:

  1. Pub/Sub 协议SUBSCRIBE/PUBLISH
  2. 事务协议MULTI/EXEC/DISCARD
  3. 脚本协议EVAL/EVALSHA/SCRIPT
  4. 连接协议AUTH/SELECT/QUIT
  5. 复制协议REPLICAOF/PSYNC/ROLE
  6. 配置协议CONFIG GET/CONFIG SET
  7. 调试统计协议INFO/MONITOR/SLOWLOG
  8. 内部命令MIGRATE/DUMP/RESTORE

6. Redis Client 选型与改进建议

以 Java 为例,目前主流客户端有:

  • Jedis:轻量、直观,支持连接池,几乎覆盖所有命令,但原生不支持读写分离。
  • Redisson:基于 Netty 的非阻塞 IO,支持异步调用、读写分离、负载均衡及 Spring Session 集成,但实现较为复杂。
  • Lettuce:也是基于 Netty,完全非阻塞、线程安全,可在多线程环境中共享同一连接;提供同步、异步(Future)、响应式(Reactive Streams)和 RxJava 风格的多种调用方式;原生支持 Redis Cluster、Sentinel、读写分离,自动故障转移;客户端实现简洁,依赖少,适合高并发、低延迟场景。

改进建议

  • 在异常访问时实现重试与熔断;
  • 动态感知主从切换,自动调整连接;
  • 多 Slave 场景下添加负载均衡策略;
  • 与配置中心和集群管理平台集成,实现实时路由和高可用。

Redis 的核心组件

一、系统架构概览

Redis 的核心组件主要包括以下四大模块:

  • 事件处理(Event Loop):基于作者开发的 ae 事件驱动模型,实现高效网络 IO 和定时任务调度
  • 数据存储与管理:内存数据库 redisDB,支持多库、多数据类型、多底层结构
  • 功能扩展(Module System):可插拔模块化设计,无需改动核心即可引入新数据类型与命令
  • 系统扩展(Replication & Cluster):主从复制与 Cluster 分片,满足高可用与横向扩容需求

二、事件处理机制

在这里插入图片描述

  1. ae 事件驱动模型概述

    • 封装 select/epoll/kqueue/evport,实现 IO 多路复用
    • 监听多个 socket,把网络读写、命令执行、定时任务整合到同一个循环
  2. 客户端连接管理

    • 收到新连接时,创建 client 结构体,维护状态、读写缓冲
    • 请求到达后将命令读取到缓冲区,并解析成参数列表
  3. 命令处理流程

    • 根据命令名称映射到 redisCommand
    • 对参数进行进一步解析与校验
    • 执行命令对应的处理函数
  4. 时间事件(Time Events)

    • 周期性执行 serverCron:包括统计更新、过期键清理、AOF/RDB 持久化触发等

三、数据管理

  1. 内存数据库结构

    • 每个逻辑库对应一个 redisDB 结构,内部通过 dict 存储 key/value
    • 八种数据类型(String、List、Set、Hash、ZSet、Stream、Bitmap、HyperLogLog)各自采用一或多种底层结构
  2. 持久化策略

    • AOF(Append Only File):将每次写操作追加到缓冲,按策略刷盘
    • RDB(Redis DataBase Snapshot):定期将全量数据快照落地,生成紧凑的二进制文件
  3. 线程模型与非阻塞

    • 核心线程为单线程,避免任何内核阻塞
    • BIO 线程池:专门处理可能阻塞的文件 close、fsync 等操作,保证主线程性能
  4. 内存淘汰与过期

    • 过期键及时清理,空闲扫描或惰性删除相结合
    • 八种淘汰策略(如 LRU、LFU、TTL 优先等),结合 eviction pool 高效回收内存

四、功能扩展(Module System)

  • 模块加载:动态链接库,可在启动时或运行时加载/卸载

  • API 接口

    • RedisModule_Init:初始化模块
    • RedisModule_CreateCommand:注册新命令
  • 应用场景:自定义数据结构、高级功能(例如图数据库、机器学习推理)


五、系统扩展(Replication & Cluster)

  1. 主从复制(Replication)

    • 支持全量同步与增量复制
    • Slave 重连、主从切换后均可继续增量复制,提升可用性
    • 读写分离:将读请求分摊到多个节点,减轻主节点负载
  2. 分片集群(Cluster)

    • 16384 个 slot,按 Hash 分布到不同节点
    • 客户端计算 slot,根据 slot 定位节点
    • 错误节点自动重定向(MOVED/ASK)
    • 在线扩容:通过迁移 slot 实现节点增减

Redis的事件驱动模型

一、事件驱动模型概述

Redis 作为一个高性能的内存数据库,充分利用事件驱动模式来处理几乎所有核心操作。与 Memcached 依赖 libevent/ libev 不同,Redis 作者从零开始,开发了自研的事件循环组件,封装在 aeEventLoop 及相关结构体中。这样做的动机是:

  • 最小化外部依赖:减少因第三方库升级或兼容性带来的不确定性;
  • 轻量可控:自研实现更契合 Redis 的业务场景,代码更简洁,性能更容易优化;
  • 灵活扩展:可在事件模型中无缝接入文件事件与时间事件的统一调度。

在这里插入图片描述

Redis 的事件驱动模型主要处理两类事件:

  1. 文件事件:与 socket 读写、连接建立/关闭直接相关的 IO 事件;
  2. 时间事件:周期性或单次需要在指定时间点执行的任务,例如定期统计、Key 淘汰、缓冲写出等。

二、文件事件处理详解

Redis 在文件事件处理上采用经典的Reactor 模式,将整个流程拆分为四部分:连接 socket、IO 多路复用、文件事件分派器与事件处理器。

在这里插入图片描述

2.1 Reactor 模式四部分

  1. 连接 Socket:监听客户端连接的 TCP 端口与已建立连接的客户端 Socket;
  2. IO 多路复用:通过底层操作系统接口同时监控多个描述符的可读写状态;
  3. 文件事件分派器:调用 aeProcessEvents,从多路复用层获取触发的事件;
  4. 事件处理器:根据事件类型(可读/可写)调用注册好的回调函数执行实际逻辑。

2.2 IO 多路复用的四种实现及选型逻辑

Redis 封装了四种主流的多路复用方案,编译时按优先级自动选择:

  • evport(Solaris 专有)
  • epoll(Linux 最佳选择)
  • kqueue(大多数 BSD 系统)
  • select(通用但性能最低)

前三者直接调用内核机制,能同时服务数十万文件描述符;select 则每次需扫描全部描述符,时间复杂度 O(n),且受描述符数量上限(默认 1024/2048)限制,不适合线上高并发场景。对应实现分布在 ae_evport.cae_epoll.cae_kqueue.cae_select.c 四个代码文件中。

2.3 aeProcessEvents:事件收集与派发流程

aeProcessEvents 是 Redis 文件事件的核心分派器,执行流程大致如下:

  1. 计算下一次阻塞等待的超时时间(兼顾时间事件);
  2. 调用 aeApiPoll(内置封装)阻塞或非阻塞等待文件事件;
  3. 收集触发的事件,将它们封装到 aeFiredEvents 数组中,每项记录文件描述符与事件类型;
  4. 将底层事件类型(如 EPOLLIN/EPOLLOUT/EPOLLERR)映射为 Redis 事件标志(AE_READABLE/AE_WRITABLE);
  5. 依次遍历 aeFiredEvents先读后写地 dispatch 到注册在 aeEventLoop 中的具体事件处理器。

2.4 三类文件事件处理函数

Redis 对文件事件的注册与处理主要分为:

  1. 连接处理acceptTcpHandler

    • initServer 阶段注册监听 socket 的读事件;
    • 有新连接时,接受连接、创建 client 结构,获取远端 IP/端口;
    • 单次循环最多处理 1000 个新连接请求;
  2. 请求读取readQueryFromClient

    • 为每个 client socket 注册读事件;
    • 读取客户端发来的命令数据,填充到 client->query_buf
    • 按 inline 或 multibulk 格式解析命令,校验参数及当前实例状态后,执行对应的 redisCommand
    • 将执行结果写入 client->reply_buf
  3. 回复发送sendReplyToClient

    • 在命令执行完将结果放入写缓冲后,注册写事件;
    • 当 socket 可写时,将缓冲区数据发送给客户端。
      在这里插入图片描述

三、时间事件机制剖析

与文件事件并行,Redis 的时间事件在同一个 aeEventLoop 内作为链表管理。每个时间事件包含五个核心属性:

  • 事件 ID:全局唯一自增;
  • 执行时间when_secwhen_ms,精确到毫秒;
  • 处理器timeProc 函数指针;
  • 关联数据clientData 传递给处理器使用;
  • 双向链表指针prevnext,便于插入与遍历。

时间事件分为:

  • 单次事件:执行一次后即标记删除;
  • 周期事件:执行后更新下一次执行时间,保持循环。

aeProcessEvents 中,文件事件处理前后都会遍历一次时间事件链表,执行所有到期的事件:

  1. 逐一比较事件时间与当前时钟;
  2. 对可执行事件调用 timeProc(clientData)
  3. 若周期事件,更新 when_sec/when_ms;若单次事件,标记 id=-1,下一轮清除。

Redis 默认主要的时间事件包括:

  • serverCron:定期执行统计、淘汰、维护缓冲等任务;
  • moduleTimerHandler:模块化扩展的定时回调。

Redis 协议解析及处理

当事件循环检测到客户端有请求到来时,Redis 如何从网络读入原始数据、解析成命令与参数,最终执行并返回结果。


一、协议解析

在这里插入图片描述

  1. 读取请求到 Query Buffer

    • 当 socket 可读事件触发,Redis 会调用 readQueryFromClient,从客户端连接的文件描述符读取数据到 client->querybuf
    • 默认读缓冲大小为 16 KB;若单次请求长度超过 1 GB,Redis 会报错并关闭连接,防止恶意或异常请求耗尽内存。
  2. 判断协议类型:MULTIBULK vs INLINE

    • MULTIBULK(以 * 开头)

      • 首字节为 *,表示后续是一个块数组。格式为:

        *<参数个数>\r\n
        $<第1个参数字节数>\r\n
        <参数1内容>\r\n
        …  
        
      • 逐行读取,根据 $ 指示读取固定长度数据,直到完整填充所有参数。

    • INLINE(单行字符串)

      • 首字节非 *,整个请求以 \r\n 结尾。命令和参数用空格分隔:

        set mykey hello\r\n
        
      • Redis 会将整行切分,再按空格拆分命令及参数。

  3. 填充 client->argcclient->argv

    • 解析结束后,将参数个数写入 client->argc
    • 对于每个参数,创建一个 robj(Redis 对象),存入 client->argv 数组,以便后续命令执行使用。

二、协议执行

在这里插入图片描述

Redis 协议解析及处理

  1. 处理特殊命令:QUIT

    • argv[0]quit,Redis 直接返回 +OK\r\n 并将 CLIENT_CLOSE_AFTER_REPLY 标记置位,表示回复后关闭连接。
  2. 查找并执行命令

    • 使用 lookupCommand 在全局命令表(server.commands)中查找 argv[0] 对应的 redisCommand 结构。

    • 若找不到,则调用 addReplyErrorFormat(c,"ERR unknown command '%s'",c->argv[0]->ptr),向客户端返回未知命令错误。

    • 找到后,进入命令执行阶段:

      c->cmd = cmd;
      c->cmd->proc(c);
      
      • proc 是命令对应的函数指针,如 setCommandgetCommand 等。
  3. 写入响应与副作用

    • 命令执行完成后,依照命令逻辑通过 addReply* 系列函数将响应数据写入 client->buf(写缓冲区)。

    • 若该命令为写操作,且开启了 AOF 或者当前角色为主节点,还需将写命令推送给 AOF 线程与所有从节点:

      • 调用 feedAppendOnlyFile(c, ...);
      • 调用 replicationFeedSlaves(c, ...);
    • 同时,更新命令统计,如 server.stat_numcommands++


Redis 内部数据结构

Redis 的内存数据结构层——Redis 如何在内存中组织和管理各种对象,才能在单线程模型下实现高性能与高扩展性。

在这里插入图片描述


一、RedisDb 结构

在这里插入图片描述

  • 多库支持:每个实例默认可配置 16 个逻辑库(db0db15),通过命令 SELECT $dbID 切换。

  • 核心字典

    • dict(主字典):存储 key → value 映射
    • expires:存储 key → 过期时间
  • 非核心字典

在这里插入图片描述

  • blocking_keys:记录 BLPOP/BRPOP 等阻塞列表的 key → client 列表
  • ready_keys:当元素入队触发唤醒时,将 key 加入此字典与全局 server.read_keys 列表中
  • watched_keys:用于事务 WATCH 监控的 key → client 列表

二、redisObject 抽象

在这里插入图片描述

Redis 中任何存储的值都封装为 redisObject,包含五个核心字段:

  1. type:对象类型(OBJ_STRINGOBJ_LISTOBJ_SETOBJ_ZSETOBJ_HASHOBJ_MODULEOBJ_STREAM
  2. encoding:底层编码(如 RAWINTHTZIPLIST 等)
  3. LRU:用于 LRU/LFU 淘汰策略的访问记录
  4. refcount:引用计数,支持对象共享与内存自动回收
  5. ptr:指向具体底层数据结构(如 sdsdictziplistquicklistzskiplist 等)

三、dict 哈希表

在这里插入图片描述

  • 双表设计dict 结构内维护长度为 2 的哈希表数组 ht[0]ht[1]

  • 渐进式 rehash

    1. ht[0] 装载因子超阈值,分配 ht[1](容量为 ht[0] 的两倍)
    2. 每次哈希操作顺带迁移部分桶,使用 rehashidx 记录迁移进度
  • 冲突解决:每个桶为 dictEntry 的单向链表
    在这里插入图片描述

  • 灵活可扩展dict 可用于主字典、过期字典,也可作为 Set、Hash 类型的内部存储


四、sds 简单动态字符串

在这里插入图片描述

  • 基本结构:底层为 sdshdr + char buf[]

    • len:当前字符串长度
    • alloc:已分配空间大小
    • flags:类型与子类型标志
    • buf:字符数据(二进制安全,允许包含 \0
      在这里插入图片描述
  • 多种子类型(从 Redis 3.2 起)

    • sdshdr5:极短字符串,仅 flagsbuf
    • sdshdr8/16/32/64:根据长度选择合适的整型字段,节省内存
  • 优势

    • O(1) 获取长度,无需遍历
    • 动态扩展与收缩,二进制安全

五、压缩列表(ziplist)

为了节约内存,并减少内存碎片,Redis 设计了 ziplist 压缩列表内部数据结构。压缩列表是一块连续的内存空间,可以连续存储多个元素,没有冗余空间,是一种连续内存数据块组成的顺序型内存结构。

在这里插入图片描述

  • 连续内存布局,减少指针开销与碎片

  • 结构字段

    1. zlbytes:总字节数
    2. zltail:尾节点距起始偏移
    3. zllen:节点数量
    4. entry…entry…:各节点数据
    5. zlend:结束标志(255)
  • 节点格式

    • 前驱长度、编码长度、实际数据长度、编码类型、数据
  • 适用场景

    • 小型 Hash(默认 ≤512 项、值 ≤64B)
    • 小型 ZSet(默认 ≤128 项、值 ≤64B)

在这里插入图片描述
在这里插入图片描述


六、快速列表(quicklist)

Redis 在 3.2 版本之后引入 quicklist,用以替换 linkedlist。因为 linkedlist 每个节点有前后指针,要占用 16 字节,而且每个节点独立分配内存,很容易加剧内存的碎片化。而 ziplist 由于紧凑型存储,增加元素需要 realloc,删除元素需要内存拷贝,天然不适合元素太多、value 太大的存储。

在这里插入图片描述

  • 设计目标:结合 ziplist 的紧凑与 linkedlist 的灵活

  • 结构

    • 双向链表节点 quicklistNode,每节点包含一个 ziplist
    • headtail 指针;count(总元素数);len(节点数);compress(LZF 压缩深度)
  • 优点

    • 头尾操作 O(1)
    • 避免过多内存碎片
    • 支持中间位置操作(O(n))

七、跳跃表(zskiplist)

跳跃表 zskiplist 是一种有序数据结构,它通过在每个节点维持多个指向其他节点的指针,从而可以加速访问。跳跃表支持平均 O(logN) 和最差 O(n) 复杂度的节点查找。在大部分场景,跳跃表的效率和平衡树接近,但跳跃表的实现比平衡树要简单,所以不少程序都用跳跃表来替换平衡树。
在这里插入图片描述

  • 多级索引:在每个节点维护多层前进指针与跨度,近似平衡树性能

  • 结构

    • zskiplistheadertaillengthlevel
    • zskiplistNodeele(sds)、scorebackward、多级 level[i]forward + span
  • 性能

    • 平均 O(log N) 查找/插入/删除
    • 同分数元素按字典序排序
  • 适用场景

    • 大型 Sorted Set、Geo 类型(超出 ziplist 阈值)

八、数据类型与内部结构映射

数据类型内部存储结构
Stringsds / 整数对象
Listquicklist
Setdict
Hashziplist(小型)/ dict(大型)
Sorted Setziplist(小型)/ zskiplist(大型)
Streamradix tree + listpacks
HyperLogLogsds
Bitmapsds
Geoziplist(小型)/ zskiplist(大型)

Redis 淘汰策略

当 Redis 内存到达或超过 maxmemory 限制时,系统如何精确、高效地清理无用或不活跃的数据,保障缓存的命中率与访问性能。

在这里插入图片描述


一、淘汰原理

  1. 内存阈值与触发条件

    • 通过配置 maxmemory 设置 Redis 可用的最大内存。
    • 当内存使用量超过阈值,或在定期过期检查时发现过期 key,均触发淘汰动作。
  2. 场景一:定期过期扫描(serverCron)

    • 周期性执行 serverCron,对每个 redisDbexpires 过期字典进行采样:

      1. 随机取 20 个带过期时间的 key 样本;
      2. 若其中超过 5 个已过期(比例 >25%),继续取样并清理,直至过期比例 ≤25% 或 时间耗尽;
    • 若某 DB 的过期字典填充率 <1%,则跳过采样。

    • 为避免阻塞主线程,清理时限:

      • Redis 5.0 及之前:慢循环策略,默认 25ms;
      • Redis 6.0:快循环策略,限时 1ms。
  3. 场景二:命令执行时检查

    • 在每次执行命令前,检查当前内存占用是否已超限;
    • 若超限,则立即依据所选 maxmemory-policy 进行 key 淘汰,释放内存后再继续执行写命令。

二、淘汰方式

Redis 提供两种删除方式,以平衡主线程响应和内存回收的及时性:

  1. 同步删除

    • 直接在主线程中删除 key 及其 value,并同步回收内存;
    • 适用于简单值或复合类型元素数 ≤64 的情况。
  2. 异步删除(Lazy Free)

    • 依赖 BIO 线程池异步回收内存,避免主线程因大对象删除而阻塞;

    • 触发条件:

      • lazyfree-lazy-expire:延迟过期清理;
      • lazyfree-lazy-eviction:延迟淘汰时;
    • 对象类型:list、set、hash、zset 中,元素数 >64 时使用。


三、淘汰策略与 Eviction Pool

为在维持高性能的同时,尽可能剔除最“冷”的数据,Redis 在淘汰前会:

  1. 随机采样 N 个 key(默认为 5)

  2. 计算每个样本的“Idle”值

    • 对于 LRU,用空闲时间;LFU 则用 255 – 频率;TTL 策略以 UINT_MAX – 过期时间
  3. 维护大小为 N 的 Eviction Pool

    • 按 Idle 从小到大插入,始终保留 Idle 最大的样本;
  4. 最终剔除 Pool 中 Idle 最大的 key


四、八种淘汰策略详解

策略名称作用范围算法原理适用场景
noeviction不淘汰任何 key达到内存上限后,对写命令返回错误,读命令正常小规模数据,Redis 作为持久存储而非缓存
volatile-lru带过期时间的 key基于 LRU,从 expires 中随机 N 样本,剔除空闲时间最长的 key热点数据明显,且淘汰对象均已设置过期时间的缓存场景
volatile-lfu带过期时间的 key基于 LFU,从 expires 随机 N 样本,剔除使用频率最低的 key(Idle = 255–freq)访问频率具有明显冷热区分的业务,且仅淘汰已设置过期时间的对象
volatile-ttl带过期时间的 key剔除最近到期的 key(Idle = UINT_MAX–TTL)按剩余生命周期冷热分区,优先清理即将过期的数据
volatile-random带过期时间的 keyexpires 随机选一个 key 直接剔除无明显访问热点,且仅对带过期时间的对象进行随机清理
allkeys-lru所有 keyvolatile-lru 类似,但样本来自主字典 dict全局范围的 LRU 淘汰,适合全量缓存且有热点区分的场景
allkeys-lfu所有 keyvolatile-lfu 类似,样本来自主字典 dict访问频率冷热明显,需要对所有 key 进行频率淘汰的场景
allkeys-random所有 key从主字典 dict 随机选一个 key 直接剔除随机访问场景,无明显热点,全局随机淘汰

Redis 的三种持久化方案及崩溃后数据恢复流程

Redis 持久化是一个将内存数据转储到磁盘的过程。Redis 目前支持 RDB、AOF,以及混合存储三种模式。

在这里插入图片描述

一、RDB 持久化

  1. 原理概述

    • 以快照方式将内存全量数据序列化为二进制格式,包含:过期时间、数据类型、key 与 value;
    • 重启时(appendonly 关闭),直接加载 RDB 文件恢复数据。
  2. 触发场景

    • 手动执行 SAVE(阻塞主进程)或 BGSAVE(子进程异步);
    • 配置 save <秒> <次数>:在指定时间内写操作次数达到阈值自动触发;
    • 主从复制全量同步时,主库为了生成同步快照会执行 BGSAVE
    • 执行 FLUSHALL 或优雅 SHUTDOWN 时,自动触发快照。

在这里插入图片描述

  1. RDB 文件结构

    1. 头部:版本信息、Redis 版本、生成时间、内存占用等;
    2. 数据区:按 DBID 分块,依次写入每个 redisDb 的主字典与过期字典条目,记录过期时间及 LRU/LFU 元数据;
    3. 尾部:Lua 脚本等附加信息、EOF 标记(255)、校验和(cksum)。
  2. 优缺点

    • 优点:文件紧凑、加载快;
    • 缺点:全量快照只能反映触发时刻数据,之后变更丢失;子进程构建仍消耗 CPU,不能频繁在高峰期执行;格式二进制,可读性差,跨版本兼容性需谨慎。

二、AOF 持久化

在这里插入图片描述

  1. 原理概述

    • 将每条写命令以 Redis 协议的 MULTIBULK 格式追加到 AOF 文件;
    • 重启时,按序加载并重放写命令,恢复到最近状态。
  2. 落地流程

在这里插入图片描述

  1. 写命令执行后写入 AOF 缓冲;
  2. serverCron 周期将缓冲写入文件系统缓冲;
  3. appendfsync 策略 fsync 同步到磁盘。

在这里插入图片描述

  1. 同步策略

    • no:不主动 fsync,依赖操作系统(约 30s 同步),风险大;
    • always:每次写缓冲后都 fsync,最安全但性能和磁盘寿命受影响;
    • everysec:每秒一次异步 fsync(BIO 线程),在安全性与性能间折中。
  2. AOF 重写(Rewrite)

    • 通过 BGREWRITEAOF 或自动触发,fork 子进程生成精简命令集:

      1. 子进程扫描每个 redisDb,将内存快照转为写命令写入临时文件;
      2. 主进程继续处理请求,并将写命令同时写入旧 AOF 与 rewrite 缓冲;
      3. 子进程完成后,主进程合并 rewrite 缓冲并替换旧文件;旧文件由 BIO 异步关闭。

在这里插入图片描述

  1. 优缺点

    • 优点:记录所有写操作,最多丢失 1–2 秒;兼容性好、可读;
    • 缺点:文件随时间增大,包含大量中间状态;恢复时需重放命令,速度相对较慢。

三、混合持久化

Redis 持久化是一个将内存数据转储到磁盘的过程。Redis 目前支持 RDB、AOF,以及混合存储三种模式。

  1. 原理与配置

    • 自 Redis 4.0 引入,5.0 默认开启;配置 aof-use-rdb-preamble yes
    • BGREWRITEAOF 时,子进程先将全量内存数据以 RDB 格式写入 AOF 临时文件,再追加期间新增写命令;
  2. 流程

    1. fork 子进程;
    2. 子进程将内存快照写为 RDB 格式到临时文件;
    3. 追加子进程运行期间主进程缓冲的写命令;
    4. 通知主进程替换 AOF,旧文件异步关闭。
  3. 优缺点

    • 优点:兼具 RDB 加载快与 AOF 新数据保留特性;恢复速度快且几乎无数据丢失;
    • 缺点:头部 RDB 部分依然为二进制,不易阅读;跨版本兼容需测试。

  • RDB:全量快照,文件小、加载快,但存在数据丢失窗口;
  • AOF:命令追加,几乎无丢失,兼容性高,但文件大、重放慢;
  • 混合:RDB+AOF,一体化折中方案。

Redis 后台异步 IO(BIO)

Redis 核心线程单线程模型虽能高效处理多数操作,但对文件关闭、磁盘同步、以及大对象的逐一回收等系统调用依然容易导致短时阻塞,从而影响整体吞吐和响应延迟。接下来深入介绍 Redis 如何通过后台 IO(BIO)线程,将这些“慢任务”异步化,保证主线程的高可用性与低延迟。

在这里插入图片描述


一、BIO 线程设计动机

  • 单线程模型的挑战

    • 主线程需处理所有客户端请求、过期清理、淘汰等,性能极高;
    • 若再执行如 close()fsync()、大对象释放等系统调用,短则数毫秒、长则上百毫秒,都将阻塞请求处理,造成卡顿;
  • 异步化解决思路

    • 将这些“慢任务”提交给后台线程异步执行;
    • 主线程仅需快速入队并继续服务,显著降低响应延迟波动。

二、BIO 线程模型

Redis 采用经典的生产者-消费者模式:

  • 生产者:主线程在检测到慢任务时,构建相应的 BIO 任务结构并入队;
  • 消费者:专属的 BIO 线程阻塞等待队列中的新任务,一旦被唤醒即取出并执行;
  • 同步机制:使用互斥锁保护队列,条件变量实现高效唤醒/等待,确保线程安全与低开销。

三、BIO 任务类型

Redis 启动时,为三类任务分别创建独立的任务队列与线程:

BIO 线程名称任务队列主要用途
closecloseQ关闭旧 AOF/客户端/其他文件描述符,避免主线程被 close() 阻塞
fsyncfsyncQ将内核文件缓冲区的内容强制同步到磁盘(fsync()),保障数据持久化
lazyfreelazyfreeQ异步回收大对象(元素数 >64 的 list/set/hash/zset),避免主线程长时间释放

四、BIO 处理流程

在这里插入图片描述

  1. 任务提交(主线程)

    • 根据任务类型分配 BIO 任务;
    • 加锁后将任务追加到对应队列尾部;
    • 通过条件变量唤醒等待的 BIO 线程;
  2. 任务消费(BIO 线程)

    • 阻塞等待新任务到来;
    • 取出并执行对应系统调用或对象释放;
    • 任务完成后释放任务结构,继续等待;

通过引入专门的 BIO 后台线程队列,Redis 将所有可能导致短时阻塞的系统调用与大对象回收异步化处理,从而最大限度地保障主线程的低延迟和高吞吐能力。


Redis 多线程架构

Redis 自身单进程单线程模型极大简化了并发控制、保证了命令执行的原子性,但也限制了吞吐能力。相比 Memcached 能够通过多线程轻松跑出百万级 TPS,Redis 单实例 TPS 往往在 10–12 万左右,线上峰值也多在 2–4 万,难以充分利用现代 16+ 核服务器。为解决这一痛点,Redis 6.0 在不改动现有核心执行逻辑的前提下,引入了可选的 IO 多线程模型,以并行化网络读写与协议解析,从而在保留主线程执行安全的同时,实现 1–2 倍的性能提升。


一、主线程职责

  • 事件驱动 Loop(ae:监听客户端连接、读写事件与定时任务,不变;
  • 命令执行:所有实际的业务命令处理逻辑继续在单一主线程中运行,保持原子性与简单的调度;
  • 核心任务分发:在网络 IO 上,主线程将读写请求委派给 IO 线程;在命令执行完毕后,将回复操作同样回交给 IO 线程处理。

二、IO 线程设计

  • 目标:并行化耗时主要集中在三处:网络读取、协议解析与响应写入;
  • 配置:可通过 io-threadsio-threads-do-reads 参数开启与设置线程数(典型 4–8 个);
  • 模型:主线程负责将待读或待写的 client 对象加入对应队列,IO 线程异步批量拉取并行处理,处理完后主线程再继续后续逻辑。

三、命令处理完整流程

Redis 6.0 的多线程处理流程如图所示。主线程负责监听端口,注册连接读事件。当有新连接进入时,主线程 accept 新连接,创建 client,并为新连接注册请求读事件。

在这里插入图片描述

  1. 连接与读事件注册

    • 主线程 accept() 新连接,创建 client,注册可读事件;
  2. 并行读取与解析

    • 读事件触发时,主线程不直接读取,而将 client 加入待读取队列;
    • 当一轮事件循环结束,发现待读取链表非空,主线程将所有待读 client 分派给 IO 线程;
    • IO 线程并行读取各自 client->fd,将原始数据填入 client->querybuf,并执行协议解析,填充 client->argc/argv
    • IO 线程处理完所有任务后更新待处理计数,主线程自旋等待计数归零。
  3. 命令执行

    • 主线程依次取出已解析的命令,调用原有 redisCommand 处理函数执行;
  4. 并行响应写入

    • 执行结束后,主线程通过 addReply* 系列将结果填入 client->buf,并将 client 加入待写队列;
    • 将队列再次分派给 IO 线程并自旋等待;
    • IO 线程并行将 client->buf 写回各自连接,完成响应;
    • 主线程检测所有写入完成后,继续下一轮事件循环。

四、多线程方案优劣

优点缺点与瓶颈
- 并行化网络 IO 和协议解析,减少主线程阻塞,整体 TPS 提升 1–2 倍- 命令执行与事件调度仍集中于单一主线程,难以突破核心逻辑瓶颈
- 利用多核 CPU 优化网络吞吐,客户端连接并发性能更好- IO 批量处理模式需要“先读完再写回”,客户端间相互等待,增加延迟抖动
- 保留原子性与简洁性,无需改动现有命令实现- 主线程自旋等待 IO 线程完成,若任务少也会高频自旋,浪费 CPU 资源
- 部分场景下性能提升有限,无法替代真正的多线程命令处理模型

整体来看,Redis 6.0 的 IO 多线程是一次低侵入式的性能优化,能在不破坏兼容性与原子性的前提下带来可观提升。但要实现数量级跃升,还需将命令处理、事件调度等核心逻辑多线程化,解耦互斥等待,并逐步演进为真正的全栈并行模型。


复制架构原理

为了避免单点故障、提高可用性与读性能,必须对数据进行多副本存储。Redis 作为高性能的内存数据库,从一开始就内建了主从复制功能,并在各个版本迭代中不断优化复制策略。

在这里插入图片描述


一、复制架构原理

在这里插入图片描述

  • 多层嵌套复制:一个 Master 可以挂载多个 Slave,Slave 也可继续挂载更多下游 Slave,形成树状层级结构。
  • 写操作分发:所有写命令仅在 Master 节点执行,执行完后即时分发给下游所有 Slave,保证数据一致。
  • 读写分离:Master 只负责写请求,所有读请求由 Slave 处理。这种架构既消除了单点故障风险,又通过 N 倍 Slave 并发提升了读 TPS。

此外,Master 在向 Slave 分发写命令的同时,会将写指令保存到复制积压缓冲区(replication backlog),以便短时断连的 Slave 重连后增量同步。


二、同步方式对比

同步类型描述优势劣势
全量同步Master 生成 RDB 快照并传输给 Slave,同时发送缓冲区积压命令,Slave 全量重建数据。数据完整,适用于首次同步构建 RDB 和网络传输压力大,耗时长
增量同步Master 仅发送自上次同步位置之后的写命令,无需生成 RDB。轻量、带宽占用极低、无 RDB 构建延迟依赖缓冲区容量和断连时长,容易导致全量重试

三、psync 与 psync2 优化

  1. psync(Redis 2.8+)

    • 引入复制积压缓冲区
    • Slave 重连时上报 runid 与偏移量
    • 若 runid 一致且偏移仍在缓冲区,则返回 CONTINUE,进行增量同步;否则触发全量同步
  2. psync2(Redis 4.0+)

    • runid 升级为 replidreplid2
    • RDB 文件中存储 replid 作为 aux 信息,重启后可保留 replid
    • 切主时,通过 replid2 支持跨主机增量同步

相比早期版本,psync2 在短链路抖动、Slave 重启和主库切换等场景中,均能在更多情况下保持增量同步,显著降低性能开销与恢复时间。


四、复制连接与授权流程

在这里插入图片描述

  1. 连接检测

    • Slave 向 Master 发送 PING → 收到 PONG 则可用
  2. 鉴权(若启用密码)

    • Slave 发送 AUTH <masterauth>
  3. 能力协商

    • Slave 通过 REPLCONF 上报自身 IP、端口及支持的 eofpsync2 能力
  4. 同步请求

    • Slave 发送 PSYNC <replid> <offset>
    • Master 根据 replid、replid2 与复制积压缓冲,决定全量或增量

五、复制过程详析

5.1 增量同步流程

  • Master 返回 CONTINUE <replid>
  • Slave 将自身 replid 更新为 Master 返回的 replid,将原 replid 存为 replid2
  • Master 从偏移量继续推送写命令

5.2 全量同步流程

  • Master 返回 FULLRESYNC <replid> <offset>
  • Master 执行 BGSAVE 生成新的 RDB 快照
  • 将 RDB 与复制缓冲区命令一起推送给 Slave
  • Slave 关闭下游子 Slave 连接,清空本地缓冲
  • Slave 写入临时 RDB 文件(每 8MB fsync)→ 重命名 → 清库 → 加载 RDB
  • 重建与 Master 的命令推送通道,并开启 AOF 持久化

六、注意事项

  • 缓冲区大小:过大会占用过多内存;过小易导致缓冲刷出,触发全量复制
  • 网络稳定性:长连接抖动或丢包会影响复制效率
  • 监控复制延迟:及时预警和扩容,避免生产环境中读数据不一致或延迟过高
  • 主库切换策略:在切换 Master 前保证所有 Slave 与当前 Master 完成同步

Redis 集群的分布式方案

Redis 的分布式方案主要分为三类:

  1. Client 端分区
  2. Proxy 分区
  3. 原生 Redis Cluster

下面将逐一介绍它们的设计思路、实现方式及各自优缺点


1. Client 端分区

在这里插入图片描述

1.1 原理与哈希算法

客户端通过哈希算法决定某个 key 应存储在哪个分片(Shard)上,常见算法包括:

  • 取模哈希hash(key) % N
  • 一致性哈希:在哈希环上分配虚拟节点,实现动态扩缩容的平滑性
  • 区间分布哈希:实际是取模的变种,将 Hash 输出映射到固定区间,再由区间决定分片

对于单 key 请求,客户端直接计算哈希并路由;对于包含多个 key 的请求,客户端先对 key 按分片分组,再拆分成多条请求并发执行。

在这里插入图片描述

1.2 DNS 动态管理

由于每个 Redis 分片的 Master/Slave 都有独立 IP:Port,当发生故障切换或新增 Slave 时,客户端需更新连接列表。

  • DNS 管理:为每个分片的主/从分别配置不同域名,客户端定时异步解析域名、更新连接池
  • 负载均衡:按权重将请求在各 Slave 之间轮询,既可分散读压,又无需业务侧改动

1.3 优缺点

  • 优点:无中心依赖、逻辑简单、性能最优(无额外代理),客户端可灵活控制
  • 缺点:扩展不够平滑(新增分片需修改客户端逻辑并重启)、业务端分片逻辑耦合

2. Proxy 分区方案

在这里插入图片描述

2.1 架构概览

客户端只需连接到统一的 Proxy 层,由 Proxy 完成路由、拆分及聚合:

  • 接收请求 → 解析命令 → 哈希计算 → 路由到对应 Redis → 聚合响应 → 返回客户端

这样,客户端免维护分片信息,真正的分布式逻辑都隐藏在 Proxy 之下。

2.2 典型实现

方案特点扩缩容性能损耗
Twemproxy单进程单线程;实现简单、稳定;不支持平滑扩缩重启 Proxy~5–15%
Codis支持在线数据迁移;丰富的 Dashboard;多实例Dashboard+ZK/etcd略高于 Twemproxy
  • Twemproxy:适合小规模、几乎不扩缩容的场景;但单线程模型对多 key 请求性能有限
  • Codis:基于 Redis 扩展的 Slot 方案,提供 dashboard 管理,支持在线扩缩容

在这里插入图片描述

2.3 优缺点

  • 优点:客户端无需感知分片;扩缩容仅改 Proxy,运维便利
  • 缺点:增加访问中间层,带来约 5–15% 的性能开销;系统更复杂

3. 原生 Redis Cluster

在这里插入图片描述

3.1 Slot 与 Gossip 架构

  • 16384 个 Slot:启动时通过 CLUSTER ADDSLOTS 将 Slot 分配到各节点,key 经 CRC16 哈希后落在具体 Slot
  • Gossip 协议:节点间去中心化通信,更新拓扑无需中心节点,操作通过 cluster meet 等命令扩散

3.2 读写与重定向

在这里插入图片描述

  • Smart Client 缓存 Slot→节点映射
  • 若请求到错节点,返回 MOVEDASK,包含正确节点信息,客户端解析后重定向
  • 迁移过程中,新旧节点返回 ASK 并引导客户端临时访问迁移节点

3.3 在线扩缩容与数据迁移

在这里插入图片描述

  1. cluster meet 加入新节点(无 Slot,不可读写)
  2. 源节点 cluster setslot slot migrating,目标节点 … importing
  3. cluster getkeysinslot + migrate 迁移 key 数据;迁移期间阻塞该进程
  4. cluster setslot slot nodeid 分配 Slot
  5. 为新主节点添加 Slave:使用 cluster replicate,Slave 只能挂到 Master

缩容则相反:先迁移 Slot,再 cluster forget 下线节点(并加入禁止列表)。

3.4 优缺点

  • 优点:社区官方实现;在线扩缩容;无中心依赖;自动故障转移
  • 缺点:Slot 与 key 映射占内存;迁移阻塞导致卡顿;复制链路单层限制了读扩展

4. 对比与选型建议

维度Client 分区Proxy 分区Redis Cluster
客户端维护需 Smart Client
扩缩容平滑度中(Proxy 重启)高(在线迁移)
性能开销最小中等(5–15%)较低
运维复杂度业务侧Proxy 层集群管理
成熟度通用方案Codis、Twemproxy官方支持
  • 读密集场景:若对扩缩容需求不强,且对性能最敏感,可考虑 Client 分区。
  • 写扩展与在线迁移:需平滑扩缩容,且可接受少量代理层开销,推荐 Codis 或 Redis Cluster。
  • 大规模复杂部署:倾向官方 Redis Cluster,享受社区生态和原生工具支持。

三种方案各有侧重:Client 分区最轻量、性能最高;Proxy 分区运维便利;Redis Cluster 原生、弹性最佳。实际生产中,可根据业务特性和运维成本做权衡。

在这里插入图片描述

相关文章:

  • 华为OD机试真题—— 矩阵匹配(2025B卷:200分)Java/python/JavaScript/C/C++/GO最佳实现
  • Redis数据安全分析
  • 上海医日健集团物联网专利技术领跑智慧药房赛道
  • Lua 脚本在 Redis 中的运用-24 (使用 Lua 脚本实现原子计数器)
  • (27)运动目标检测 之 分类(如YOLO) 数据集自动划分
  • 大语言模型在软件工程中的应用、影响与展望
  • 什么是 Spring MVC 的异步请求处理?
  • ZLG USBCANFD python UDS刷写脚本
  • 【HarmonyOS5】DevEco Studio 使用指南:代码阅读与编辑功能详解
  • 【寻找Linux的奥秘】第八章:进程控制
  • string的使用和模拟实现
  • LeetCode --- 450周赛
  • 【图像大模型】ControlNet:深度条件控制的生成模型架构解析
  • 项目阅读:Instruction Defense
  • 前端vue3实现图片懒加载
  • 漫谈英伟达GPU架构进化史:从Celsius到Blackwell
  • 《仿盒马》app开发技术分享-- 原生地图展示(端云一体)
  • 《深入剖析:Python自动化测试框架之unittest与pytest》
  • 2025-5-22Vue3快速上手
  • Linux--vim
  • 平湖网站设计/百度文章收录查询
  • 山东网站制作/产品市场推广方案范文
  • 重庆企业网站制作外包/东莞谷歌推广公司
  • 地方网站怎么做挣钱/杭州seo推广公司
  • 通用网址通用网站查询/百度下载免费官方安装
  • 网站开发的wbs分解图/潮州网络推广