跳至正文
来两杯美式
返回

Redis 知识系列(三):持久化与高可用

By 来两杯美式
发布于

RDB 持久化

触发配置

save   900  1       # 900s 内有 1 次更新
save  300   10      # 300s 内有 10 次更新
save  60     10000  # 60s 内有 10000 次更新

三个条件任一满足则触发 RDB 持久化。要禁用 RDB:

save  ""

其他相关配置

stop-writes-on-bgsave-error  yes
# 备份出错时主进程停止接受新写入,保护持久化数据一致性

rdbcompression  yes
# 备份时压缩再保存,节约磁盘 IO;CPU 紧张可设为 no

手动触发

Fork & CopyOnWrite 原理

调用 bgsave 时:

  1. 检查是否有正在执行的 aof/rdb 进程,有则抛错。
  2. Fork 主进程创建子进程。fork 使用 CopyOnWrite(写时复制):
    • 初始时父子进程指向同一块内存空间(共享页表)。
    • 任意一方试图修改数据时,操作系统才真正复制那一页给修改方。
  3. 子进程把内存中的数据序列化为 RDB 文件。

rdb 的代价:全量同步,数据量大时 IO 严重影响性能。fork 阻塞时间与内存大小正相关(页表复制成本)。

检查持久化状态

lastsave   # 上次持久化成功的时间戳

AOF 持久化

记录除查询以外所有变更数据库状态的指令,以 append 形式追加到 aof 文件,默认关闭。

开启

# redis.conf
appendonly  yes

# 或运行时
config set appendonly yes
config rewrite

三种刷盘策略

appendfsync  [always | everysec | no]
策略行为数据安全性性能开销
always每次修改都 fsync不丢数据高(一般 sata 盘只有几百 TPS)
everysec每秒 fsync 一次最多丢 1 秒数据中(推荐)
no交由 OS 控制不可控

AOF 重写

随着时间推移,AOF 文件越来越大,但中间数据可以省略(多次 SET 同一 key、过期 key 失效等),通过重写精简。

两种触发方式

auto-aof-rewrite-min-size    64mb      # AOF 文件达到这个大小开始重写
auto-aof-rewrite-percentage  100       # AOF 文件增长率(上次重写后翻倍触发)

为什么需要两个参数?单设尺寸阈值(如 64mb)的话,重写完成后数据增长超过 64mb 又会触发;加增长率默认 100%,第一次 64mb 触发后,第二次要 128mb 才触发。

重写原理:fork 子进程,子进程根据内存中的当前数据生成新 AOF 文件(不是基于原 AOF 处理),同时主进程继续将变更写入原 AOF 缓冲区,子进程完成后合并。

AOF 阻塞

AOF 追加阻塞流程

设置 everysec 后,AOF 流程:

  1. 主线程将变更写入 AOF 缓冲区
  2. 同步线程每 1 秒把缓冲区 fsync 到磁盘。
  3. 主线程对比上次 fsync 时间,如果距离上次已超过 2 秒,主线程会阻塞等待同步线程刷盘完成。
  4. 阻塞期间所有新命令都会排队,造成 Redis 整体响应延迟。

监控指标:INFO Persistence 中的 aof_delayed_fsync 计数大于 0 就说明发生过 AOF 阻塞。解决办法:换 ssd 盘、降低写入压力、或开启 no-appendfsync-on-rewrite yes

RDB-AOF 混合模式(4.0+ 推荐)

aof-use-rdb-preamble  yes

触发 AOF 重写后:

启动时如果同时存在 RDB 和 AOF 文件,优先加载 AOF

对比总结

  • RDB:文件小、恢复快、但只能保留最近一次快照,容易丢数据
  • AOF:可读性高、数据安全(everysec 丢 1 秒)、但文件体积大、恢复慢
  • RDB-AOF 混合:兼顾两者优点,4.0 后默认推荐

主从复制

配置从节点

# 命令方式
slaveof <主节点ip> <主节点端>

# 配置文件
slaveof <ip> <port>
slave-read-only yes       # 从节点只读(推荐)

全量同步(首次同步)

从节点刚刚加入时的同步过程:

  1. slave 发送 SYNC 命令到 master
  2. master 启动后台进程,将 redis 中的数据快照保存到 RDB 文件。
  3. master 将保存快照期间接收到的写命令缓存起来。
  4. master 完成快照保存后,将 RDB 文件发送给 slave
  5. slave 使用新 RDB 文件替换旧的 RDB 文件,恢复快照。
  6. master 将期间收集的增量写命令发送给 slave 进行回放。

Redis 2.8 之后用 PSYNC 替代 SYNC,支持部分重同步(基于 repl_backlog 复制偏移量),断线重连时只同步断开期间丢失的命令,不必每次都全量同步。

增量同步(运行期同步)

master 接收到用户写操作后的传播流程:

  1. 判断是否需要传播到 slave(一般增删改指令需要)。
  2. 将操作追加到 AOF 文件(如果开启)。
  3. 将操作传播到其他 slave:
    1. 对齐主从复制偏移量。
    2. 向响应缓存写入指令。
  4. 将缓存中的数据发送给 slave。

复制风暴

复制风暴:当主节点故障、新主节点产生时,所有从节点都要执行全量主从同步,fork 子进程、生成 RDB、在多个从节点间传输 RDB 都会消耗大量资源(CPU、磁盘 IO、网络带宽)。

解决:树状复制结构

复制风暴的树状解决

让部分从节点挂在新主节点下作为二级主节点,二级从节点再挂在一级从节点下,避免所有从节点同时对单点拉取全量数据。

新的问题:当 slave-1 发生故障时,二级从节点全部要重新级联同步,层级越多同步链越长。生产中一般不超过 2 级。


Redis Sentinel(哨兵)

Sentinel 是 Redis 官方提供的高可用方案:自动监控、通知、故障迁移、配置提供

架构

哨兵架构示意-1

哨兵架构图

Sentinel 自身也是一个不保存数据的 Redis 实例,多个部署(最少 3 个,部署在至少 2 台机器上)保证自身高可用。

故障迁移流程

故障迁移流程

故障迁移的关键步骤:

  1. 主观下线(SDOWN):单个 Sentinel 认为某个 master 不可达。
  2. 客观下线(ODOWN):超过 quorum(法定人数)数量的 Sentinel 都认为该 master 不可达,则客观下线。
  3. Leader 选举:在多个 Sentinel 中通过 Raft 简化版协议选举出一个 Leader 来执行 failover。
  4. 选新主:从 slave 中选出数据最新(slave-priorityrepl offset)的作为新 master。
  5. 切换角色:其他 slave 切换到复制新 master,原 master 恢复后变成新 master 的 slave。

Sentinel 选举机制(Raft 简化版)

Sentinel 集群要求奇数个节点(3、5、7),因为偶数节点的多数票和少 1 个节点一样(如 4 个和 3 个都需要 3 票容灾能力反而更低)。

客户端集成

Jedis 客户端实现

Jedis 客户端的实现原理:

  1. 启动时配置 masterNamesentinel 地址集合。
  2. 连接 sentinel 获取当前可用 master。
  3. 连接 master 进行操作。
  4. 监听 sentinel 的 +switch-master 频道,当 master 切换时自动重连新 master。
// Jedis 示例
Set<String> sentinels = new HashSet<>();
sentinels.add(new HostAndPort("10.0.0.1", 26379).toString());
sentinels.add(new HostAndPort("10.0.0.2", 26379).toString());

JedisSentinelPool pool = new JedisSentinelPool("mymaster", sentinels, config);
Jedis jedis = pool.getResource();
jedis.set("key", "value");

命令文档

redisdoc.com


分享这篇文章:
通过邮件分享这篇文章✓ 链接已复制
所属专题
Redis
第 3 / 4 篇
查看系列全部文章
  1. 01.Redis 知识系列(一):数据结构与单机原理
  2. 02.Redis 知识系列(二):高效使用
  3. 03.Redis 知识系列(三):持久化与高可用
  4. 04.Redis 知识系列(四):集群与缓存架构

上一篇
MySQL 知识系列(六):SQL 注入、版本特性与容量评估
下一篇
MySQL 知识系列(五):隔离级别、Spring 事务与查询流程