• Redis第四五六章 持久化&事务&主从复制


            Redis ⽀持 RDB 和 AOF 两种持久化机制,持久化功能有效地避免因进程退出造成数据丢失问题, 当下次重启时利⽤之前持久化的⽂件即可实现数据恢复。

    目录

    第四章 持久化

    4.1 RDB

    4.1.1 触发机制

    4.1.2 流程说明

    4.1.3 RDB ⽂件的处理

    4.1.4 RDB 的优缺点

    4.2 AOF

    4.2.1 使⽤ AOF

    4.2.2 命令写⼊

    4.2.3 ⽂件同步

    4.2.4 重写机制

    4.2.5 启动时数据恢复

    4.3 本章重点回顾

    第五章 Redis 事务

    5.1 什么是事务

    5.2 事务操作

    第六章 主从复制

    6.1 配置

    6.2 拓扑

    6.3 原理


    第四章 持久化

    4.1 RDB

            RDB 持久化是把当前进程数据⽣成快照保存到硬盘的过程,触发 RDB 持久化过程分为⼿动触发和 ⾃动触发。

    4.1.1 触发机制

    ⼿动触发分别对应 save 和 bgsave 命令:

            • save 命令:阻塞当前 Redis 服务器,直到 RDB 过程完成为⽌,对于内存⽐较⼤的实例造成⻓时间 阻塞,基本不采⽤。

            • bgsave 命令:Redis 进程执⾏ fork 操作创建⼦进程,RDB 持久化过程由⼦进程负责,完成后⾃动 结束。阻塞只发⽣在 fork 阶段,⼀般时间很短。

    Redis 内部的所有涉及 RDB 的操作都采⽤类似 bgsave 的⽅式。

    除了⼿动触发之外,Redis 运⾏⾃动触发 RDB 持久化机制,这个触发机制才是在实战中有价值的。

            1. 使⽤ save 配置。如 "save m n" 表⽰ m 秒内数据集发⽣了 n 次修改,⾃动 RDB 持久化。

            2. 从节点进⾏全量复制操作时,主节点⾃动进⾏ RDB 持久化,随后将 RDB ⽂件内容发送给从结点。

            3. 执⾏ shutdown 命令关闭 Redis 时,执⾏ RDB 持久化。

    4.1.2 流程说明

    bgsave 是主流的 RDB 持久化⽅式,下⾯根据图 了解它的运作流程。

    1. 执⾏ bgsave 命令,Redis ⽗进程判断当前进是否存在其他正在执⾏的⼦进程,如 RDB/AOF ⼦进 程,如果存在 bgsave 命令直接返回。

    2. ⽗进程执⾏ fork 创建⼦进程,fork 过程中⽗进程会阻塞,通过 info stats 命令查看 latest_fork_usec 选项,可以获取最近⼀次 fork 操作的耗时,单位为微秒。

    3. ⽗进程 fork 完成后,bgsave 命令返回 "Background saving started" 信息并不再阻塞⽗进程,可 以继续响应其他命令。

    4. ⼦进程创建 RDB ⽂件,根据⽗进程内存⽣成临时快照⽂件,完成后对原有⽂件进⾏原⼦替换。执 ⾏ lastsave 命令可以获取最后⼀次⽣成 RDB 的时间,对应 info 统计的 rdb_last_save_time 选 项。

    5. 进程发送信号给⽗进程表⽰完成,⽗进程更新统计信息。


    4.1.3 RDB ⽂件的处理

            保存:RDB ⽂件保存再 dir 配置指定的⽬录(默认 /var/lib/redis/)下,⽂件名通过 dbfilename 配置(默认 dump.rdb)指定。可以通过执⾏ config set dir {newDir} 和 config set dbfilename {newFilename} 运⾏期间动态执⾏,当下次运⾏时 RDB ⽂件会保存到新⽬录。

            压缩:Redis 默认采⽤ LZF 算法对⽣成的 RDB ⽂件做压缩处理,压缩后的⽂件远远⼩于内存⼤⼩,默认开启,可以通过参数 config set rdbcompression {yes|no} 动态修改。

            💡 虽然压缩 RDB 会消耗 CPU,但可以⼤幅降低⽂件的体积,⽅便保存到硬盘或通过⽹络发送到 从节点,因此建议开启。

            校验:如果 Redis 启动时加载到损坏的 RDB ⽂件会拒绝启动。这时可以使⽤ Redis 提供的 redis-check-dump ⼯具检测 RDB ⽂件并获取对应的错误报告。


    4.1.4 RDB 的优缺点

            • RDB 是⼀个紧凑压缩的⼆进制⽂件,代表 Redis 在某个时间点上的数据快照。⾮常适⽤于备份,全 量复制等场景。⽐如每 6 ⼩时执⾏ bgsave 备份,并把 RDB ⽂件复制到远程机器或者⽂件系统中 (如 hdfs)⽤于灾备。

            • Redis 加载 RDB 恢复数据远远快于 AOF 的⽅式。

            • RDB ⽅式数据没办法做到实时持久化 / 秒级持久化。因为 bgsave 每次运⾏都要执⾏ fork 创建⼦进 程,属于重量级操作,频繁执⾏成本过⾼。

             • RDB ⽂件使⽤特定⼆进制格式保存,Redis 版本演进过程中有多个 RDB 版本,兼容性可能有⻛ 险。


    4.2 AOF

            AOF(Append Only File)持久化:以独⽴⽇志的⽅式记录每次写命令,重启时再重新执⾏ AOF ⽂件中的命令达到恢复数据的⽬的。AOF 的主要作⽤是解决了数据持久化的实时性,⽬前已经是Redis 持久化的主流⽅式。理解掌握好 AOF 持久化机制对我们兼顾数据安全性和性能⾮常有帮助。

    4.2.1 使⽤ AOF

            开启 AOF 功能需要设置配置:appendonly yes,默认不开启。AOF ⽂件名通过 appendfilename 配置(默认是 appendonly.aof)设置。保存⽬录同 RDB 持久化⽅式⼀致,通过 dir 配置指定。AOF 的⼯作流程操作:命令写⼊(append)、⽂件同步(sync)、⽂件重写 (rewrite)、重启加载(load),如图 所⽰。

    1. 所有的写⼊命令会追加到 aof_buf(缓冲区)中。

    2. AOF 缓冲区根据对应的策略向硬盘做同步操作。

    3. 随着 AOF ⽂件越来越⼤,需要定期对 AOF ⽂件进⾏重写,达到压缩的⽬的。

    4. 当 Redis 服务器启动时,可以加载 AOF ⽂件进⾏数据恢复。


    4.2.2 命令写⼊

            AOF 命令写⼊的内容直接是⽂本协议格式。例如 set hello world 这条命令,在 AOF 缓冲区会追加如下 ⽂本:

    *3\r\n$3\r\nset\r\n$5\r\nhello\r\n$5\r\nworld\r\n

            此处遵守 Redis 格式协议,Redis 选择⽂本协议可能的原因:⽂本协议具备较好的兼容性;实现简单; 具备可读性。

    AOF 过程中为什么需要 aof_buf 这个缓冲区?        

            Redis 使⽤单线程响应命令,如果每次写 AOF ⽂件都直 接同步硬盘,性能从内存的读写变成 IO 读写,必然会下降。先写⼊缓冲区可以有效减少 IO 次数,同 时,Redis 还可以提供多种缓冲区同步策略,让⽤⼾根据⾃⼰的需求做出合理的平衡。


    4.2.3 ⽂件同步

            Redis 提供了多种 AOF 缓冲区同步⽂件策略,由参数 appendfsync 控制,不同值的含义如表 所⽰。 

    系统调⽤ write 和 fsync 说明:

    • write 操作会触发延迟写(delayed write)机制。Linux 在内核提供⻚缓冲区⽤来提供硬盘 IO 性 能。write 操作在写⼊系统缓冲区后⽴即返回。同步硬盘操作依赖于系统调度机制,例如:缓冲区 ⻚空间写满或达到特定时间周期。同步⽂件之前,如果此时系统故障宕机,缓冲区内数据将丢失。

    • Fsync 针对单个⽂件操作,做强制硬盘同步,fsync 将阻塞直到数据写⼊到硬盘。

    • 配置为 always 时,每次写⼊都要同步 AOF ⽂件,性能很差,在⼀般的 SATA 硬盘上,只能⽀持⼤ 约⼏百 TPS 写⼊。除⾮是⾮常重要的数据,否则不建议配置。

    • 配置为 no 时,由于操作系统同步策略不可控,虽然提⾼了性能,但数据丢失⻛险⼤增,除⾮数据 重要程度很低,⼀般不建议配置。

    • 配置为 everysec,是默认配置,也是推荐配置,兼顾了数据安全性和性能。理论上最多丢失 1 秒的 数据。


    4.2.4 重写机制

            随着命令不断写⼊ AOF,⽂件会越来越⼤,为了解决这个问题,Redis 引⼊ AOF 重写机制压缩⽂ 件体积。AOF ⽂件重写是把 Redis 进程内的数据转化为写命令同步到新的 AOF ⽂件。

    重写后的 AOF 为什么可以变⼩?有如下原因:

    • 进程内已超时的数据不再写⼊⽂件。

    • 旧的 AOF 中的⽆效命令,例如 del、hdel、srem 等重写后将会删除,只需要保留数据的最终版 本。

    • 多条写操作合并为⼀条,例如 lpush list a、lpush list b、lpush list 从可以合并为 lpush list a b c。

    较⼩的 AOF ⽂件⼀⽅⾯降低了硬盘空间占⽤,⼀⽅⾯可以提升启动 Redis 时数据恢复的速度。

    AOF 重写过程可以⼿动触发和⾃动触发:

    • ⼿动触发:调⽤ bgrewriteaof 命令。

    • ⾃动触发:根据 auto-aof-rewrite-min-size 和 auto-aof-rewrite-percentage 参数确定⾃动触发时 机。

             ◦ auto-aof-rewrite-min-size:表⽰触发重写时 AOF 的最⼩⽂件⼤⼩,默认为 64MB。

            ◦ auto-aof-rewrite-percentage:代表当前 AOF 占⽤⼤⼩相⽐较上次重写时增加的⽐例。

    当触发 AOF 重写时,下图介绍它的运⾏流程。 

    1. 执⾏ AOF 重写请求。

            如果当前进程正在执⾏ AOF 重写,请求不执⾏。如果当前进程正在执⾏ bgsave 操作,重写命令 延迟到 bgsave 完成之后再执⾏。

    2. ⽗进程执⾏ fork 创建⼦进程。

    3. 重写

            a. 主进程 fork 之后,继续响应其他命令。所有修改操作写⼊ AOF 缓冲区并根据 appendfsync 策 略同步到硬盘,保证旧 AOF ⽂件机制正确。

             b. ⼦进程只有 fork 之前的所有内存信息,⽗进程中需要将 fork 之后这段时间的修改操作写⼊ AOF 重写缓冲区中。

    4. ⼦进程根据内存快照,将命令合并到新的 AOF ⽂件中。

    5. ⼦进程完成重写

            a. 新⽂件写⼊后,⼦进程发送信号给⽗进程。

            b. ⽗进程把 AOF重写缓冲区内临时保存的命令追加到新 AOF ⽂件中。

            c. ⽤新 AOF ⽂件替换⽼ AOF ⽂件。


    4.2.5 启动时数据恢复

            当 Redis 启动时,会根据 RDB 和 AOF ⽂件的内容,进⾏数据恢复,如图所⽰。 Redis 根据持久化⽂件进⾏数据恢复


    4.3 本章重点回顾

    1. Redis 提供了两种持久化⽅案:RDB 和 AOF。

    2. RDB 视为内存的快照,产⽣的内容更为紧凑,占⽤空间较⼩,恢复时速度更快。但产⽣ RDB 的开 销较⼤,不适合进⾏实时持久化,⼀般⽤于冷备和主从复制。

    3. AOF 视为对修改命令保存,在恢复时需要重放命令。并且有重写机制来定期压缩 AOF ⽂件。

    4. RDB 和 AOF 都使⽤ fork 创建⼦进程,利⽤ Linux ⼦进程拥有⽗进程内存快照的特点进⾏持久化, 尽可能不影响主进程继续处理后续命令。


    第五章 Redis 事务

    5.1 什么是事务

    Redis 的事务和 MySQL 的事务概念上是类似的. 都是把⼀系列操作绑定成⼀组. 让这⼀组能够批量执 ⾏. 但是注意体会 Redis 的事务和 MySQL 事务的区别:

    • 弱化的原⼦性: redis 没有 "回滚机制". 只能做到这些操作 "批量执⾏". 不能做到 "⼀个失败就恢复到 初始状态".

    • 不保证⼀致性: 不涉及 "约束". 也没有回滚. MySQL 的⼀致性体现的是运⾏事务前和运⾏后 , 结果都 是合理有效的, 不会出现中间⾮法状态

    • 不需要隔离性: 也没有隔离级别, 因为不会并发执⾏事务 (redis 单线程处理请求) .

    • 不需要持久性: 是保存在内存的. 是否开启持久化, 是redis-server ⾃⼰的事情, 和事务⽆关.

    Redis 事务本质上是在服务器上搞了⼀个 "事务队列". 每次客⼾端在事务中进⾏⼀个操作, 都会把命令先 发给服务器, 放到 "事务队列" 中(但是并不会⽴即执⾏)

    ⽽是会在真正收到 EXEC 命令之后, 才真正执⾏队列中的所有操作.

    因此, Redis 的事务的功能相⽐于 MySQL 来说, 是弱化很多的. 只能保证事务中的这⼏个操作是 "连续 的", 不会被别的客⼾端 "加塞", 仅此⽽已.

    5.2 事务操作

    MULTI 开启⼀个事务. 执⾏成功返回 OK.

    1. 127.0.0.1:6379> MULTI
    2. OK

    EXEC 真正执⾏事务. 

    1. 127.0.0.1:6379> MULTI
    2. OK
    3. 127.0.0.1:6379> set k1 1
    4. QUEUED
    5. 127.0.0.1:6379> set k2 2
    6. QUEUED
    7. 127.0.0.1:6379> set k3 3
    8. QUEUED
    9. 127.0.0.1:6379> EXEC
    10. 1) OK
    11. 2) OK
    12. 3) OK

    每次添加⼀个操作, 都会提⽰ "QUEUED", 说明命令已经进⼊客⼾端的队列了.

    真正执⾏ EXEC 的时候, 客⼾端才会真正把上述操作发送给服务器.

    此时就可以获取到上述 key 的值了.

    1. 127.0.0.1:6379> get k1
    2. "1"
    3. 127.0.0.1:6379> get k2
    4. "2"
    5. 127.0.0.1:6379> get k3
    6. "3"

    DISCARD 放弃当前事务. 此时直接清空事务队列. 之前的操作都不会真正执⾏到. 

    1. 127.0.0.1:6379> MULTI
    2. OK
    3. 127.0.0.1:6379> set k1 1
    4. QUEUED
    5. 127.0.0.1:6379> set k2 2
    6. QUEUED
    7. 127.0.0.1:6379> DISCARD
    8. OK
    9. 127.0.0.1:6379> get k1
    10. (nil)
    11. 127.0.0.1:6379> get k2
    12. (nil)

    WATCH 在执⾏事务的时候, 如果某个事务中修改的值, 被别的客⼾端修改了, 此时就容易出现数据不⼀致的问题. 

    1. # 客⼾端1 先执⾏
    2. 127.0.0.1:6379> MULTI
    3. OK
    4. 127.0.0.1:6379> set key 100
    5. QUEUED
    6. # 客⼾端2 再执⾏
    7. 127.0.0.1:6379> set key 200
    8. OK
    9. # 客⼾端1 最后执⾏
    10. 127.0.0.1:6379> EXEC
    11. 1) OK

    此时, key 的值是多少呢??

    从输⼊命令的时间看, 是客⼾端1 先执⾏的 set key 100. 客⼾端2 后执⾏的 set key 200. 但是从实际的执⾏时间看, 是客⼾端2 先执⾏的, 客⼾端1 后执⾏的.

    1. 127.0.0.1:6379> get key
    2. "100"

    这个时候, 其实就容易引起歧义. 

    因此, 即使不保证严格的隔离性, ⾄少也要告诉⽤⼾, 当前的操作可能存在⻛险.

    watch 命令就是⽤来解决这个问题的.

    watch 在该客⼾端上监控⼀组具体的 key.

            • 当开启事务的时候, 如果对 watch 的 key 进⾏修改, 就会记录当前 key 的 "版本号". (版本号是个简单 的整数, 每次修改都会使版本变⼤. 服务器来维护每个 key 的版本号情况)

            • 在真正提交事务的时候, 如果发现当前服务器上的 key 的版本号已经超过了事务开始时的版本号, 就 会让事务执⾏失败. (事务中的所有操作都不执⾏).

    客⼾端1 先执⾏ 

    1. 127.0.0.1:6379> watch k1 # 开始监控 k1
    2. OK
    3. 127.0.0.1:6379> MULTI
    4. OK
    5. 127.0.0.1:6379> set k1 100 # 进⾏修改, 从服务器获取 k1 的版本号是 0. 记录 k1 的版
    6. QUEUED
    7. 127.0.0.1:6379> set k2 1000
    8. QUEUED

    只是⼊队列, 但是不提交事务执⾏.

    客⼾端2 再执⾏

    1. 127.0.0.1:6379> set k1 200 # 修改成功, 使服务器端的 k1 的版本号 0 -> 1
    2. OK

    客⼾端1 再执⾏

    1. 127.0.0.1:6379> EXEC # 真正执⾏修改操作, 此时对⽐版本发现, 客⼾端的 k1 的版本
    2. (nil)
    3. 127.0.0.1:6379> get k1
    4. "200"
    5. 127.0.0.1:6379> get k2
    6. (nil)

    此时说明事务已经被取消了. 这次提交的所有命令都没有执⾏.


    UNWATCH 取消对 key 的监控. 相当于 WATCH 的逆操作. 此处不做演⽰.


    第六章 主从复制

    本章节相关操作不需要记忆!!! 后续⼯作中如果⽤到了能查到即可. 重点⼤家理解流程和原理.

            在分布式系统中为了解决单点问题,通常会把数据复制多个副本部署到其他服务器,满⾜故障恢 复和负载均衡等需求。Redis 也是如此,它为我们提供了复制的功能,实现了相同数据的多个 Redis 副 本。复制功能是⾼可⽤ Redis 的基础,哨兵和集群都是在复制的基础上构建的。本章内容如下:

            • 介绍复制的使⽤⽅式:如何建⽴或断开复制、安全性、只读等。

            • 说明复制可⽀持的拓扑结构,以及每个拓扑结构的适⽤场景。

            • 分析复制的原理,包括:建⽴复制、全量复制、部分复制、⼼跳检测等。

    如果同学⽐较少, 那么⼀个⽼师既可以进⾏授课, 也可以进⾏答疑; 但是随着学⽣多了, ⼀个⽼师也就 应付不过来了. 就需要配⼏个助教⽼师, 助教⽼师从授课⽼师这⾥获取知识, 协助授课⽼师答疑.


    6.1 配置

            建⽴复制

            参与复制的 Redis 实例划分为主节点(master)和从节点(slave)。每个从结点只能有⼀个主节点, ⽽⼀个主节点可以同时具有多个从结点。复制的数据流是单向的,只能由主节点到从节点。配置复制 的⽅式有以下三种:

            1. 在配置⽂件中加⼊ slaveof {masterHost} {masterPort} 随 Redis 启动⽣效。

            2. 在 redis-server 启动命令时加⼊ --slaveof {masterHost} {masterPort} ⽣效。

            3. 直接使⽤ redis 命令:slaveof {masterHost} {masterPort} ⽣效。

    接下来,我们将 redis.conf 配置⽂件复制⼀份 redis-slave.conf,并且修改其 daemonize 为 yes。

    1. # By default Redis does not run as a daemon. Use 'yes' if you need it.
    2. # Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
    3. daemonize yes

    接下来,默认启动的 redis 作为主 Redis,重新通过命令⾏启动⼀个 Redis 实例作为从 Redis:

    1. # ubuntu
    2. redis-server /etc/redis/redis-slave.conf --port 6380 --slaveof 127.0.0.1 6379
    3. # centos
    4. redis-server /etc/redis-slave.conf --port 6380 --slaveof 127.0.0.1 6379

    注意: 修改配置主要是修改从机的配置. 主机配置不变.

    通过 netstat -nlpt 确保两个 Redis 均已正确启动。

    1. [root@host ~]# netstat -nlpt
    2. Active Internet connections (only servers)
    3. Proto Recv-Q Send-Q Local Address Foreign Address State
    4. tcp 0 0 127.0.0.1:6379 0.0.0.0:* LISTEN
    5. tcp 0 0 127.0.0.1:6380 0.0.0.0:* LISTEN

    通过 redis-cli 可以连接主 Redis 实例,通过 redis-cli -p 6380 连接从 Redis。并且观察复制关系。

    1. 127.0.0.1:6379> set hello world
    2. OK
    3. 127.0.0.1:6379> get hello
    4. "world"

    从运⾏结果中看到复制已经⼯作了,针对主节点 6379 的任何修改都可以同步到从节点 6380 中,复制 过程如图所⽰。


    Redis 主从节点复制过程

    可以通过 info replication 命令查看复制相关状态。

    1)主节点 6379 复制状态信息

    1. 127.0.0.1:6379> info replication
    2. # Replication
    3. role:master
    4. connected_slaves:1
    5. slave0:ip=127.0.0.1,port=6380,state=online,offset=100,lag=0
    6. master_replid:2fbd35a8b8401b22eb92ff49ad5e42250b3e7a06
    7. master_replid2:0000000000000000000000000000000000000000
    8. master_repl_offset:100
    9. second_repl_offset:-1
    10. repl_backlog_active:1
    11. repl_backlog_size:1048576
    12. repl_backlog_first_byte_offset:1
    13. repl_backlog_histlen:100

    2)从节点 6380 复制状态信息

    1. 127.0.0.1:6380> info replication
    2. # Replication
    3. role:slave
    4. master_host:127.0.0.1
    5. master_port:6379
    6. master_link_status:up
    7. master_last_io_seconds_ago:1
    8. master_sync_in_progress:0
    9. slave_repl_offset:170
    10. slave_priority:100
    11. slave_read_only:1
    12. connected_slaves:0
    13. master_replid:2fbd35a8b8401b22eb92ff49ad5e42250b3e7a06
    14. master_replid2:0000000000000000000000000000000000000000
    15. master_repl_offset:170
    16. second_repl_offset:-1
    17. repl_backlog_active:1
    18. repl_backlog_size:1048576
    19. repl_backlog_first_byte_offset:1
    20. repl_backlog_histlen:170

    断开复制

            slaveof 命令不但可以建⽴复制,还可以在从节点执⾏ slaveof no one 来断开与主节点复制关系。 例如在 6380 节点上执⾏ slaveof no one 来断开复制。

    断开复制主要流程:

            1)断开与主节点复制关系。

            2)从节点晋升为主节点。

    从节点断开复制后并不会抛弃原有数据,只是⽆法再获取主节点上的数据变化。

    通过 slaveof 命令还可以实现切主操作,将当前从节点的数据源切换到另⼀个主节点。执⾏ slaveof {newMasterIp} {newMasterPort} 命令即可。

    切主操作主要流程:

    1)断开与旧主节点复制关系。

    2)与新主节点建⽴复制关系。

    3)删除从节点当前所有数据。

    4)从新主节点进⾏复制操作。


    安全性

            对于数据⽐较重要的节点,主节点会通过设置 requirepass 参数进⾏密码验证,这时所有的客⼾ 端访问必须使⽤ auth 命令实⾏校验。从节点与主节点的复制连接是通过⼀个特殊标识的客⼾端来完 成,因此需要配置从节点的

    masterauth 参数与主节点密码保持⼀致,这样从节点才可以正确地连接到 主节点并发起复制流程。

    只读

            默认情况下,从节点使⽤ slave-read-only=yes 配置为只读模式。由于复制只能从主节点到从节 点,对于从节点的任何修改主节点都⽆法感知,修改从节点会造成主从数据不⼀致。所以建议线上不 要修改从节点的只读模式。

    传输延迟

            主从节点⼀般部署在不同机器上,复制时的⽹络延迟就成为需要考虑的问题,Redis 为我们提供 了 repl-disable-tcp-nodelay 参数⽤于控制是否关闭 TCP_NODELAY,默认为 no,即开启 tcp-nodelay 功能,说明如下:

            • 当关闭时,主节点产⽣的命令数据⽆论⼤⼩都会及时地发送给从节点,这样主从之间延迟会变⼩, 但增加了⽹络带宽的消耗。适⽤于主从之间的⽹络环境良好的场景,如同机房部署。

            • 当开启时,主节点会合并较⼩的 TCP 数据包从⽽节省带宽。默认发送时间间隔取决于 Linux 的内 核,⼀般默认为 40 毫秒。这种配置节省了带宽但增⼤主从之间的延迟。适⽤于主从⽹络环境复杂 的场景,如跨机房部署。


    6.2 拓扑

            Redis 的复制拓扑结构可以⽀持单层或多层复制关系,根据拓扑复杂性可以分为以下三种:⼀主⼀ 从、⼀主多从、树状主从结构。

    ⼀主⼀从结构

            ⼀主⼀从结构是最简单的复制拓扑结构,⽤于主节点出现宕机时从节点提供故障转移⽀持,如图 所⽰。当应⽤写命令并发量较⾼且需要持久化时,可以只在从节点上开启 AOF,这样既可以保证数据 安全性同时也避免了持久化对主节点的性能⼲扰。但需要注意的是,当主节点关闭持久化功能时,如 果主节点宕机要避免⾃动重启操作。

    ⼀主多从结构

            ⼀主多从结构(星形结构)使得应⽤端可以利⽤多个从节点实现读写分离,如图所⽰。对于 读⽐重较⼤的场景,可以把读命令负载均衡到不同的从节点上来分担压⼒。同时⼀些耗时的读命令可 以指定⼀台专⻔的从节点执⾏,避免破坏整体的稳定性。对于写并发量较⾼的场景,多个从节点会导 致主节点写命令的多次发送从⽽加重主节点的负载。

    树形主从结构

            树形主从结构(分层结构)使得从节点不但可以复制主节点数据,同时可以作为其他从节点的主 节点继续向下层复制。通过引⼊复制中间层,可以有效降低住系欸按负载和需要传送给从节点的数据 量,如图所⽰。数据写⼊节点 A 之后会同步给 B 和 C 节点,B 节点进⼀步把数据同步给 D 和 E 节 点。当主节点需要挂载等多个从节点时为了避免对主节点的性能⼲扰,可以采⽤这种拓扑结构。


    6.3 原理

    复制过程
            如图所⽰,下⾯详细介绍建⽴复制的完整流程。从图中可以看出复制过程⼤致分为 6 个过程:

    1)保存主节点(master)的信息。

    开始配置主从同步关系之后,从节点只保存主节点的地址信息,此时建⽴复制流程还没有开始, 在从节点 6380 执⾏ info replication 可以看到如下信息:

    1. master_host: 127.0.0.1
    2. master_port: 6379
    3. master_link_status: down

    从统计信息可以看出,主节点的 ip 和 port 被保存下来,但是主节点的连接状态 (master_link_status)是下线状态。

    2)从节点(slave)内部通过每秒运⾏的定时任务维护复制相关逻辑,当定时任务发现存在新的主节 点后,会尝试与主节点建⽴基于 TCP 的⽹络连接。如果从节点⽆法建⽴连接,定时任务会⽆限重试直 到连接成功或者⽤⼾停⽌主从复制。

    3)发送 ping 命令。连接建⽴成功之后,从节点通过 ping 命令确认主节点在应⽤层上是⼯作良好的。 如果 ping 命令的结果 pong 回复超时,从节点会断开 TCP 连接,等待定时任务下次重新建⽴连接。

    4)权限验证。如果主节点设置了 requirepass 参数,则需要密码验证,从节点通过配置 masterauth 参数来设置密码。如果验证失败,则从节点的复制将会停⽌。

    5)同步数据集。对于⾸次建⽴复制的场景,主节点会把当前持有的所有数据全部发送给从节点,这步 操作基本是耗时最⻓的,所以⼜划分称两种情况:全量同步和部分同步,下⼀节重点介绍。

    6)命令持续复制。当从节点复制了主节点的所有数据之后,针对之后的修改命令,主节点会持续的把 命令发送给从节点,从节点执⾏修改命令,保证主从数据的⼀致性。


    数据同步 psync

    Redis 使⽤ psync 命令完成主从数据同步,同步过程分为:全量复制和部分复制。

            • 全量复制:⼀般⽤于初次复制场景,Redis 早期⽀持的复制功能只有全量复制,它会把主节点全部 数据⼀次性发送给从节点,当数据量较⼤时,会对主从节点和⽹络造成很⼤的开销。

            • 部分复制:⽤于处理在主从复制中因⽹络闪断等原因造成的数据丢失场景,当从节点再次连上主节 点后,如果条件允许,主节点会补发数据给从节点。因为补发的数据远⼩于全量数据,可以有效避 免全量复制的过⾼开销。

    PSYNC 的语法格式

    PSYNC replicationid offset

    如果 replicationid 设为 ? 并且 offset 设为 -1 此时就是在尝试进⾏全量复制.

    如果 replicationid offset 设为了具体的数值, 则是尝试进⾏部分复制.


    1. replicationid/replid (复制id)

    主节点的复制 id. 主节点重新启动, 或者从节点晋级成主节点, 都会⽣成⼀个 replicationid. (同⼀个节 点, 每次重启, ⽣成的 replicationid 也会变化).

    从节点在和主节点建⽴连接之后, 就会获取到主节点的 replicationid.

    通过 info replication 即可看到 replicationid

    1. 127.0.0.1:6379> info replication
    2. # Replication
    3. role:master
    4. connected_slaves:0
    5. master_replid:1da596acecf5a34b4b2aae45bd35be785691ae69
    6. master_replid2:0000000000000000000000000000000000000000
    7. master_repl_offset:0
    8. second_repl_offset:-1
    9. repl_backlog_active:0
    10. repl_backlog_size:1048576
    11. repl_backlog_first_byte_offset:0
    12. repl_backlog_histlen:0

    关于 master_replid 和 master_replid2

    每个节点需要记录两组 master_replid . 这个设定解决的问题场景是这样的:

    ⽐如当前有两个节点 A 和 B, A 为 master, B 为 slave.

    此时 B 就会记录 A 的 master_replid.

    如果⽹络出现抖动, B 以为 A 挂了, B ⾃⼰就会成为主节点. 于是 B 给⾃⼰分配了新的 master_replid. 此时就会使⽤ master_replid2 来保存之前 A 的 master_replid.

            • 后续如果⽹络恢复了, B 就可以根据 master_replid2 找回之前的主节点.

            • 后续如果⽹络没有恢复, B 就按照新的 master_replid ⾃成⼀派, 继续处理后续的数据.


    2. offset (偏移量)

            参与复制的主从节点都会维护⾃⾝复制偏移量。主节点(master)在处理完写⼊命令后,会把命令的 字节⻓度做累加记录,统计信息在 info replication 中的 master_repl_offset 指标中。

    1. 127.0.0.1:6379> info replication
    2. # Replication
    3. role:master
    4. ...
    5. master_repl_offset:1055130

    从节点(slave)每秒钟上报⾃⾝的复制偏移量给主节点,因此主节点也会保存从节点的复制偏移量, 统计指标如下:

    1. 127.0.0.1:6379> info replication
    2. connected_slaves:1
    3. slave0:ip=127.0.0.1,port=6380,state=online,offset=1055214,lag=1
    4. ...

    从节点在接受到主节点发送的命令后,也会累加记录⾃⾝的偏移量。统计信息在 info replication 中的 slave_repl_offset 指标中:

    1. 127.0.0.1:6380> info replication
    2. # Replication
    3. role:slave
    4. ...
    5. slave_repl_offset:1055214

    复制偏移量的维护如图所⽰。通过对⽐主从节点的复制偏移量,可以判断主从节点数据是否⼀致。 

    这个偏移量, 就相当于 "学习进度".

    ⽐如⽼师这边准备了 10 个课件的内容. 助教要想给同学答疑, 也就需要学习完这 10 个课件的内容. 这 个偏移量就是当前助教学到了第⼏个课件了.

    ✍ replid + offset 共同标识了⼀个 "数据集". 如果两个节点, 他们的 replid 和 offset 都相同, 则这两个节点上持有的数据, 就⼀定相同.


    psync 运⾏流程

    1)从节点发送 psync 命令给主节点,replid 和 offset 的默认值分别是 ? 和 -1.

    2)主节点根据 psync 参数和⾃⾝数据情况决定响应结果:

            • 如果回复 +FULLRESYNC replid offset,则从节点需要进⾏全量复制流程。

            • 如果回复 +CONTINEU,从节点进⾏部分复制流程。

            • 如果回复 -ERR,说明 Redis 主节点版本过低,不⽀持 psync 命令。从节点可以使⽤ sync 命令进⾏ 全量复制。

                    • psync ⼀般不需要⼿动执⾏. Redis 会在主从复制模式下⾃动调⽤执⾏.

                    • sync 会阻塞 redis server 处理其他请求. psync 则不会.


    全量复制

            全量复制是 Redis 最早⽀持的复制⽅式,也是主从第⼀次建⽴复制时必须经历的阶段。全量复制的运 ⾏流程如图所⽰。 

    1)从节点发送 psync 命令给主节点进⾏数据同步,由于是第⼀次进⾏复制,从节点没有主节点的运 ⾏ ID 和复制偏移量,所以发送 psync ? -1。

    2)主节点根据命令,解析出要进⾏全量复制,回复 +FULLRESYNC 响应。

    3)从节点接收主节点的运⾏信息进⾏保存。

    4)主节点执⾏ bgsave 进⾏ RDB ⽂件的持久化。

    5)从节点发送 RDB ⽂件给从节点,从节点保存 RDB 数据到本地硬盘。

    6)主节点将从⽣成 RDB 到接收完成期间执⾏的写命令,写⼊缓冲区中,等从节点保存完 RDB ⽂件 后,主节点再将缓冲区内的数据补发给从节点,补发的数据仍然按照 rdb 的⼆进制格式追加写⼊到收 到的 rdb ⽂件中. 保持主从⼀致性。

    7)从节点清空⾃⾝原有旧数据。

    8)从节点加载 RDB ⽂件得到与主节点⼀致的数据。

    9)如果从节点加载 RDB 完成之后,并且开启了 AOF 持久化功能,它会进⾏ bgrewrite 操作,得到最 近的 AOF ⽂件。

    通过分析全量复制的所有流程,我们会发现全量复制是⼀件⾼成本的操作:主节点 bgsave 的时间, RDB 在⽹络传输的时间,从节点清空旧数据的时间,从节点加载 RDB 的时间等。所以⼀般应该尽可能 避免对已经有⼤量数据集的 Redis 进⾏全量复制。

    🚅 有磁盘复制 vs ⽆磁盘复制(diskless)

    默认情况下, 进⾏全量复制需要主节点⽣成 RDB ⽂件到主节点的磁盘中, 再把磁盘上的 RDB ⽂件通过发送给从节点.

    Redis 从 2.8.18 版本开始⽀持⽆磁盘复制. 主节点在执⾏ RDB ⽣成流程时, 不会⽣成 RDB ⽂ 件到磁盘中了, ⽽是直接把⽣成的 RDB 数据通过⽹络发送给从节点. 这样就节省了⼀系列的写 硬盘和读硬盘的操作开销.


    部分复制

            部分复制主要是 Redis 针对全量复制的过⾼开销做出的⼀种优化措施,使⽤ psync replicationId offset 命令实现。当从节点正在复制主节点时,如果出现⽹络闪断或者命令丢失等异常情况时,从节点 会向主节点要求补发丢失的命令数据,如果主节点的复制积压缓冲区存在数据则直接发送给从节点, 这样就可以保持主从节点复制的⼀致性。补发的这部分数据⼀般远远⼩于全量数据,所以开销很⼩。 整体流程如图所⽰。 图部分复制过程

    1)当主从节点之间出现⽹络中断时,如果超过 repl-timeout 时间,主节点会认为从节点故障并终端 复制连接。

    2)主从连接中断期间主节点依然响应命令,但这些复制命令都因⽹络中断⽆法及时发送给从节点,所 以暂时将这些命令滞留在复制积压缓冲区中。

    3)当主从节点⽹络恢复后,从节点再次连上主节点。

    4)从节点将之前保存的 replicationId 和 复制偏移量作为 psync 的参数发送给主节点,请求进⾏部分 复制。

    5)主节点接到 psync 请求后,进⾏必要的验证。随后根据 offset 去复制积压缓冲区查找合适的数据, 并响应 +CONTINUE 给从节点。

    6)主节点将需要从节点同步的数据发送给从节点,最终完成⼀致性。

    如果某个课件传输失败了, 助教可以单独要这个缺失的课件.


    复制积压缓冲区

            复制积压缓冲区是保存在主节点上的⼀个固定⻓度的队列,默认⼤⼩为 1MB,当主节点有连接的从节 点(slave)时被创建,这时主节点(master)响应写命令时,不但会把命令发送给从节点,还会写⼊ 复制积压缓冲区,如图所⽰。

    由于缓冲区本质上是先进先出的定⻓队列,所以能实现保存最近已复制数据的功能,⽤于部分复制和 复制命令丢失的数据补救。复制缓冲区相关统计信息可以通过主节点的 info replication 中:

    1. 127.0.0.1:6379> info replication
    2. # Replication
    3. role:master
    4. ...
    5. repl_backlog_active:1 // 开启复制缓冲区
    6. repl_backlog_size:1048576 // 缓冲区最⼤⻓度
    7. repl_backlog_first_byte_offset:7479 // 起始偏移量,计算当前缓冲区可⽤范围
    8. repl_backlog_histlen:1048576 // 已保存数据的有效⻓度

    根据统计指标,可算出复制积压缓冲区内的可⽤偏移量范围:[repl_backlog_first_byte_offset, repl_backlog_first_byte_offset + repl_backlog_histlen]。

    这个相当于⼀个基于数组实现的环形队列. 上述区间中的值就是 "数组下标" .

    📍 如果当前从节点需要的数据, 已经超出了主节点的积压缓冲区的范围, 则⽆法进⾏部分复制, 只 能全量复制了.


    实时复制

            主从节点在建⽴复制连接后,主节点会把⾃⼰收到的 修改操作 , 通过 tcp ⻓连接的⽅式, 源源不断的传 输给从节点. 从节点就会根据这些请求来同时修改⾃⾝的数据. 从⽽保持和主节点数据的⼀致性.

            另外, 这样的⻓连接, 需要通过⼼跳包的⽅式来维护连接状态. (这⾥的⼼跳是指应⽤层⾃⼰实现的⼼跳, ⽽不是 TCP ⾃带的⼼跳).

    1)主从节点彼此都有⼼跳检测机制,各⾃模拟成对⽅的客⼾端进⾏通信。

    2)主节点默认每隔 10 秒对从节点发送 ping 命令,判断从节点的存活性和连接状态。

    3)从节点默认每隔 1 秒向主节点发送 replconf ack {offset} 命令,给主节点上报⾃⾝当前的复制偏移 量。

    如果主节点发现从节点通信延迟超过 repl-timeout 配置的值(默认 60 秒),则判定从节点下线,断 开复制客⼾端连接。从节点恢复连接后,⼼跳机制继续进⾏。


    6.4 本章重点回顾

    主从复制解决的问题:

    单点问题.

            1. 单个 redis 节点, 可⽤性不⾼.

            2. 单个 redis 节点, 性能有限.

    主从复制的特点:

            1. Redis 通过复制功能实现主节点的多个副本。

            2. 主节点⽤来写, 从节点⽤来读. 这样做可以降低主节点的访问压⼒.

            3. 复制⽀持多种拓扑结构,可以在适当的场景选择合适的拓扑结构。

            4. 复制分为全量复制, 部分复制和实时复制。

            5. 主从节点之间通过⼼跳机制保证主从节点通信正常和数据⼀致性。

    主从复制配置的过程:

            1. 主节点配置不需要改动.

            2. 从节点在配置⽂件中加⼊ slaveof 主节点ip 主节点端⼝ 的形式即可.

    主从复制的缺点:

            1. 从机多了, 复制数据的延时⾮常明显.

            2. 主机挂了, 从机不会升级成主机. 只能通过⼈⼯⼲预的⽅式恢复.

  • 相关阅读:
    从云计算六大技术趋势,看亚马逊云科技的领先优势
    U-Net 模型改进和应用场景研究性综述
    导航系统架构及业务模块组合策略
    LLVM系列(1)- LLVM简介
    了解前端知识
    版本控制gitlab
    在 Windows 搭建 SVN 服务
    MR混合现实情景实训教学系统模拟历史情景
    QT创建数据库(QT4.11.1)
    Day14/15/16:哈夫曼树、哈弗曼编码(压缩与解压缩)
  • 原文地址:https://blog.csdn.net/m0_65601072/article/details/133813541