Redis 主从复制原则和过期密钥处理(下)
4 增量复制
- 如果全量复制过程中,M-R网络连接中断,那么salve重连M时,会触发增量复制
- M直接从自己的backlog中获取部分丢失的数据,发送给R node
- msater就是根据R发送的psync中的offset来从backlog中获取数据的
5 M关闭持久化时的复制安全性
在使用 Redis 复制功能时的设置中,推荐在 M 和 R 中启用持久化。
当不可能启用时,例如由于非常慢的磁盘性能而导致的延迟问题,应该配置实例来避免重启后自动重新开始复制。
关闭持久化并配置了自动重启的 M 是危险的:
- 设置节点 A 为 M 并关闭它的持久化设置,节点 B 和 C 从 节点 A 复制数据
- 节点 A 宕机,但它有一些自动重启系统可重启进程。但由于持久化被关闭了,节点重启后其数据集是空的!
- 这时B、C 会从A复制数据,但A数据集空,因此复制结果是它们会销毁自身之前的数据副本!
当 Redis Sentinel 被用于高可用并且 M 关闭持久化,这时如果允许自动重启进程也是很危险的。例如, M 可以重启的足够快以致于 Sentinel 没有探测到故障,因此上述的故障模式也会发生。
任何时候数据安全性都是很重要的,所以如果 M 使用复制功能的同时未配置持久化,那么自动重启进程这项就该被禁用。
用Redis主从同步,写入Redis的数据量太大,没加频次控制,导致每秒几十万写入,主从延迟过大,运维频频报警,在主库不挂掉的情况下,这样大量写入会不会造成数据丢失?
若主从延迟很大,数据会堆积到redis主库的发送缓冲区,会导致主库OOM。
6 复制工作原理
每个 M 都有一个 replication ID :一个较大的伪随机字符串,标记了一个给定的数据集。
每个 M 也持有一个偏移量,M 将自己产生的复制流发送给 R 时,发送多少个字节的数据,自身的偏移量就会增加多少,目的是当有新的操作修改自己的数据集时,它可据此更新 R 的状态。
复制偏移量即使在没有一个 R 连接到 M 时,也会自增,所以基本上每一对给定的
Replication ID, offset
都会标识一个 M 数据集的确切版本。
psync
R使用psync
从M复制,psync runid offset
M会根据自身情况返回响应信息:
- 可能是FULLRESYNC runid offset触发全量复制
- 可能是CONTINUE触发增量复制
R 连接到 M 时,它们使用 PSYNC 命令来发送它们记录的旧的 M replication ID 和它们至今为止处理的偏移量。通过这种方式, M 能够仅发送 R 所需的增量部分。
但若 M 的缓冲区中没有足够的命令积压缓冲记录,或者如果 R 引用了不再知道的历史记录(replication ID),则会转而进行一个全量重同步:在这种情况下, R 会得到一个完整的数据集副本,从头开始。即:
- 若R重连M,那么M仅会复制给R缺少的部分数据
- 若第一次连接M,那么会触发全量复制
Redis使用复制保证数据同步,以2.8版本为界:
2.8前性能较差的复制和命令传播
首先是从服务器发生同步操作sync,主服务器执行bgsave生成一个全量RDB文件,然后传输给从服务器。
同时主服务器会把这一过程中执行的写命令写入缓存区。从服务器会把RDB文件进行一次全量加载。
加载完毕后,主服务器会把缓存区中的写命令传给从服务器。从服务器执行命令后,主从服务器的数据就一致了。
这种方式每次如果网络出现故障,故障重连后都要进行全量数据的复制。对主服务器的压力太大,也会增加主从网络传输的资源消耗。
2.8后的优化
增加部分重同步功能,就是同步故障后的一部分数据,而非全量数据。这种优化在量级非常大的情况下效率提升很明显。
4.0的PSYNC2
7 复制的完整流程
R如果跟M有网络故障,断开连接会自动重连。
M如果发现有多个R都重新连接,仅会启动一个rdb save操作,用一份数据服务所有R。
- R启动,仅保存M的信息,包括M的
host
和ip
,但复制流程尚未开始M host和ip配置在redis.conf
中的 Rof - R内部有个定时任务,每s检查是否有新的M要连接和复制,若发现,就跟M建立socket网络连接。
- R发送ping命令给M
- 口令认证 - 若M设置了requirepass,那么salve必须同时发送Mauth的口令认证
- M 第一次执行全量复制,将所有数据发给R
- M后续持续将写命令,异步复制给R
heartbeat
主从节点互相都会发送heartbeat信息。
M默认每隔10秒发送一次heartbeat,salve node每隔1秒发送一个heartbeat。
8 断点续传
Redis 2.8开始支持主从复制的断点续传
主从复制过程,若网络连接中断,那么可以接着上次复制的地方,继续复制下去,而不是从头开始复制一份。
M和R都会维护一个offset
- M在自身基础上累加offset,R亦是
- R每秒都会上报自己的offset给M,同时M保存每个R的offset
M和R都要知道各自数据的offset,才能知晓互相之间的数据不一致情况。
backlog
M会在内存中维护一个backlog,默认1MB。M给R复制数据时,也会将数据在backlog中同步写一份。
backlog主要是用做全量复制中断时候的增量复制。
M和R都会保存一个replica offset还有一个M id,offset就是保存在backlog中的。若M和R网络连接中断,R会让M从上次replica offset开始继续复制。但若没有找到对应offset,就会执行resynchronization。
M run id
- info server,可见M run id
根据host+ip定位M node,是不靠谱的,如果M node重启或者数据出现了变化,那么R node应该根据不同的run id区分,run id不同就做全量复制。
如果需要不更改run id重启redis,可使用:
redis-cli debug reload
9 无磁盘化复制
M在内存中直接创建RDB,然后发送给R,不会在自己本地持久化。
只需要在配置文件中开启repl-diskless-sync yes
即可.
等待 5s 再开始复制,因为要等更多 R 重连 repl-diskless-sync-delay 5
10 处理过期key
Redis 的过期机制可以限制 key 的生存时间。此功能取决于 Redis 实例计算时间的能力,但是,即使使用 Lua 脚本更改了这些 key,Redis Rs 也能正确地复制具有过期时间的 key。
为实现功能,Redis 不能依靠主从使用同步时钟,因为这是一个无法解决的问题并且会导致 race condition 和数据不一致,所以 Redis 使用三种主要的技术使过期的 key 的复制能够正确工作:
R 不会让 key 过期,而是等待 M 让 key 过期。当一个 M 让一个 key 到期(或由于 LRU 删除)时,它会合成一个 DEL 命令并传输到所有 R
但由于这是 M 驱动的 key 过期行为,M 无法及时提供 DEL 命令,所以有时 R 的内存中仍可能存在逻辑上已过期的 key 。为处理该问题,R 使用它的逻辑时钟以报告只有在不违反数据集的一致性的读取操作(从主机的新命令到达)中才存在 key。用这种方法,R 避免报告逻辑过期的 key 仍然存在。在实际应用中,使用 R 程序进行缩放的 HTML 碎片缓存,将避免返回已经比期望的时间更早的数据项
在Lua脚本执行期间,不执行任何 key 过期操作
当一个Lua脚本运行时,概念上讲,M 中的时间是被冻结的,这样脚本运行的时候,一个给定的键要么存在or不存在。这可以防止 key 在脚本中间过期,保证将相同的脚本发送到 R ,从而在二者的数据集中产生相同的效果。
一旦 R 被提升 M ,它将开始独立过期 key,而不需要任何旧 M 帮助。
11 重新启动和故障转移后的部分重同步
Redis 4.0 开始,当一个实例在故障转移后被提升为 M 时,它仍然能够与旧 M 的 R 进行部分重同步。为此,R 会记住旧 M 的旧 replication ID 和复制偏移量,因此即使询问旧的 replication ID,也可以将部分复制缓冲提供给连接的 R 。
但是,升级的 R 的新 replication ID 将不同,因为它构成了数据集的不同历史记录。例如,M 可以返回可用,并且可以在一段时间内继续接受写入命令,因此在被提升的 R 中使用相同的 replication ID 将违反一对复制标识和偏移对只能标识单一数据集的规则。
另外,R 在关机并重新启动后,能够在 RDB 文件中存储所需信息,以便与 M 进行重同步。这在升级的情况下很有用。当需要时,最好使用 SHUTDOWN 命令来执行 R 的保存和退出操作。
参考
- https://raw.githubusercontent.com/antirez/redis/2.8/00-RELEASENOTES
- https://redis.io/topics/replication