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

文章目录

  • 缓存全景图
  • Pre
  • [Redis 整体认知框架](#Redis 整体认知框架)
  • [Redis 核心数据类型](#Redis 核心数据类型)
    • 概述
    • [1. String](#1. String)
    • [2. List](#2. List)
    • [3. Set](#3. Set)
    • [4. Sorted Set(有序集合)](#4. Sorted Set(有序集合))
    • [5. Hash](#5. Hash)
    • [6. Bitmap](#6. Bitmap)
    • [7. Geo](#7. Geo)
    • [8. HyperLogLog](#8. HyperLogLog)
  • [Redis 协议分析](#Redis 协议分析)
    • [1. RESP 设计原则](#1. RESP 设计原则)
    • [2. 三种响应模型与特殊模式](#2. 三种响应模型与特殊模式)
    • [3. 两种请求格式](#3. 两种请求格式)
      • [3.1 Inline 命令格式](#3.1 Inline 命令格式)
      • [3.2 Array(数组)格式](#3.2 Array(数组)格式)
    • [4. 五种响应格式详解](#4. 五种响应格式详解)
    • [5. 协议分类概览](#5. 协议分类概览)
    • [6. Redis Client 选型与改进建议](#6. Redis Client 选型与改进建议)
  • [Redis 的核心组件](#Redis 的核心组件)
  • Redis的事件驱动模型
  • [Redis 协议解析及处理](#Redis 协议解析及处理)
  • [Redis 内部数据结构](#Redis 内部数据结构)
  • [Redis 淘汰策略](#Redis 淘汰策略)
  • [Redis 的三种持久化方案及崩溃后数据恢复流程](#Redis 的三种持久化方案及崩溃后数据恢复流程)
    • [一、RDB 持久化](#一、RDB 持久化)
    • [二、AOF 持久化](#二、AOF 持久化)
    • 三、混合持久化
  • [Redis 后台异步 IO(BIO)](#Redis 后台异步 IO(BIO))
    • [一、BIO 线程设计动机](#一、BIO 线程设计动机)
    • [二、BIO 线程模型](#二、BIO 线程模型)
    • [三、BIO 任务类型](#三、BIO 任务类型)
    • [四、BIO 处理流程](#四、BIO 处理流程)
  • [Redis 多线程架构](#Redis 多线程架构)
  • 复制架构原理
  • [Redis 集群的分布式方案](#Redis 集群的分布式方案)
    • [1. Client 端分区](#1. Client 端分区)
      • [1.1 原理与哈希算法](#1.1 原理与哈希算法)
      • [1.2 DNS 动态管理](#1.2 DNS 动态管理)
      • [1.3 优缺点](#1.3 优缺点)
    • [2. Proxy 分区方案](#2. Proxy 分区方案)
      • [2.1 架构概览](#2.1 架构概览)
      • [2.2 典型实现](#2.2 典型实现)
      • [2.3 优缺点](#2.3 优缺点)
    • [3. 原生 Redis Cluster](#3. 原生 Redis Cluster)
      • [3.1 Slot 与 Gossip 架构](#3.1 Slot 与 Gossip 架构)
      • [3.2 读写与重定向](#3.2 读写与重定向)
      • [3.3 在线扩缩容与数据迁移](#3.3 在线扩缩容与数据迁移)
      • [3.4 优缺点](#3.4 优缺点)
    • [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:基数统计的近似算法
  9. ...

1. String

  • 存储方式

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

  • 常用指令SETGETMSETINCRDECR 等。

  • 典型场景

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

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

    • 分布式锁的简单实现。

      java 复制代码
      SET user:1001:token "abcd1234"
      INCR page:views

2. List

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

  • 指令摘要

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

  • 典型场景

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

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

    • 简易栈/队列。

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

3. Set

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

  • 指令摘要SADDSREMSISMEMBERSDIFFSINTERSUNIONSPOPSRANDMEMBER

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

  • 典型场景

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

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

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

      java 复制代码
      SADD user:1001:friends 1002 1003
      SISMEMBER user:1001:friends 1003  # 返回 1

4. Sorted Set(有序集合)

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

  • 指令摘要ZADDZREMZSCOREZRANGEZINCRBYZINTERSTOREZUNIONSTORE

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

  • 典型场景

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

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

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

      java 复制代码
      ZADD leaderboard 100 user:1001
      ZRANGE leaderboard 0 9 WITHSCORES  # TOP10

5. Hash

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

  • 指令摘要HSET/HMSETHGET/HMGETHEXISTSHINCRBYHGETALL

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

  • 典型场景

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

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

    • 业务统计字段聚合。

      java 复制代码
      HMSET user:1001 name "Alice" age 30
      HINCRBY user:1001:stats login_count 1

6. Bitmap

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

  • 指令摘要SETBITGETBITBITCOUNTBITOPBITFIELDBITPOS

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

  • 典型场景

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

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

    • 简易布隆过滤器原型。

      java 复制代码
      SETBIT login:20250525 1001 1
      BITCOUNT login:20250525  # 当天活跃用户数

7. Geo

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

  • 指令摘要GEOADDGEOPOSGEODISTGEORADIUSGEORADIUSBYMEMBER

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

  • 典型场景

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

    • 地理围栏告警;

    • 实时位置服务(LBS)。

      java 复制代码
      GEOADD restaurants 116.397128 39.916527 "PekingDuck"
      GEORADIUS restaurants 116.40 39.92 5 km WITHDIST

8. HyperLogLog

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

  • 指令摘要PFADDPFCOUNTPFMERGE

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

  • 典型场景

    • 大规模 UV 统计;

    • 海量搜索词汇去重;

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

      java 复制代码
      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

text 复制代码
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 等简短状态。
    text 复制代码
    +OK\r\n
  2. Error(错误)

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

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

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

    • 前缀 *,后跟元素个数,再 \r\n;随后依次是各元素(可嵌套上述任何格式)。
    text 复制代码
    *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 复制代码
      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 阈值)

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

数据类型 内部存储结构
String sds / 整数对象
List quicklist
Set dict
Hash ziplist(小型)/ dict(大型)
Sorted Set ziplist(小型)/ zskiplist(大型)
Stream radix tree + listpacks
HyperLogLog sds
Bitmap sds
Geo ziplist(小型)/ 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 带过期时间的 key expires 随机选一个 key 直接剔除 无明显访问热点,且仅对带过期时间的对象进行随机清理
allkeys-lru 所有 key volatile-lru 类似,但样本来自主字典 dict 全局范围的 LRU 淘汰,适合全量缓存且有热点区分的场景
allkeys-lfu 所有 key volatile-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 秒;兼容性好、可读;
    • 缺点:文件随时间增大,包含大量中间状态;恢复时需重放命令,速度相对较慢。

三、混合持久化

  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 线程名称 任务队列 主要用途
close closeQ 关闭旧 AOF/客户端/其他文件描述符,避免主线程被 close() 阻塞
fsync fsyncQ 将内核文件缓冲区的内容强制同步到磁盘(fsync()),保障数据持久化
lazyfree lazyfreeQ 异步回收大对象(元素数 >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 原生、弹性最佳。实际生产中,可根据业务特性和运维成本做权衡。

相关推荐
今天又在摸鱼4 小时前
rabbitmq的高级特性
分布式·rabbitmq
野犬寒鸦4 小时前
Redis核心数据结构操作指南:字符串、哈希、列表详解
数据结构·数据库·redis·后端·缓存·哈希算法
cui_win7 小时前
深入理解 Redis 哨兵模式
运维·redis·哨兵·哨兵故障
林晓lx7 小时前
[学习笔记] 从零开始虚拟化搭建数据库服务器
数据库·redis·笔记·centos
清幽竹客8 小时前
理解 Redis 事务-20 (MULTI、EXEC、DISCARD)
数据库·redis
龙哥·三年风水8 小时前
openresty+lua+redis把非正常访问的域名加入黑名单
redis·lua·openresty
LDM>W<9 小时前
黑马点评-分布式锁Lua脚本
java·分布式·lua
CET中电技术10 小时前
分布式光伏接入引起农村电压越限,如何处理?
分布式·光伏
鲸屿19511 小时前
kafka之操作示例
分布式·kafka