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.

127.0.0.1:6379> MULTI
OK

EXEC 真正执⾏事务. 

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 1
QUEUED
127.0.0.1:6379> set k2 2
QUEUED
127.0.0.1:6379> set k3 3
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) OK
3) OK

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

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

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

127.0.0.1:6379> get k1
"1"
127.0.0.1:6379> get k2
"2"
127.0.0.1:6379> get k3
"3"

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

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 1
QUEUED
127.0.0.1:6379> set k2 2
QUEUED
127.0.0.1:6379> DISCARD
OK
127.0.0.1:6379> get k1
(nil)
127.0.0.1:6379> get k2
(nil)

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

# 客⼾端1 先执⾏
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set key 100
QUEUED
# 客⼾端2 再执⾏
127.0.0.1:6379> set key 200
OK
# 客⼾端1 最后执⾏
127.0.0.1:6379> EXEC
1) OK

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

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

127.0.0.1:6379> get key 
"100"

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

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

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

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

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

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

客⼾端1 先执⾏ 

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

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

客⼾端2 再执⾏

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

客⼾端1 再执⾏

127.0.0.1:6379> EXEC # 真正执⾏修改操作, 此时对⽐版本发现, 客⼾端的 k1 的版本
(nil)
127.0.0.1:6379> get k1
"200"
127.0.0.1:6379> get k2
(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。

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

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

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

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

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

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

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

127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> get hello
"world" 

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


Redis 主从节点复制过程

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

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

127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=100,lag=0
master_replid:2fbd35a8b8401b22eb92ff49ad5e42250b3e7a06
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:100
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:100

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

127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_repl_offset:170
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:2fbd35a8b8401b22eb92ff49ad5e42250b3e7a06
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:170
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
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 可以看到如下信息:

master_host: 127.0.0.1
master_port: 6379
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

127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:0
master_replid:1da596acecf5a34b4b2aae45bd35be785691ae69
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
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 指标中。

127.0.0.1:6379> info replication
# Replication
role:master
...
master_repl_offset:1055130

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

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

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

127.0.0.1:6380> info replication
# Replication
role:slave
...
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 中:

127.0.0.1:6379> info replication
# Replication
role:master
...
repl_backlog_active:1 // 开启复制缓冲区
repl_backlog_size:1048576 // 缓冲区最⼤⻓度
repl_backlog_first_byte_offset:7479 // 起始偏移量,计算当前缓冲区可⽤范围
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. 主机挂了, 从机不会升级成主机. 只能通过⼈⼯⼲预的⽅式恢复.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/157548.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Godot快速精通-从看懂英文文档开始-翻译插件

视频教程地址:https://www.bilibili.com/video/BV1t8411q7hw/ 大家好,我今天要和大家分享的是如何快速精通Godot,众所周知,一般一个开源项目都会有一个文档,对于有一定基础或者是理解能力强的同学,看文档比…

深度学习_1_基本语法

数据结构 代码: import torchx torch.arange(12)##产生长度为12的一维张量print(x)##X x.resize(3, 4)##被弃用##print(X)y torch.reshape(x, (3, 4))##修改向量为矩阵,一维变二维print(y)print(y.size())xx torch.zeros((2, 3, 4))##三维矩阵&…

前端-uniapp-开发指南

美团外卖微信小程序开发 uniapp-美团外卖微信小程序开发P1 成果展示P2外卖小程序后端,学习给小程序写http接口P3 主界面配置P4 首页组件拆分P13 外卖列表布局筛选组件商家 布局测试数据创建样式 请求商家外卖数据封装请求并发请求 uni-app框架调用https接口 开发小程…

《UnityShader入门精要》学习3

笛卡尔坐标系(Cartesian Coordinate System) 二维笛卡儿坐标系 一个二维的笛卡儿坐标系包含了两个部分的信息: 一个特殊的位置,即原点,它是整个坐标系的中心。两条过原点的互相垂直的矢量,即x轴和y轴。这…

Elasticsearch 和 Arduino:一起变得更好!

作者:Enrico Zimuel 使用 Arduino IoT 设备与 Elasticsearch 和 Elastic Cloud 进行通信的简单方法 在 Elastic,我们不断寻找简化搜索体验的新方法,并开始关注物联网世界。 来自物联网的数据收集可能非常具有挑战性,尤其是当我们…

Webmin(CVE-2019-15107)远程命令执行漏洞复现

漏洞编号 CVE-2019-15107 webmin介绍 什么是webmin Webmin是目前功能最强大的基于Web的Unix系统管理工具。管理员通过浏览器访问Webmin的各种管理功能并完成相应的管理动作http://www.webmin.com/Webmin 是一个用 Perl 编写的基于浏览器的管理应用程序。是一个基于Web的界面…

Python 实训教学,更便捷的学生邀请及内容分发|ModelWhale 版本更新

中秋国庆假期结束,ModelWhale 又迎来了新一轮的版本更新,让我们调整好节奏,一起奔赴新的旅程! 本次更新中,ModelWhale 主要进行了以下功能迭代: 新增 一键邀请外部用户加入课程(团队版✓&#…

某医疗机构:建立S-SDLC安全开发流程,保障医疗前沿科技应用高质量发展

某医疗机构是头部资本集团旗下专注大健康领域战略性投资与运营的实业公司,市场规模超300亿。该医疗机构已完成数字赋能,形成了标准化、专业化、数字化的疾病和健康管理体系,将进一步规划战略方向,为人工智能纳米技术、高温超导、生…

代码随想录算法训练营第五十八天 |583. 两个字符串的删除操作、72. 编辑距离、编辑距离总结篇

一、583. 两个字符串的删除操作 题目链接/文章讲解/视频讲解:代码随想录 思考: 1.确定dp数组(dp table)以及下标的含义 dp[i][j]:以i-1为结尾的字符串word1,和以j-1位结尾的字符串word2,想要达…

springboot项目Html页面引入css文件不生效

我的出错原因&#xff1a; 在调用css文件时&#xff1a; <link rel"stylesheet" type"text/css" href"/static/css/style.css" /> 这里我多加了一个/static,而使得css样式不生效 因为在springboot项目中&#xff0c;静态资源是默认存…

TensorFlow入门(二十三、退化学习率)

学习率 学习率,控制着模型的学习进度。模型训练过程中,如果学习率的值设置得比较大,训练速度会提升,但训练结果的精度不够,损失值容易爆炸;如果学习率的值设置得比较小,精度得到了提升,但训练过程会耗费太多的时间,收敛速度慢,同时也容易出现过拟合的情况。 退化学习率 退化学…

PySpark 概述

文章最前&#xff1a; 我是Octopus&#xff0c;这个名字来源于我的中文名--章鱼&#xff1b;我热爱编程、热爱算法、热爱开源。所有源码在我的个人github &#xff1b;这博客是记录我学习的点点滴滴&#xff0c;如果您对 Python、Java、AI、算法有兴趣&#xff0c;可以关注我的…

B站视频“多模态大模型,科大讯飞前NLP专家串讲”记录

文章目录 多模态&#xff1a;对齐 -- align迁移学习和zero-shotClipBlip 多模态&#xff1a; 图片、文字、视频、语音等不同的表征。 表示信息的方式有多种&#xff0c;但是不同的表示方式携带的信息不完全相同。 对齐 – align 如第一个图中&#xff0c;文字内容的描述和图…

shiro反序列化和log4j

文章目录 安装环境shiro漏洞验证log4j 安装环境 进入vulhb目录下的weblogic&#xff0c;复现CVE-2018-2894漏洞&#xff1a; cd /vulhub/shiro/CVE-2010-3863查看docker-compose的配置文件&#xff1a; cat docker-compose.yml如图&#xff0c;里面有一个镜像文件的信息和服…

【数据结构】算法效率的度量方法

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:数据结构 ⚙️操作环境:Visual Studio 2022 目录 &#x1f38f;事后统计方法 &#x1f38f;事前分析估算方法 &#x1f38f;函数的渐进式增长 结语 在上篇文章中我们提到了算法的设计要求中我们要尽量满足时间效率高…

【Python 零基础入门】 Numpy

【Python 零基础入门】第六课 Numpy 概述什么是 Numpy?Numpy 与 Python 数组的区别并发 vs 并行单线程 vs 多线程GILNumpy 在数据科学中的重要性 Numpy 安装Anaconda导包 ndarraynp.array 创建数组属性np.zeros 创建np.ones 创建 数组的切片和索引基本索引切片操作数组运算 常…

计算机体系结构和操作系统

这篇文章的主要内容是冯诺依曼计算机体系结构和操作系统的理解。 目录 一.冯诺依曼计算机体系结构 二.操作系统的理解 一.冯诺依曼计算机体系结构 如图是冯诺依曼计算机体系结构&#xff0c;计算机本质就是对数据进行处理的机器&#xff0c;图中&#xff0c;数据从输入设备交给…

uni-app : 生成三位随机数、自定义全局变量、自定义全局函数、传参、多参数返回值

核心代码 function generateRandomNumber() {const min 100;const max 999;// 生成 min 到 max 之间的随机整数// Math.random() 函数返回一个大于等于 0 且小于 1 的随机浮点数。通过将其乘以 (max - min 1)&#xff0c;我们得到一个大于等于 0 且小于等于 (max - min 1…

200、使用默认 Exchange 实现 P2P 消息 之 消息生产者(发送消息) 和 消息消费者(消费消息)

RabbitMQ 工作机制图&#xff1a; Connection&#xff1a; 代表客户端&#xff08;包括消息生产者和消费者&#xff09;与RabbitMQ之间的连接。 Channel&#xff1a; 连接内部的Channel。channel&#xff1a;通道 Exchange&#xff1a; 充当消息交换机的组件。 Queue&#xff…

服务运营 |摘要:学术+业界-近期前沿运筹医疗合作精选

推文作者&#xff1a;李舒湉 编者按 本文归纳整理了近期INFORMS Journal on Applied Analytics中的相关业界合作研究。 这些研究成果体现了运筹学在医疗健康领域实践的效果。文中的学术业界合作使用了不同的研究工具。第一篇文章使用仿真模型帮助诊所进行不同拥挤程度下诊所使用…