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. 选举新主节点(三大标准)

  1. 优先级:slave-priority 配置最高的(硬件配置好的)。
  2. 断线时间:与主节点断开连接时间最短的(数据较新)。
  3. 复制偏移量: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 海量数据、水平扩展 架构复杂,客户端需支持重定向

整体脉络:
为了不丢数据,搞了持久化;为了扛并发和备份,搞了主从;为了自动容灾,搞了哨兵;为了存海量数据,最终搞了集群。