Redis 核心机制与架构演进详解(从单机到分布式集群)
一、 持久化机制:解决“数据不丢”的问题
Redis 数据存于内存,宕机即丢失。为了解决这个问题,Redis 提供了两种持久化方式:RDB(快照) 和 AOF(日志)。
1. RDB(Redis DataBase)—— 全量快照
核心原理:
将某一时刻的内存数据,以二进制形式全量写入硬盘(默认文件 dump.rdb)。
通俗解释:
给整个仓库拍一张全景照片。恢复时直接铺开照片,速度极快。但拍照需要遍历整个仓库,耗时较长。
核心机制:写时复制(Copy-On-Write, COW)
- 痛点:如果主进程自己去写硬盘,会阻塞客户端请求(浪费接待客人的时间)。
- 解决方案:主进程
fork出一个子进程去写硬盘。主进程继续接待新请求。 - COW 详细原理:
- 子进程刚出生时,与主进程共享同一份内存数据。
- 如果主进程要修改数据,不修改原内存,而是复制出该数据所在的内存页,在副本上修改。
- 子进程依然读取旧内存页,将其写入硬盘。
- 效果:子进程看到的是“出生那一刻”的完整快照;主进程不受影响,继续处理新请求。
- 内存膨胀风险:如果子进程写盘期间,主进程修改了所有数据,内存会短暂膨胀为原来的 2 倍。因此建议预留一半内存。
触发方式:
- 手动触发:
SAVE(阻塞主进程)、BGSAVE(fork 子进程)。 - 自动触发:配置文件中的
save <秒> <修改次数>。例如save 900 1表示 900 秒内至少 1 个键被修改则触发。- 三个规则是“或”关系,谁先满足谁触发。
- 触发后计数器清零,重新计时。
优缺点:
- 优点:文件小,恢复速度快。
- 缺点:全量备份耗时长,不能频繁执行,宕机可能丢失几分钟数据。
2. AOF(Append Only File)—— 增量日志
核心原理:
记录每一次写命令(如 SET, DEL, RPUSH),以追加的方式写入文件。读命令(如 GET)不记录。
通俗解释:
记流水账。恢复时需要把流水账里的命令重新跑一遍。
核心机制 1:aof_buf 缓冲区
- 痛点:每来一个命令就写一次硬盘,磁盘 I/O 会拖垮 Redis。
- 解决方案:命令先写入内存中的
aof_buf,然后根据策略批量刷入硬盘。 - 刷盘策略(appendfsync):
always:命令一进缓冲区立刻刷盘。最安全,最慢。everysec:每秒刷盘一次。默认配置,兼顾性能与安全(最多丢 1 秒数据)。no:由操作系统决定何时刷盘。最快,最不安全。
核心机制 2:AOF 重写(Rewrite)
- 痛点:流水账越记越长,文件巨大,恢复极慢。
- 例如:
SET count 1,SET count 2…SET count 10000。
- 例如:
- 解决方案:直接读取当前内存的最终数据,用最精简的命令重写一个新的 AOF 文件。
- 例如:直接写
SET count 10000。
- 例如:直接写
- 重写期间的数据一致性:
- 主进程的新写命令会同时写入
aof_buf和AOF重写缓冲区。 - 重写完成后,将
AOF重写缓冲区的内容追加到新 AOF 文件,保证数据不丢。
- 主进程的新写命令会同时写入
优缺点:
- 优点:丢数据少(可做到每秒记录),适合高频写入。
- 缺点:文件较大,恢复速度慢(需逐条执行命令)。
3. 混合持久化(推荐)
结合两者优点:先以 RDB 格式写入全量数据,再以 AOF 格式追加增量命令。兼顾恢复速度与数据安全性。
二、 主从复制:解决“读性能与数据备份”的问题
核心思想:读写分离。主节点(Master)负责写,从节点(Slave)负责读。
1. 全量同步(首次连接)
- 从节点连接主节点,主节点生成 RDB 发送给从节点。
- 关键细节:发送 RDB 期间,主节点产生的新写命令,会暂存到
replication buffer(复制缓冲区)。RDB 发送完毕后,再把这部分命令补发给从节点,保证数据一致。 - 通俗解释:新来的转学生抄笔记。老师一边发旧笔记(RDB),一边把新讲的知识点记在小本本(buffer)上,等旧笔记发完,再把小本本补发给转学生。
2. 增量同步(断线重连)
- 痛点:从节点掉线 10 秒,重连后不需要重新接收全量 RDB。
- 解决方案:引入
offset(复制偏移量)。- 主从节点各自维护一个 offset(类似看书页码)。
- 从节点重连后,上报自己的 offset。
- 主节点对比自己的 offset,只把缺失的那部分命令补发给从节点。
- 复制积压缓冲区(repl_backlog_buffer):主节点维护的一个固定大小环形缓冲区。如果从节点缺失的数据超过该缓冲区大小,则只能触发全量同步。
三、 哨兵模式(Sentinel):解决“自动容灾”的问题
痛点:主从模式下,主节点宕机需要人工手动切换,不够智能。
解决方案:引入哨兵集群,实现自动故障转移。
1. 哨兵集群
- 哨兵不存数据,只负责监控。
- 必须部署多个哨兵(奇数个),防止单个哨兵误判或挂掉。
2. 判定下线
- 主观下线(SDOWN):单个哨兵发现主节点无响应。
- 客观下线(ODOWN):多个哨兵(超过半数)投票确认主节点真的挂了。
3. 选举新主节点(三大标准)
- 优先级:
slave-priority配置最高的(硬件配置好的)。 - 断线时间:与主节点断开连接时间最短的(数据较新)。
- 复制偏移量:
offset最大的(数据最全)。
4. 故障转移
选出新主节点后,其他从节点改连新主,旧主节点复活后降级为从节点。
四、 Redis Cluster 集群:解决“海量数据与水平扩展”的问题
痛点:主从+哨兵,每个节点依然存全量数据,单机内存无法容纳海量数据。
解决方案:数据分片(Sharding),分布式存储。
1. 哈希槽(Hash Slot)
- Redis 集群共有 16384 个槽位。
- 数据路由:
CRC16(key) % 16384,计算出数据属于哪个槽。 - 槽位分配:每个主节点负责一部分槽位(如 3 个节点,每个管 5000+ 个槽)。
- 铁律:16384 个槽位必须全部分完,不能有空缺,否则集群下线。
2. 底层数据结构
- 每个节点内部维护一个
slots[16384/8]的位图数组(16384 个比特位)。 - 数组元素指向
clusterNode结构体,记录该槽位归属哪个节点。
3. 通信机制:Gossip 协议
- 节点之间互相“八卦”,定期随机通信。
- 同步集群状态(谁管哪些槽,谁死谁活),最终所有节点达成一致。
4. 客户端访问:MOVED 重定向
- 客户端算错槽,连错节点,节点返回
MOVED错误,告诉客户端去连正确的节点。 - 客户端会缓存“槽-节点”映射表,下次直接连对。
- 通俗解释:去银行 A 网点办业务,柜员说你的账户归 B 网点管,出门左转去 B。
5. 高可用
- 集群内部依然有主从复制。
- 主节点挂了,其他主节点投票,从该主节点的从节点中选一个晋升,接管其槽位。
五、 架构演进总结
| 阶段 | 核心技术 | 解决的问题 | 局限性 |
|---|---|---|---|
| 1. 单机 | RDB / AOF | 数据不丢(防断电) | 内存有限,无法存海量数据;单点故障 |
| 2. 主从 | 复制偏移量(offset) | 读并发、数据备份 | 主节点挂掉需人工切换 |
| 3. 哨兵 | 投票、客观下线 | 自动容灾(高可用) | 每个节点存全量数据,内存受限 |
| 4. 集群 | 哈希槽(16384)、Gossip | 海量数据、水平扩展 | 架构复杂,客户端需支持重定向 |
整体脉络:
为了不丢数据,搞了持久化;为了扛并发和备份,搞了主从;为了自动容灾,搞了哨兵;为了存海量数据,最终搞了集群。