文章目录
一、redis主从复制
虽然Redis可以实现单机的数据持久化,但无论是RDB也好或者AOF也好,都解决不了单点宕机问题,即一旦单台 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复制功能时,强烈建议打开主服务器的持久化功能。否则的话,由于延迟等问题,部署的服务应该要避免自动启动。
- 假设节点A为主服务器,并且关闭了持久化。并且节点B和节点c从节点A复制数据
- 节点A崩溃,然后由自动拉起服务重启了节点A.由于节点A的持久化被关闭了,所以重启之后没有任何数据
- 节点B和节点c将从节点A复制数据,但是A的数据是空的,于是就把自身保存的数据副本删除。在关闭主服务器上的持久化,并同时开启自动拉起进程的情况下,即便使用Sentinel来实现Redis的高可用性,也是非常危险的。因为主服务器可能拉起得非常快,以至于Sentinel在配置的心跳时间间隔内没有检测到主服务器已被重启,然后还是会执行上面的数据丢失的流程。无论何时,数据安全都是极其重要的,所以应该禁止主服务器关闭持久化的同时自动启动。
命令行配置
需要两台机子,克隆一个,更改IP,
使用一键按照redis的脚本安装
- 启用主从同步
- 默认redis 状态为master,需要转换为slave角色并指向master服务器的IP+PORT+Password
- REPLICAOF MASTER_IP PORT 指令可以启用主从同步复制功能,早期版本使用 SLAVEOF 指令
- 在master上设置key1
[root@master ~]# redis-cli -a centos
Warning: Using a password with '-a' or '-u' option on the command line
interface may not be safe.
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:0
master_replid:75166c08ad870d6eaa5c27e003bc2b36488a3cae
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
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"
- 以下都在slave上执行,登录,设置key1
[root@slave1 ~]# redis-cli -a centos --no-auth-warning
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:0
master_replid:1a274a8475450314d9ebb232717fa6cc3720e62f
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
127.0.0.1:6379> set key1 v1-slave1
OK
127.0.0.1:6379> keys *
1) "key1"
127.0.0.1:6379> get key1
"v1-slave1"
- 在第二个slave2上,也设置相同的key1,但值不同
[root@slave2 ~]# redis-cli -a centos --no-auth-warning
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:0
master_replid:2b8d846c9f01eea950100d75caf1e8e3600a8d4a
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
127.0.0.1:6379> set key1 v1-slave2
OK
127.0.0.1:6379> keys *
1) "key1"
127.0.0.1:6379> get key1
"v1-slave2"
- 在所有的slave上设置master的IP和端口,4.0版本之前的指令为slaveof
- 在slave1上
127.0.0.1:6379> replicaof 192.168.64.129 6379
# 仍可使用SLAVEOF MasterIP Port
OK
127.0.0.1:6379> config set masterauth centos
#在slave上设置master的密码,才可以同步
OK
127.0.0.1:6379> info replication
# Replication
role:slave #角色变为slave
master_host:192.168.64.129 #指向master
master_port:6379
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_repl_offset:0
master_link_down_since_seconds:1625794777
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:1a274a8475450314d9ebb232717fa6cc3720e62f
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
127.0.0.1:6379> get key1
# 查看数据是否同步成功
"v1-master"
* 在slave2上
127.0.0.1:6379> replicaof 192.168.64.129 6379
OK
127.0.0.1:6379> config set masterauth centos
OK
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:192.168.64.129
master_port:6379
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_repl_offset:0
master_link_down_since_seconds:1625794971
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:2b8d846c9f01eea950100d75caf1e8e3600a8d4a
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
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=192.168.175.30,port=6379,state=online,offset=224,lag=1
slave1:ip=192.168.175.20,port=6379,state=online,offset=224,lag=0
master_replid:d6f1142f22e456f4e4093fcb81648cab127ebc7f
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:224
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:224
- 删除主从同步
- REPLICAOF NO ONE 指令可以取消主从复制
在从节点上执行
127.0.0.1:6379> replicaof no one
OK
127.0.0.1:6379> info replication
# Replication
role:master #角色变回了master
connected_slaves:0
master_replid:9507932f3addde3a76b51c5d6ecbb5daac398caa
master_replid2:f68659a5c6e8381dbbdd6841f5b89d1a60157af8
master_repl_offset:1064
second_repl_offset:1065
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:1064
- 在master上看到slave数量减少
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1
同步日志
- 在 master 上观察日志
[root@master ~]# tail /apps/redis/log/redis_6379.log
6815:M 09 Jul 2021 09:44:03.398 * Synchronization with replica
192.168.175.20:6379 succeeded
6815:M 09 Jul 2021 09:53:26.312 # Connection with replica
192.168.175.20:6379 lost.
6815:M 09 Jul 2021 09:54:04.298 * Replica 192.168.175.20:6379 asks for
synchronization
6815:M 09 Jul 2021 09:54:04.298 * Partial resynchronization not accepted:
Replication ID mismatch (Replica asked for
'2feb7cc9c49ff2a52f4749a0cd26218d9ca6577a', my replication IDs are
'd6f1142f22e456f4e4093fcb81648cab127ebc7f' and
'0000000000000000000000000000000000000000')
6815:M 09 Jul 2021 09:54:04.298 * Starting BGSAVE for SYNC with target: disk
6815:M 09 Jul 2021 09:54:04.299 * Background saving started by pid 7234
7234:C 09 Jul 2021 09:54:04.300 * DB saved on disk
7234:C 09 Jul 2021 09:54:04.300 * RDB: 0 MB of memory used by copy-on-write
6815:M 09 Jul 2021 09:54:04.394 * Background saving terminated with success
6815:M 09 Jul 2021 09:54:04.395 * Synchronization with replica
192.168.175.20:6379 succeeded
- 在 slave 节点观察日志
[root@slave2 ~]# tail /apps/redis/log/redis_6379.log
6838:S 09 Jul 2021 09:44:02.914 * MASTER <-> REPLICA sync started
6838:S 09 Jul 2021 09:44:02.914 * Non blocking connect for SYNC fired the
event.
6838:S 09 Jul 2021 09:44:02.915 * Master replied to PING, replication can
continue...
6838:S 09 Jul 2021 09:44:02.916 * Trying a partial resynchronization
(request 2b8d846c9f01eea950100d75caf1e8e3600a8d4a:1).
6838:S 09 Jul 2021 09:44:02.918 * Full resync from master:
d6f1142f22e456f4e4093fcb81648cab127ebc7f:0
6838:S 09 Jul 2021 09:44:02.918 * Discarding previously cached master state.
6838:S 09 Jul 2021 09:44:02.992 * MASTER <-> REPLICA sync: receiving 196
bytes from master
6838:S 09 Jul 2021 09:44:02.993 * MASTER <-> REPLICA sync: Flushing old data
6838:S 09 Jul 2021 09:44:02.993 * MASTER <-> REPLICA sync: Loading DB in
memory
6838:S 09 Jul 2021 09:44:02.993 * MASTER <-> REPLICA sync: Finished with
success
修改slave节点配置文件
- 在slave1上
[root@slave1 ~]# grep replicaof /apps/redis/etc/redis.conf
# Master-Replica replication. Use replicaof to make a Redis instance a copy of
# replicaof <masterip> <masterport>
[root@slave1 ~]# echo "replicaof 192.168.64.129 6379" >> /apps/redis/etc/redis.conf
[root@slave1 ~]# echo "masterauth centos" >> /apps/redis/etc/redis.conf
[root@slave1 ~]# systemctl restart redis
- 在slave2上
[root@slave2 ~]# echo "replicaof 192.168.64.129 6379" >> /apps/redis/etc/redis.conf
[root@slave2 ~]# echo "masterauth centos" >> /apps/redis/etc/redis.conf
[root@slave2 ~]# systemctl restart redis
- master和slave查看状态
# 在master上
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=192.168.64.134,port=6379,state=online,offset=4452,lag=1
slave1:ip=192.168.64.135,port=6379,state=online,offset=4452,lag=1
# 在slave1上
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:192.168.64.129
master_port:6379
127.0.0.1:6379> get key1
"v1-master"
#在slave2上
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:192.168.64.129
master_port:6379
127.0.0.1:6379> get key1
"v1-master"
- 停止master的redis服务:systemctl stop redis,在slave上可以看到以下现象
# 在master上停止服务
[root@master ~]# systemctl stop redis
#在slave上可以看到
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:192.168.175.10
master_port:6379
master_link_status:down #显示down,表示无法连接master
- slave 观察日志
[root@slave1 ~]# tail /apps/redis/log/redis_6379.log
15506:S 09 Jul 2021 10:41:56.295 # Error condition on socket for SYNC:
Connection refused
15506:S 09 Jul 2021 10:41:57.311 * Connecting to MASTER 192.168.175.10:6379
15506:S 09 Jul 2021 10:41:57.311 * MASTER <-> REPLICA sync started
15506:S 09 Jul 2021 10:41:57.311 # Error condition on socket for SYNC:
Connection refused
15506:S 09 Jul 2021 10:41:58.333 * Connecting to MASTER 192.168.175.10:6379
15506:S 09 Jul 2021 10:41:58.333 * MASTER <-> REPLICA sync started
15506:S 09 Jul 2021 10:41:58.334 # Error condition on socket for SYNC:
Connection refused
15506:S 09 Jul 2021 10:41:59.359 * Connecting to MASTER 192.168.175.10:6379
15506:S 09 Jul 2021 10:41:59.359 * MASTER <-> REPLICA sync started
15506:S 09 Jul 2021 10:41:59.360 # Error condition on socket for SYNC:
Connection refused
- slave 状态只读无法写入数据
127.0.0.1:6379> set key1 v1-slave1
(error) READONLY You can't write against a read only replica.
主从复制故障恢复
主从复制故障恢复过程介绍
- slave 节点故障和恢复
- client指向另一个从节点即可,并及时修复故障从节点
- master 节点故障和恢复
- 需要提升slave为新的master
- master故障后,只能手动提升一个slave为新master,不支持自动切换。master的切换会导致master_replid发生变化,slave之前的master_replid就和当前master不一致从而会引发所有slave的全量同步
主从复制故障恢复实现
- 假设当前主节点192.168.175.10故障,提升192.168.175.20为新的master
[root@master ~]# systemctl stop redis
- 查看slave1
[root@slave1 ~]# redis-cli -a centos --no-auth-warning
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:192.168.175.10
master_port:6379
master_link_status:down
- 停止slave1(192.168.175.20)同步并提升为新的master
127.0.0.1:6379> replicaof no one
OK
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:0
master_replid:46804bd951a74f28dbd453d4cab68ca6fbd2e6bc
master_replid2:d6f1142f22e456f4e4093fcb81648cab127ebc7f
master_repl_offset:4648
second_repl_offset:4649
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:827
repl_backlog_histlen:3822
127.0.0.1:6379> set keytest1 vtest1
OK
- 修改所有的slave指向新的master节点
127.0.0.1:6379> replicaof 192.168.175.20 6379
OK
127.0.0.1:6379> config set masterauth centos
OK
127.0.0.1:6379> set key100 v100
(error) READONLY You can't write against a read only replica.
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:192.168.175.20
master_port:6379
master_link_status:up
127.0.0.1:6379> get keytest1
"vtest1"
- 在新master(192.168.175.20)上可看到slave
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=192.168.175.30,port=6379,state=online,offset=4836,lag=1
实现redis的级联复制
在前面搭建好的一主一从架构中,master和slave1节点无需修改,只需要修改slave2及slave3指向slave1作为master即可
[root@slave2 ~]# redis-cli -a centos
Warning: Using a password with '-a' or '-u' option on the command line
interface may not be safe.
127.0.0.1:6379> REPLICAOF 192.168.175.20 6379
OK
127.0.0.1:6379> config set masterauth centos
OK
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:192.168.175.20
master_port:6379
[root@slave3 ~]# redis-cli -a centos
Warning: Using a password with '-a' or '-u' option on the command line
interface may not be safe.
127.0.0.1:6379> replicaof 192.168.175.20 6379
OK
127.0.0.1:6379> config set masterauth centos
OK
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:192.168.175.20
master_port:6379
- 在master上设置key,观察是否同步
# master设置key
127.0.0.1:6379> set name eagle
OK
127.0.0.1:6379> get name
"eagle"
#在slave上验证key
127.0.0.1:6379> get name
"eagle"
- 在中间那个slave(即级联salve1 192.168.175.20)上查看状态
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:192.168.175.10
master_port:6379
master_link_status:up
master_last_io_seconds_ago:9 #最近一次与master通信已经过去多少秒
master_sync_in_progress:0 #是否正在与master通信
slave_repl_offset:799 #当前同步的偏移量
slave_priority:100 #slave优先级,master故障后优先级值越小越优先同步
slave_read_only:1
connected_slaves:2
slave0:ip=192.168.175.30,port=6379,state=online,offset=799,lag=0
slave1:ip=192.168.175.40,port=6379,state=online,offset=799,lag=0
master_replid:e262f6e6fdd1d02222afc48ade1860163e3c096b
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:799
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:799
主从复制优化
主从复制过程
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的位置)给主服务器,然后主服务器根据相应的位置将之后的数据(包括写在缓冲区的积压数据)发送给从服务器,其再次保存到其内存即可。
- 主从同步完整过程
- 从服务器连接主服务器,发送PSYNC命令
- 主服务器接收到PSYNC命令后,开始执行BGSAVE命令生成RDB快照文件并使用缓冲区记录此后执行的所有写命令
- 主服务器BGSAVE执行完后,向所有从服务器发送RDB快照文件,并在发送期间继续记录被执行的写命令
- 从服务器收到快照文件后丢弃所有旧数据,载入收到的快照至内存
- 主服务器快照发送完毕后,开始向从服务器发送缓冲区中的写命令
- 从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令
- 后期同步会先发送自己slave_repl_offset位置,只同步新增加的数据,不再全量同步
- 复制缓冲区(环形队列)配置参数
#复制缓冲区大小,建议要设置足够大
rep-backlog-size 1mb
#Redis同时也提供了当没有slave需要同步的时候,多久可以释放环形队列:
repl-backlog-ttl 3600
-
避免全量复制
- 第一次全量复制不可避免,后续的全量复制可以利用小主节点(内存小),业务低峰时进行全量
- 节点运行 run-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,则redismaster会立即发送同步数据,没有延迟,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@slave1 ~]#tail -f /var/log/redis/redis.log
3939:S 24 Oct 2020 16:13:57.552 # Server initialized
3939:S 24 Oct 2020 16:13:57.552 * DB loaded from disk: 0.000 seconds
3939:S 24 Oct 2020 16:13:57.552 * Ready to accept connections
3939:S 24 Oct 2020 16:13:57.554 * Connecting to MASTER 10.0.0.18:6379
3939:S 24 Oct 2020 16:13:57.554 * MASTER <-> REPLICA sync started
3939:S 24 Oct 2020 16:13:57.556 * Non blocking connect for SYNC fired the event.
3939:S 24 Oct 2020 16:13:57.558 * Master replied to PING, replication can continue...
3939:S 24 Oct 2020 16:13:57.559 # Unable to AUTH to MASTER: -ERR invalid password
Redis版本不一致
不同的redis 大版本之间存在兼容性问题,比如:3和4,4和5之间,因此各master和slave之间必须保持版本一致
无法远程连接
在开启了安全模式情况下,没有设置bind地址或者密码
[root@slave1 ~]#redis-cli -h 192.168.175.10
192.168.175.10:6379> KEYS *
(error) DENIED Redis is running in protected mode because protected mode is enabled
配置不一致
主从节点的maxmemory不一致,主节点内存大于从节点内存,主从复制可能丢失数据
rename-command 命令不一致,如在主节点定义了fushall,flushdb,从节点没定义,结果执行flushdb,不同步