NoSQL数据库之Redis主从同步与级联复制

消息队列

消息队列: 把要传输的数据放在队列中

功能: 可以实现多个系统之间的解耦,异步,削峰/限流等
常用的消息队列应用: kafka,rabbitMQ,redis

消息队列主要分为两种,这两种模式Redis都支持

生产者/消费者模式
发布者/订阅者模式

生产者消费者模式

在生产者/消费者(Producer/Consumer)模式下,上层应用接收到的外部请求后开始处理其当前步骤的操
作,在执行完成后将已经完成的操作发送至指定的频道(channel,逻辑队列)当中,并由其下层的应用监听
该频道并继续下一步的操作,如果其处理完成后没有下一步的操作就直接返回数据给外部请求,如果还
有下一步的操作就再将任务发布到另外一个频道,由另外一个消费者继续监听和处理。此模式应用广泛

模式介绍

生产者消费者模式下,多个消费者同时监听一个队列,但是一个消息只能被最先抢到消息的消费者消
费,即消息任务是一次性读取和处理,此模式在分布式业务架构中很常用,比较常用的消息队列软件还有RabbitMQ、Kafka、RocketMQ、ActiveMQ等。

队列介绍

队列当中的消息由不同的生产者写入,也会有不同的消费者取出进行消费处理,但是一个消息一定是只能被取出一次也就是被消费一次。

生产者发布消息

[root@redis ~]# redis-cli
127.0.0.1:6379> AUTH 123456
OK
127.0.0.1:6379> LPUSH channel1 msg1 #从管道的左侧写入
(integer) 1
127.0.0.1:6379> LPUSH channel1 msg2
(integer) 2
127.0.0.1:6379> LPUSH channel1 msg3
(integer) 3
127.0.0.1:6379> LPUSH channel1 msg4
(integer) 4
127.0.0.1:6379> LPUSH channel1 msg5
(integer) 5

查看队列所有消息

127.0.0.1:6379> LRANGE channel1 0 -1
1) "msg5"
2) "msg4"
3) "msg3"
4) "msg2"
5) "msg1"

消费者消费消息

127.0.0.1:6379> RPOP channel1 #从管道的右侧消费,用于消息的先进先出
"msg1"
127.0.0.1:6379> RPOP channel1
"msg2"
127.0.0.1:6379> RPOP channel1
"msg3"
127.0.0.1:6379> RPOP channel1
"msg4"
127.0.0.1:6379> RPOP channel1
"msg5"
127.0.0.1:6379> RPOP channel1
(nil)

再次验证队列消息

127.0.0.1:6379> LRANGE channel1 0 -1
(empty list or set) #队列中的消息已经被已全部消费完毕

发布者订阅模式

模式简介

在发布者订阅者模式下,发布者将消息发布到指定的channel里面,凡是监听该channel的消费者都会收
到同样的一份消息,这种模式类似于是收音机的广播模式,即凡是收听某个频道的听众都会收到主持人发布的相同的消息内容。此模式常用语群聊天、群通知、群公告等场景

Publisher:发布者

Subscriber:订阅者

Channel:频道

订阅者监听频道
[root@redis ~]# redis-cli
127.0.0.1:6379> AUTH 123456
OK
127.0.0.1:6379> SUBSCRIBE channel1 #订阅者事先订阅指定的频道,之后发布的消息才
能收到
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel1"
3) (integer) 1

发布者发布消息

127.0.0.1:6379> PUBLISH channel1 test1 #发布者发布消息
(integer) 2 #订阅者个数
127.0.0.1:6379> PUBLISH channel1 test2
(integer) 2

各个订阅者都能收到消息

订阅多个频道

#订阅指定的多个频道
127.0.0.1:6379> SUBSCRIBE channel1 channel2

订阅所有频道

127.0.0.1:6379> PSUBSCRIBE * #支持通配符*

订阅匹配的频道

127.0.0.1:6379> PSUBSCRIBE chann* #匹配订阅多个频道

取消订阅

127.0.0.1:6379> unsubscribe channel1
1) "unsubscribe"
2) "channel1"
3) (integer) 0

redis 集群与高可用

虽然Redis可以实现单机的数据持久化,但无论是RDB也好或者AOF也好,都解决不了单点宕机问题,即
一旦单台 redis服务器本身出现系统故障、硬件故障等问题后,就会直接造成数据的丢失

此外,单机的性能也是有极限的,因此需要使用另外的技术来解决单点故障和性能扩展的问题。

redis 主从复制

redis 主从复制架构

主从模式(master/slave),可以实现Redis数据的跨主机备份。
程序端连接到高可用负载的VIP,然后连接到负载服务器设置的Redis后端real server,此模式不需要在
程序里面配 置Redis服务器的真实IP地址,当后期Redis服务器IP地址发生变更只需要更改redis 相应的
后端real server即可, 可避免更改程序中的IP地址设置。

主从复制特点

一个master可以有多个slave
一个slave只能有一个master
数据流向是从master到slave单向的

主从复制实现

Redis Slave 也要开启持久化并设置和master同样的连接密码,因为后期slave会有提升为master的可
能,Slave 端切换master同步后会丢失之前的所有数据,而通过持久化可以恢复数据
一旦某个Slave成为一个master的slave,Redis Slave服务会清空当前redis服务器上的所有数据并将
master的数据导入到自己的内存,但是如果只是断开同步关系后,则不会删除当前已经同步过的数据。

当配置Redis复制功能时,强烈建议打开主服务器的持久化功能。否则的话,由于延迟等问题,部署的主
节点Redis服务应该要避免自动启动。

参考案例: 导致主从服务器数据全部丢失

1.假设节点A为主服务器,并且关闭了持久化。并且节点B和节点c从节点A复制数据
2.节点A崩溃,然后由自动拉起服务重启了节点A.由于节点A的持久化被关闭了,所以重启之后没有任何数据
3.节点B和节点c将从节点A复制数据,但是A的数据是空的,于是就把自身保存的数据副本删除。

在关闭主服务器上的持久化,并同时开启自动拉起进程的情况下,即便使用Sentinel来实现Redis的高可
用性,也是非常危险的。因为主服务器可能拉起得非常快,以至于Sentinel在配置的心跳时间间隔内没
有检测到主服务器已被重启,然后还是会执行上面的数据丢失的流程。无论何时,数据安全都是极其重要的,所以应该禁止主服务器关闭持久化的同时自动启动。

命令行配置

启用主从同步

默认redis 状态为master,需要转换为slave角色并指向master服务器的IP+PORT+Password
在从节点执行REPLICAOF MASTER_IP PORT 指令可以启用主从同步复制功能,早期版本使用 SLAVEOF指令(注意防火墙问题Firewalld)

127.0.0.1:6379> REPLICAOF MASTER_IP PORT
127.0.0.1:6379> CONFIG SET masterauth <masterpass>

范例:

#在mater上设置key1
[root@centos8 ~]# redis-cli
127.0.0.1:6379> AUTH 123456
OK
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass 123456
OK
127.0.0.1:6379> config get requirepass
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth 123456
OK
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "123456"

127.0.0.1:6379> INFO replication
# Replication
role:master
connected_slaves:0
...
127.0.0.1:6379> SET key1 v1-master
OK
127.0.0.1:6379> KEYS *
1) "key1"
127.0.0.1:6379> GET key1
"v1-master"
127.0.0.1:6379>

范例:

#以下都在slave上执行,登录
[root@centos8 ~]# redis-cli
127.0.0.1:6379> info
NOAUTH Authentication required.
127.0.0.1:6379> AUTH 123456
OK
127.0.0.1:6379> INFO replication #查看当前角色默认为master
# Replication
role:master
connected_slaves:0
...
127.0.0.1:6379> SET key1 v1-slave-18
OK
127.0.0.1:6379> KEYS *
1) "key1"
127.0.0.1:6379> GET key1
"v1-slave-18"
127.0.0.1:6379>

#在第二个slave,也设置相同的key1,但值不同
127.0.0.1:6379> KEYS *
1) "key1"
127.0.0.1:6379> GET key1
"v1-slave-28"
127.0.0.1:6379>
127.0.0.1:6379> INFO replication
# Replication
role:master
...
#在slave上设置master的IP和端口,4.0版之前的指令为slaveof
127.0.0.1:6379> REPLICAOF 172.31.0.8 6379 #仍可使用SLAVEOF MasterIP Port
OK
#在slave上设置master的密码,才可以同步
127.0.0.1:6379> CONFIG SET masterauth 123456
OK
127.0.0.1:6379> INFO replication
# Replication #角色变为slave
role:slave
master_host:172.31.0.8 #指向master
master_port:6379
master_link_status:up

#查看已经同步成功
127.0.0.1:6379> GET key1
"v1-master"
#在master上可以看到所有slave信息
127.0.0.1:6379> INFO replication
# Replication
role:master
connected_slaves:2
slave0:ip=172.31.0.18,port=6379,state=online,offset=112,lag=1 #slave信息
slave1:ip=172.31.0.28,port=6379,state=online,offset=112,lag=1

删除主从同步

在从节点执行 REPLIATOF NO ONE 指令可以取消主从复制

#取消复制,在slave上执行REPLIATOF NO ONE,会断开和master的连接不再主从复制, 但不会清除slave
上已有的数据
127.0.0.1:6379> REPLICAOF no one

同步日志

在 master 上观察日志

[root@centos8 ~]# tail /var/log/redis/redis.log
24402:M 06 Oct 2021 09:09:16.448 * Replica 172.31.0.18:6379 asks for
synchronization

在 slave 节点观察日志

[root@centos8 ~]# tail -f /var/log/redis/redis.log
24395:S 06 Oct 2021 09:09:16.411 * Connecting to MASTER 172.31.0.8:6379
24395:S 06 Oct 2021 09:09:16.412 * MASTER <-> REPLICA sync started

修改slave节点配置文件

范例: (CentOS8 注意防火墙问题)

[root@centos8 ~]# vim /etc/redis.conf
.......
# replicaof <masterip> <masterport>
replicaof 172.31.0.8 6379 #指定master的IP和端口号
# If the master is password protected (using the "requirepass" configuration
# directive below) it is possible to tell the replica to authenticate before
# starting the replication synchronization process, otherwise the master will
# refuse the replica request.
# masterauth <master-password>
masterauth 123456 #如果密码需要设置
.......
[root@centos8 ~]# systemctl restart redis

master和slave查看状态

#在master上查看状态
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=172.31.0.18,port=6379,state=online,offset=1104403,lag=0

#在slave上查看状态
127.0.0.1:6379> get key1 #同步成功后,slave上的key信息丢失,从master复制过来新的值
"v1-master"
127.0.0.1:6379> INFO replication
# Replication
role:slave
master_host:172.31.0.8
master_port:6379
master_link_status:up
...

#停止master的redis服务:systemctl stop redis,在slave上可以观察到以下现象
127.0.0.1:6379> INFO replication
# Replication
role:slave
master_host:172.31.0.8
master_port:6379
master_link_status:down #显示down,表示无法连接master

slave 观察日志

[root@centos8 ~]# tail -f /var/log/redis/redis.log
24592:S 20 Feb 2021 12:03:58.792 * Connecting to MASTER 172.31.0.8:6379
24592:S 20 Feb 2021 12:03:58.792 * MASTER <-> REPLICA sync started

Master日志

[root@centos8 ~]# tail /var/log/redis/redis.log
11846:M 20 Feb 2021 12:11:35.171 * DB loaded from disk: 0.000 seconds
11846:M 20 Feb 2021 12:11:35.171 * Ready to accept connections

slave 状态只读无法写入数据

127.0.0.1:6379> set key1 v1-slave
(error) READONLY You can't write against a read only replica.

主从复制故障恢复

主从复制故障恢复过程介绍

slave 节点故障和恢复

当 slave 节点故障时,将Redis Client指向另一个 slave 节点即可,并及时修复故障从节点

master 节点故障和恢复

需要提升slave为新的master

master故障后,只能手动提升一个slave为新master,不支持自动切换。
之后将其它的slave节点重新指定新的master为master节点
Master的切换会导致master_replid发生变化,slave之前的master_replid就和当前master不一致从而会引发所有 slave的全量同步。

主从复制故障恢复实现

假设当前主节点172.31.0.8故障,提升172.31.0.18为新的master

127.0.0.1:6379> info replication
# Replication
role:slave
master_host:172.31.0.18
master_port:6379
master_link_status:up

停止slave同步并提升为新的master

#将当前 slave 节点提升为 master 角色
127.0.0.1:6379> REPLICAOF NO ONE #旧版使用SLAVEOF no one
OK
(5.04s)
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:0

测试能否写入数据:

127.0.0.1:6379> set keytest1 vtest1
OK

修改所有slave 指向新的master节点

#修改172.31.0.28节点指向新的master节点172.31.0.18
127.0.0.1:6379> SLAVEOF 172.31.0.8 6379
OK
127.0.0.1:6379> set key100 v100
(error) READONLY You can't write against a read only replica.
#查看日志
[root@centos8 ~]# tail -f /var/log/redis/redis.log
1762:S 20 Feb 2021 13:28:21.943 # Connection with master lost.
1762:S 20 Feb 2021 13:28:21.943 * Caching the disconnected master state.

在新master可看到slave

#在新master节点172.31.0.18上查看状态
127.0.0.1:6379> INFO replication
# Replication
role:master
connected_slaves:1
slave0:ip=172.31.0.28,port=6379,state=online,offset=4606,lag=0
...

实现 redis 的级联复制

即实现基于Slave节点的Slave
master和slave1节点无需修改,只需要修改slave2及slave3指向slave1做为mater即可

#在slave2和slave3上执行下面指令
127.0.0.1:6379> REPLICAOF 172.31.0.18 6379
OK
127.0.0.1:6379> CONFIG SET masterauth 123456

在 master 设置key,观察是否同步

#在master新建key
127.0.0.1:6379> set key2 v2
OK
127.0.0.1:6379> get key2
"v2"
#在slave1和slave2验证key
127.0.0.1:6379> get key2
"v2"
#在slave1和slave2都无法新建key
127.0.0.1:6379> set key3 v3
(error) READONLY You can't write against a read only replica.

在中间那个slave查看状态

127.0.0.1:6379> INFO replication
# Replication
role:slave
master_host:172.31.0.8
master_port:6379
master_link_status:up
master_last_io_seconds_ago:8 #最近一次与master通信已经过去多少秒。
master_sync_in_progress:0 #是否正在与master通信。
slave_repl_offset:4312 #当前同步的偏移量
slave_priority:100 #slave优先级,master故障后值越小越优先同步。
slave_read_only:1
connected_slaves:1
slave0:ip=172.31.0.28,port=6379,state=online,offset=4312,lag=0 #slave的slave节点

主从复制优化

主从复制过程

Redis主从复制分为全量同步和增量同步

首次主从同步是全量同步,主从同步可以让从服务器从主服务器同步数据,而且从服务器还可再有其它
的从服务器,即另外一台redis服务器可以从一台从服务器进行数据同步,redis 的主从同步是非阻塞
的,master收到从服务器的psync(2.8版本之前是SYNC)命令,会fork一个子进程在后台执行bgsave命
令,并将新写入的数据写入到一个缓冲区中,bgsave执行完成之后,将生成的RDB文件发送给slave,然
后master再将缓冲区的内容以redis协议格式再全部发送给slave,slave 先删除旧数据,slave将收到后的
RDB文件载入自己的内存,再加载所有收到缓冲区的内容 从而这样一次完整的数据同步

Redis全量复制一般发生在Slave首次初始化阶段,这时Slave需要将Master上的所有数据都复制一份。

全量同步之后再次需要同步时,从服务器只要发送当前的offset位置(等同于MySQL的binlog的位置)给主
服务器,然后主服务器根据相应的位置将之后的数据(包括写在缓冲区的积压数据)发送给从服务器,再次
将其保存到从节点内存即可。
主从同步完整过程

具体主从同步过程如下:

1)从服务器连接主服务器,发送PSYNC命令
2)主服务器接收到PSYNC命令后,开始执行BGSAVE命令生成RDB快照文件并使用缓冲区记录此后执行的所有
写命令
3)主服务器BGSAVE执行完后,向所有从服务器发送RDB快照文件,并在发送期间继续记录被执行的写命令
4)从服务器收到快照文件后丢弃所有旧数据,载入收到的快照至内存
5)主服务器快照发送完毕后,开始向从服务器发送缓冲区中的写命令
6)从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令
7)后期同步会先发送自己slave_repl_offset位置,只同步新增加的数据,不再全量同步

复制缓冲区(环形队列)配置参数:

#复制缓冲区大小,建议要设置足够大
repl-backlog-size 1mb
#Redis同时也提供了当没有slave需要同步的时候,多久可以释放环形队列:
repl-backlog-ttl 3600

避免全量复制

第一次全量复制不可避免,后续的全量复制可以利用小主节点(内存小),业务低峰时进行全量
节点运行ID不匹配:主节点重启会导致RUNID变化,可能会触发全量复制,可以利用故障转移,例如哨
兵或集群,而从节点重启动,不会导致全量复制
复制积压缓冲区不足: 当主节点生成的新数据大于缓冲区大小,从节点恢复和主节点连接后,会导致全量复制.解决方法将repl-backlog-size 调大

避免复制风暴

单主节点复制风暴
当主节点重启,多从节点复制
解决方法:更换复制拓扑

单机器多实例复制风暴
机器宕机后,大量全量复制
解决方法:主节点分散多机器

主从同步优化配置

Redis在2.8版本之前没有提供增量部分复制的功能,当网络闪断或者slave Redis重启之后会导致主从之间的全量同步,即从2.8版本开始增加了部分复制的功能。

性能相关配置
repl-diskless-sync no # 是否使用无盘同步RDB文件,默认为no,no为不使用无盘,需要将RDB文件保
存到磁盘后再发送给slave,yes为支持无盘,支持无盘就是RDB文件不需要保存至本地磁盘,而且直接通过
socket文件发送给slave
repl-diskless-sync-delay 5 #diskless时复制的服务器等待的延迟时间
repl-ping-slave-period 10 #slave端向server端发送ping的时间间隔,默认为10秒
repl-timeout 60 #设置主从ping连接超时时间,超过此值无法连接,master_link_status显示为down,
并记录错误日志
repl-disable-tcp-nodelay no #是否启用TCP_NODELAY,如设置成yes,则redis会合并小的TCP包从
而节省带宽, 但会增加同步延迟(40ms),造成master与slave数据不一致,假如设置成no,则redis
master会立即发送同步数据,没有延迟,yes关注网络性能,no关注redis服务中的数据一致性
repl-backlog-size 1mb #master的写入数据缓冲区,用于记录自上一次同步后到下一次同步过程中间的
写入命令,计算公式:repl-backlog-size = 允许从节点最大中断时长 * 主实例offset每秒写入量,比
如master每秒最大写入64mb,最大允许60秒,那么就要设置为64mb*60秒=3840MB(3.8G),建议此值是设
置的足够大
repl-backlog-ttl 3600 #如果一段时间后没有slave连接到master,则backlog size的内存将会被释
放。如果值为0则 表示永远不释放这部份内存。
slave-priority 100 #slave端的优先级设置,值是一个整数,数字越小表示优先级越高。当master故障
时将会按照优先级来选择slave端进行恢复,如果值设置为0,则表示该slave永远不会被选择。
min-replicas-to-write 1 #设置一个master的可用slave不能少于多少个,否则master无法执行写
min-slaves-max-lag 20 #设置至少有上面数量的slave延迟时间都大于多少秒时,master不接收写操作(拒绝写入)

常见主从复制故障汇总

master密码不对

即配置的master密码不对,导致验证不通过而无法建立主从同步关系。

[root@centos8 ~]# tail -f /var/log/redis/redis.log
24930:S 20 Feb 2021 13:53:57.029 * Connecting to MASTER 172.31.0.8:6379
24930:S 20 Feb 2021 13:53:57.030 * MASTER <-> REPLICA sync started
24930:S 20 Feb 2021 13:53:57.030 * Non blocking connect for SYNC fired the
event.
24930:S 20 Feb 2021 13:53:57.030 * Master replied to PING, replication can
continue...
24930:S 20 Feb 2021 13:53:57.031 # Unable to AUTH to MASTER: -ERR invalid password
Redis版本不一致

不同的redis 大版本之间存在兼容性问题,比如:3和4,4和5之间,因此各master和slave之间必须保持版本一致

无法远程连接

在开启了安全模式情况下,没有设置bind地址或者密码

[root@centos8 ~]# vim /etc/redis.conf
#bind 127.0.0.1 #将此行注释

[root@centos8 ~]# systemctl restart redis
[root@centos8 ~]# ss -ntl

#可以本机登录
[root@centos8 ~]# redis-cli
127.0.0.1:6379> KEYS *
(empty list or set)
配置不一致

主从节点的maxmemory不一致,主节点内存大于从节点内存,主从复制可能丢失数据
rename-command 命令不一致,如在主节点定义了fushall,flushdb,从节点没定义,结果执行flushdb,不同步

#master有一个rename-command flushdb "king",而slave没有这个配置,则同步时从节点可以看到
以下同步错误
3181:S 21 Oct 2021 17:34:50.581 # == CRITICAL == This replica is sending an
error to its master: 'unknown command `king`, with args beginning with: ' after processing the command '<unknown>'
上一篇:Redis 集群主要特性&原理复习


下一篇:《C语言深度剖析》第五章 内存管理 p1(完结)( C语言从入门到入土(进阶篇)