9. Redis高可用集群
虽然redis可以实现单机的数据持久化, 但无论是AOF还是RDB, 都解决不了单点宕机的问题
即一旦单台redis服务器本身出现系统故障, 硬件故障等问题后, 就会直接造成数据的丢失
此外, 单机的性能也是有极限的, 因此需要使用另外的技术来解决单点故障和性能扩展的问题
9.1 主从复制架构
主从模式(master/slave), 可以实现Redis数据的跨主机备份, 主节点将自己的rdb文件同步给从节点
程序端连接到高可用负载的vip(如LVS), 然后连接到负载服务器设置的Redis后端的real server, 此模式不需要在程序里面配置Redis服务器的真实ip地址, 当后期Redis服务器ip地址发生变化, 只需要更改Redis相应的后端real server即可, 可避免更改程序中的ip地址设置
以上功能需要配合应用程序实现redis的读写分离, 通过负载均衡和vip, 将写操作发往master节点, 读操作发往slave节点
主从复制特点:
- 一个master可以有多个slave
- 一个slave只能有一个master
- 数据流向是单向的, master到slave
9.2 主从复制实现
Redis slave也要开启持久化并设置和master同样的密码, 因为后期slave也有提升为master的可能, 因此要确保主从的密码统一, 同时也要确保主从服务器的配置统一, 包括服务器硬件配置和配置文件包含的最大内存, 最大客户端连接数等
注意: 一旦某个redis服务器成为了一个maste的slave, 那么Redis slave服务会清空当前Redis服务器上的所有数据并将master的数据导入到自己的内存, 因此, 如果把已经运行的redis服务器添加到主从架构前,一定要确保其现有的数据是无用的, 如果有用, 要先关闭redis服务, 把rdb文件和aof文件进行备份, 然后再添加到主从; 但是如果只是断开同步关系的话, 则不会删除当前已经同步过的数据
当配置Redis复制功能时, 强烈建议打开主服务器的持久化功能(rdb和aof). 否则的话, 由于延迟等问题, 部署的主服务器Redis应该避免服务自动重启, 如果主服务器没有配置持久化, 并且没有禁止自动重启, 那么一旦主服务器发生故障服务重启后, 会造成主从服务器数据全部丢失, 因为一旦主服务器redis重启, 主从同步正常后, 主节点此时已经没有数据了, 那么会把空的数据复制到从节点, 把从节点也清空了
虽然redis哨兵可以在主节点故障时, 自动提升一个从节点为主节点, 不会因为原先的主节点没有做持久化并且自动重启把数据清空, 但是最好还是要在主节点开启rdb和aof
案例:
1. 假设节点A为主服务器, 并且关闭了持久化; 同时节点B和节点C从节点A复制数据
2. 节点A崩溃, 然后由自动拉起服务重启了节点A, 由于节点A没有开启持久化, 所以重启之后节点A是没有任何数据的
3. 节点B和节点C将从节点A复制数据, 但是A的数据是空的, 于是自身保存的数据也会被清空
在关闭主服务器上的持久化, 并同时开启自动拉起进程的情况下. 即便使用哨兵来实现Redis的高可用, 也是非常危险的. 因为主服务器可能拉起得非常快, 以至于哨兵在配置的心跳时间间隔内没来得及检测到主服务器已被重启, 然后还是会执行上面的数据丢失的流程. 无论何时, 数据安全都是及其重要的, 所以应该禁止主服务器关闭持久化的同时自动重启Redis服务.
9.3 主从复制的配置
Redis主从复制可以进行临时实现和永久保存. 临时实现只需在从服务器上执行replicaof命令即可, 如果想持久保存, 需要写入配置文件
默认情况下, 每个redis服务器的状态都是master, 需要转换成slave角色, 并且配置指向master服务器的ip,端口和master的密码
[02:08:41 root@redis-slave ~]#redis-cli
127.0.0.1:6379> info replication
# Replication
role:master # 默认都是master节点
connected_slaves:0 # 本master节点有几个slave节点
master_replid:b70842a993908df24d5b6c58b7f3a79578020113
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
9.3.1 命令行实现主从复制
具体步骤:
准备环境:
- 一台主服务器(10.0.0.82), 配置bind 0.0.0.0, 密码为redis(在配置文件修改, 因为之后也需要实现主从的持久保存并且redis肯定都是要有密码的, 而且是在配置文件中添加, 如果只是测试环境, 想临时搭建主从, 那么可以直接在主从的命令行执行config set requirepass 来设定密码)
# bind指令不支持config set临时修改
vim /etc/redis.conf
bind 127.0.0.1 --> bind 0.0.0.0
systemctl restart redis
- 一台从服务器(10.0.0.83), 配置bind 0.0.0.0, 密码为redis, masterauth为redis(如果只是在命令行配置主从, 那么无需在配置文件修改masterauth)
# bind指令不支持config set临时修改
vim /etc/redis.conf
bind 127.0.0.1 --> bind 0.0.0.0
systemctl restart redis
确保主从服务器的防火墙都是禁用的, 否则主从无法建立连接
配置slave服务器和master服务器
127.0.0.1:6379> replicaof 10.0.0.82 6379 #指定主节点ip和端口号
127.0.0.1:6379> config set masterauth redis #指定主节点密码
127.0.0.1:6379> config set requirepass redis #指定从节点本地redis的密码
127.0.0.1:6379> config set requirepass redis # 指定主节点本地redis的密码
- 在主从节点分别查看replication信息
主:
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1 #显示当前有一个从节点
从:
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:10.0.0.82 #当前主节点信息
master_port:6379
master_link_status:up # 主节点是up状态
# 主节点原有数据
127.0.0.1:6379> keys *
1) "channel1"
2) "9528"
3) "9527"
# 从节点也会同步过来
127.0.0.1:6379> keys *
1) "channel1"
2) "9528"
3) "9527"
- 主节点增加数据, 验证从节点可以同步
#主:
127.0.0.1:6379> set master redis
OK
#从:
127.0.0.1:6379> get master
"redis"
- 查看主节点redis日志
#主:
9913:M 20 Oct 2020 16:44:11.899 * Synchronization with replica 10.0.0.83:6379 succeeded
#从节点自从添加到集群, 直到接收到新的namekey时的日志
[02:24:39 root@redis-slave ~]#tail /var/log/redis/redis.log
1883:S 07 Mar 2021 02:18:11.053 * MASTER <-> REPLICA sync started # 添加到主从, 开启同步
1883:S 07 Mar 2021 02:18:11.056 * Non blocking connect for SYNC fired the event.
1883:S 07 Mar 2021 02:18:11.059 * Master replied to PING, replication can continue... # 探测主节点存活, 使用ping
1883:S 07 Mar 2021 02:18:11.064 * Trying a partial resynchronization (request 171d35a785ef1ef6ce9bbcdd2427dd80e1cbf50c:1). # 部分复制
1883:S 07 Mar 2021 02:18:11.077 * Full resync from master: 801410d76e1b3ae2f40910fff77b773d0014964a:0 # master_replid_id, master的复制id
1883:S 07 Mar 2021 02:18:11.078 * Discarding previously cached master state. # 删除此前master的缓存状态
1883:S 07 Mar 2021 02:18:11.215 * MASTER <-> REPLICA sync: receiving 274 bytes from master # 从新的master接收数据
1883:S 07 Mar 2021 02:18:11.215 * MASTER <-> REPLICA sync: Flushing old data # 将旧的数据清空
1883:S 07 Mar 2021 02:18:11.215 * MASTER <-> REPLICA sync: Loading DB in memory # 载入主节点rdb文件内容
1883:S 07 Mar 2021 02:18:11.215 * MASTER <-> REPLICA sync: Finished with success # 同步成功
- 删除主从同步
在从节点执行, relicatof no one
127.0.0.1:6379> replicaof no one
OK
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:0
查看从节点日志
1883:M 07 Mar 2021 02:29:25.124 # Setting secondary replication ID to 801410d76e1b3ae2f40910fff77b773d0014964a, valid up to offset: 997. New replication ID is e3d960e7d97aa5f4a781ff4f0eb10b2ccdd1551b
1883:M 07 Mar 2021 02:29:25.124 # Connection with master lost.
1883:M 07 Mar 2021 02:29:25.124 * Caching the disconnected master state.
1883:M 07 Mar 2021 02:29:25.125 * Discarding previously cached master state.
1883:M 07 Mar 2021 02:29:25.125 * MASTER MODE enabled (user request from 'id=3 addr=127.0.0.1:37906 fd=7 name= age=724 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=36 qbuf-free=32732 obl=0 oll=0 omem=0 events=r cmd=replicaof')
查看主节点replication字段
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:0 # slave节点数变为0
查看主节点日志
2351:M 07 Mar 2021 02:29:28.222 # Connection with replica 10.0.0.83:6379 lost.
主从注意事项:
从主变成从, 那么原有的内存中的数据会被清除
从从变成主, 原有的数据还会保留
主从服务器的配置文件一定要一致, 否则会造成不同步等一系列问题
主服务器端设置密码 requirepass
从服务器端设置密码 masterauth, 是master的Redis密码, 而不是本机Redis的密码.
此外, 从服务器也要设置requirepass密码, 并且和主节点相同, 因为从节点有可能会被提升为主节点, 通过主节点的masterauth也要指向从节点redis的密码
- slave节点是无法写数据的, redis的主从, 从节点默认就是无法写数据
127.0.0.1:6379> set gender male
(error) READONLY You can't write against a read only replica.
从节点首次加入到集群时, 会对master执行ping命令, 如果master能回复, 那么就会开始从master同步, 认为master存活可连接, master节点会执行bgsave, 把rdb文件传给从节点, 之后的主从连接需要靠监控手段去监控, 因为默认的主从架构是不会监控主节点状态的, 包括主从的延时也需要进行监控
初次完成rdb同步后, 主节点每次的数据修改会复制给从节点, 从节点上的rdb保存策略是由从节点配置决定. 也就是说主从只是把主节点的数据修改发给从节点, 而各自己rdb持久化策略互不影响
从节点会每隔10秒(配置文件中可以指定:repl-ping-slave-period 10), 探测主节点健康性, 如果异常, 则同步会失败, 可以通过replication字段的参数查看探测间隔
# 从节点执行info replication查看
master_last_io_seconds_ago:6
- 主从节点如果开启了aof, 那么主节点的写入数据会同步rdb同步给从节点, 并且按照rdb保存策略存到rdb文件, 另外增量信息也会写入从节点的aof文件
9.3.2 持久保存主从复制
步骤:
- 先停止从服务器的redis服务, 把从服务器上之前留下的的aof和rdb文件清空
- 修改从服务器配置文件, 指定master服务器的密码
[root@slave-1 ~]# grep masterauth /etc/redis.conf
masterauth redis
appendonly yes
- 修改从服务器配置文件, 指定master服务器的ip和端口号
[root@slave-1 ~]# grep 10.0.0.82 /etc/redis.conf
replicaof 10.0.0.82 6379
- 启动redis服务, 验证配置
#主
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=10.0.0.152,port=6379,state=online,offset=1709,lag=1
#从
# Replication
role:slave
master_host:10.0.0.82
master_port:6379
master_link_status:up
- 测试同步成功
#主
127.0.0.1:6379> set hahaha lalala
OK
#从
127.0.0.1:6379> get hahaha
"lalala"
- 查看从节点同步信息
offset
master_sync_in_progress:0 # 表示有多少数据正在同步, 0 就表示没有正在同步的数据
slave_repl_offset:1274 #当前同步的偏移量, 类似MySQL二进制日志的位置信息. 一般只要和master_repl_offset保持一致, 即表明同步成功.
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:d9b92e71240244d6b7e69d44bdd92b6d2a99fdd9 #master_replid是从节点与主节点同步时, 用来识别主节点的
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:1274 #要和slave_repl_offset:1274保持一致才说明同步正常
主节点同步信息
offset
role:master
connected_slaves:1
slave0:ip=10.0.0.152,port=6379,state=online,offset=3068,lag=1 #和master_repl_offset数值一致, 同步成功
master_replid:d9b92e71240244d6b7e69d44bdd92b6d2a99fdd9
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:3068 #要和slave0:ip=10.0.0.152,port=6379,state=online,offset=3068,lag=1中的offset一致, 表面同步成功
9.3.3 实现一主多从
只需添加从节点,指向和主节点同步, 设置主节点密码即可
10.0.0.84
replicaof 10.0.0.82 6379
masterauth redis
9974:S 22 Oct 2020 18:06:37.817 * Connecting to MASTER 10.0.0.82:6379
9974:S 22 Oct 2020 18:06:37.817 * MASTER <-> REPLICA sync started
9974:S 22 Oct 2020 18:06:37.827 * Non blocking connect for SYNC fired the event.
9974:S 22 Oct 2020 18:06:37.828 * Master replied to PING, replication can continue...
9974:S 22 Oct 2020 18:06:37.830 * Partial resynchronization not possible (no cached master)
9974:S 22 Oct 2020 18:06:37.836 * Full resync from master: d9b92e71240244d6b7e69d44bdd92b6d2a99fdd9:7688
9974:S 22 Oct 2020 18:06:37.938 * MASTER <-> REPLICA sync: receiving 280 bytes from master
9974:S 22 Oct 2020 18:06:37.939 * MASTER <-> REPLICA sync: Flushing old data
9974:S 22 Oct 2020 18:06:37.939 * MASTER <-> REPLICA sync: Loading DB in memory
9974:S 22 Oct 2020 18:06:37.939 * MASTER <-> REPLICA sync: Finished with success
9.4 主从复制的故障恢复
9.4.1 从节点故障
从节点只读, 如果从节点故障,只需将Client指向另一个从节点即可, 并及时修复故障从节点
具体切换实践, 一般需要由开发人员完成, 或者如果用了负载均衡进行多个从节点的调度, 那么程序无需改动, 因为故障的从节点不会被调度请求, 负载均衡器会监控后端服务器的状态. 这样只需要运维人员及时修复从节点即可
9.4.2 主节点故障
- 方法1: 手动提升一个slave为主节点
- 方法2: 利用Redis哨兵实现自动提升一个从节点为主节点
- 之后用户的写操作要发到新的主节点
手动切换大致过程:
- 将提升的从节点,设为主机节点, replicaof no one
- 将其余从节点, 指向新的主节点
9.4.3 实现手动切换主节点
实验环境:
三台Redis服务器
主:10.0.0.82
从1: 10.0.0.83
从2: 10.0.0.84
模拟主节点故障, systemctl stop redis, 提升从1;10.0.0.83为新的主节点
- 停止主节点redis服务
systemctl stop redis
- 观察从节点复制信息和日志
# Replication
role:slave
master_host:10.0.0.82
master_port:6379
master_link_status:down #down说明与主从连接断开
#从节点日志
744:S 22 Oct 2020 18:24:33.499 * Connecting to MASTER 10.0.0.82:6379
744:S 22 Oct 2020 18:24:33.499 * MASTER <-> REPLICA sync started
744:S 22 Oct 2020 18:24:33.499 # Error condition on socket for SYNC: Connection refused
- 将从1: 10.0.0.83提升为主节点. 提升前后都要确保数据没有丢失
#丛节点1配置
127.0.0.1:6379> replicaof no one
OK
# Replication
role:master
connected_slaves:0
744:M 22 Oct 2020 18:26:21.480 * Discarding previously cached master state.
744:M 22 Oct 2020 18:26:21.480 * MASTER MODE enabled (user request from 'id=4 addr=127.0.0.1:56326 fd=7 name= age=5857 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=36 qbuf-free=32732 obl=0 oll=0 omem=0 events=r cmd=replicaof')
- 从节点2上指向和新的主节点10.0.0.83同步
修改配置文件
replicaof 10.0.0.83 6379
masterauth redis
注意: 一定要确保每个redis节点的密码一致, 并且bind在0.0.0.0, 否则会出现主从无法建立, 从节点连接主节点失败
重启redis服务, 观察日志
systemctl restart redis
#可以看到从节点2与新的主节点建立了主从同步
10008:S 22 Oct 2020 18:30:43.502 * Connecting to MASTER 10.0.0.83:6379
10008:S 22 Oct 2020 18:30:43.503 * MASTER <-> REPLICA sync started
10008:S 22 Oct 2020 18:30:43.510 * Non blocking connect for SYNC fired the event.
10008:S 22 Oct 2020 18:30:43.511 * Master replied to PING, replication can continue...
10008:S 22 Oct 2020 18:30:43.513 * Trying a partial resynchronization (request d9b92e71240244d6b7e69d44bdd92b6d2a99fdd9:9075).
10008:S 22 Oct 2020 18:30:43.515 * Successful partial resynchronization with master.
10008:S 22 Oct 2020 18:30:43.515 # Master replication ID changed to eeeab96b3382ee4f552bd76e8849dc26bbd3d17f
10008:S 22 Oct 2020 18:30:43.515 * MASTER <-> REPLICA sync: Master accepted a Partial Resynchronization.
- 测试同步
在新的主机点添加信息
127.0.0.1:6379> set name Jordan
OK
从节点验证
127.0.0.1:6379> get name
"Jordan"
- 恢复主节点
故障的主节点恢复后, 需要配置为从节点, 指向新的主节点
修改故障主节点配置文件
replicaof 10.0.0.83 6379
masterauth redis
启动节点, 观察新主上的日志
[root@master ~]# systemctl start redis
744:M 22 Oct 2020 18:39:52.204 * Replica 10.0.0.82:6379 asks for synchronization
744:M 22 Oct 2020 18:39:52.204 * Full resync requested by replica 10.0.0.82:6379
744:M 22 Oct 2020 18:39:52.204 * Starting BGSAVE for SYNC with target: disk
744:M 22 Oct 2020 18:39:52.205 * Background saving started by pid 1419
1419:C 22 Oct 2020 18:39:52.207 * DB saved on disk
1419:C 22 Oct 2020 18:39:52.208 * RDB: 2 MB of memory used by copy-on-write
744:M 22 Oct 2020 18:39:52.281 * Background saving terminated with success
744:M 22 Oct 2020 18:39:52.281 * Synchronization with replica 10.0.0.152:6379 succeeded
主节点故障后, 在新的主节点设置的key,也同步到了本机
127.0.0.1:6379> get name
"Jordan"
9.5 级联复制
准备环境:
四台redis服务器
主节点master: 10.0.0.83
从节点-slave-1: 10.0.0.82
丛节点-slave-2: 10.0.0.84, 与丛节点1同步
丛节点-slave-3: 10.0.0.85, 与丛节点1同步
大致步骤:
配置主节点
配置中间从节点与主节点同步
配置其他从节点与中间节点同步
上一个实验中, 实现了故障处理, 目前主节点为10.0.0.83, 两个丛节点为10.0.0.82和10.0.0.84
- 主节点为10.0.0.83
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=10.0.0.84,port=6379,state=online,offset=13407,lag=1
slave1:ip=10.0.0.82,port=6379,state=online,offset=13407,lag=1
- 从节点10.0.0.82和10.0.0.84
#10.0.0.82
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:10.0.0.83
master_port:6379
master_link_status:up
master_last_io_seconds_ago:2
master_sync_in_progress:0
#10.0.0.84
# Replication
role:slave
master_host:10.0.0.83
master_port:6379
master_link_status:up
master_last_io_seconds_ago:9
master_sync_in_progress:0
- 配置10.0.0.84与10.0.0.82同步
replicaof 10.0.0.82 6379
masterauth redis
systemctl restart redis
#10.0.0.82观察日志
2926:S 07 Mar 2021 18:56:06.796 * Replica 10.0.0.84:6379 asks for synchronization
2926:S 07 Mar 2021 18:56:06.796 * Partial resynchronization request from 10.0.0.84:6379 accepted. Sending 0 bytes of backlog starting from offset 16502.
- 准备另一个丛节点10.0.0.85, 与10.0.0.82同步
replicaof 10.0.0.82 6379
masterauth redis
requirepass redis
bind 0.0.0.0
systemctl enable --now redis
- 观察各节点信息
10.0.0.82 中间从节点
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:10.0.0.83
master_port:6379
master_link_status:up
master_last_io_seconds_ago:7
master_sync_in_progress:0
slave_repl_offset:17243
slave_priority:100
slave_read_only:1
connected_slaves:2
slave0:ip=10.0.0.84,port=6379,state=online,offset=17243,lag=1
slave1:ip=10.0.0.85,port=6379,state=online,offset=17243,lag=1
master_replid:1fd3b98646fe677575436b745b3e4e9d0a2c41e5
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:17243
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:3866
repl_backlog_histlen:13378
10.0.0.84 slave2
# Replication
role:slave
master_host:10.0.0.82 # 与中间节点同步
master_port:6379
master_link_status:up
master_last_io_seconds_ago:2
master_sync_in_progress:0
slave_repl_offset:17341
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:1fd3b98646fe677575436b745b3e4e9d0a2c41e5
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:17341
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:16502
repl_backlog_histlen:840
10.0.0.85 slave3
# Replication
role:slave
master_host:10.0.0.82 # 与中间节点同步
master_port:6379
master_link_status:up
master_last_io_seconds_ago:6
master_sync_in_progress:0
slave_repl_offset:17383
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:1fd3b98646fe677575436b745b3e4e9d0a2c41e5
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:17383
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:17048
repl_backlog_histlen:336
10.0.0.83 master节点
[18:31:07 root@master ~]#redis-cli -a redis
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379>
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1 # master节点之和中间节点同步
slave0:ip=10.0.0.82,port=6379,state=online,offset=17103,lag=1
master_replid:1fd3b98646fe677575436b745b3e4e9d0a2c41e5
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:17103
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:17103
- 验证同步成功
10.0.0.83 创建信息
127.0.0.1:6379> set drink pepsi
OK
从节点分别验证即可
127.0.0.1:6379> get drink
"pepsi"
9.6 主从复制过程
Redis主从复制分为全量同步和增量同步
首次搭建完, 主节点会做bgsave, 把rdb全量复制给slave
全量复制后, 新的变化的数据为增量同步
9.6.1 全量复制
首次同步是全量同步, 主从同步可以让从服务器从主服务器同步数据, 而且从服务器还可以再有其他的从服务器, 即另外一台redis服务器可以从一台从服务器进行数据同步, redis的主从同步是非阻塞的.
master收到从服务器的psync(2.8版本以前是sync)命令, 会fork一个子进程在后台执行bgsave命令, 并将新写入的数据写入到一个缓冲区, bgsave执行完成后, 将生成的rdb文件发送给slave, 然后master再将缓冲区中的内容以redis协议格式再全部发送给slave.
slave会先删除旧数据, 然后将收到的rdb文件先载入自己的内存, 然后再加载所有收到的缓冲区的内容. 从而完成一次全量同步
master_replid: redis主从复制时用来识别主节点. 第一次全量复制时, 因为从节点是没有主节点的master_replid, 发起同步时从节点日志会显示partial resynchronization not possible (no cached master), 但是主节点可以通过密码来认证从节点. 一旦判断没有master_replid, 那么主节点会进行全量复制. 之后, 每次从节点和主节点建立连接时, 从节点会发送主节点的master_replid给主节点进行验证, 主节点通过master_replid验证是否要连接自己还是别人.
所以, 首次连接时, 只要从节点设置对了主节点的密码, 主节点就会做全量同步, 并且把自己的master_replid发给从节点. 之后, 每次从节点和主节点同步时, 从节点会发送之前收到的主节点的master_replid, 主节点确认是自己的则同步
主节点观察
master_replid:1fd3b98646fe677575436b745b3e4e9d0a2c41e5
级联中间从节点观察
master_replid:1fd3b98646fe677575436b745b3e4e9d0a2c41e5
级联从节点slave3观察
master_replid:1fd3b98646fe677575436b745b3e4e9d0a2c41e5
可以看出, 级联复制情况下, 所有的从节点, 包括中间节点都是统一用主节点的master_replid
主从复制buffer空间大小: 如果主服务器在生成RDB和发送RDB文件期间, buffer缓冲区写满了, 则会把旧的信息删除, 造成主从数据不同步, 导致同步失败. 会重复执行全量复制, 因为buffer空间一直是满的. 生产环境要设置要为大的buffer空间值.
可以调整配置文件中的参数来调大buffer值
vim /etc/redis.conf
# repl-backlog-size 1mb
9.6.2 增量复制
全量同步之后, 再次需要同步时, 从服务器只要发送当前的offset位置(等同于MySQL的binlog的位置)给主服务器, 然后主服务器根据相应的位置将之后的数据(包括在缓冲区积压的数据)发送给从服务器, 从服务器将它们都加载到内存即可
从节点会发送自己的slave_repl_offset位置, 只同步增加的数据, 不会全量同步
注意: 如果是短暂的连接中断, 那么主从重新连接后, 是进行增量复制, 因为主节点master_replid不变, 从节也是保存的主节点的master_replid.
案例: 验证短暂中断,主从重新连接后, 是进行增量复制, 因为主节点master_replid不变, 从节也是保存的主节点的master_replid.
在master节点添加iptables拒绝10.0.0.82 slave1的流量
[19:52:04 root@master ~]#iptables -A INPUT -s 10.0.0.82 -j REJECT
查看master日志
2572:M 07 Mar 2021 21:04:39.975 # Disconnecting timedout replica: 10.0.0.82:6379
2572:M 07 Mar 2021 21:04:39.975 # Connection with replica 10.0.0.82:6379 lost.
master创建新的key, 验证slave1-10.0.0.82不会同步
127.0.0.1:6379> set aaaaaaaa bbbbbbbbb
OK
127.0.0.1:6379> get aaaaaaaa
(nil)
slave2和slave3也不会同步
127.0.0.1:6379> get aaaaaaaa
(nil)
slave1-10.0.0.82的日志会显示连接主节点拒绝
2926:S 07 Mar 2021 21:07:43.871 * MASTER <-> REPLICA sync started
2926:S 07 Mar 2021 21:07:43.871 # Error condition on socket for SYNC: Connection refused
slave2和slave3的日志会显示master节点暂时无法接受psync
22736:S 07 Mar 2021 21:08:17.056 * Trying a partial resynchronization (request 1fd3b98646fe677575436b745b3e4e9d0a2c41e5:27065).
22736:S 07 Mar 2021 21:08:17.056 * Master is currently unable to PSYNC but should be in the future: -NOMASTERLINK Can't SYNC while not connected with my master
再次在master创建新的key
127.0.0.1:6379> set color red
OK
127.0.0.1:6379> set car bmw
OK
取消iptables规则
[21:13:06 root@master ~]#iptables -D INPUT 1
[21:13:15 root@master ~]#tail /var/log/redis/redis.log
2572:M 07 Mar 2021 19:07:46.560 * Background saving terminated with success
2572:M 07 Mar 2021 21:04:39.975 # Disconnecting timedout replica: 10.0.0.82:6379
2572:M 07 Mar 2021 21:04:39.975 # Connection with replica 10.0.0.82:6379 lost.
2572:M 07 Mar 2021 21:05:32.442 * 1 changes in 900 seconds. Saving...
2572:M 07 Mar 2021 21:05:32.443 * Background saving started by pid 2925
2925:C 07 Mar 2021 21:05:32.446 * DB saved on disk
2925:C 07 Mar 2021 21:05:32.446 * RDB: 2 MB of memory used by copy-on-write
2572:M 07 Mar 2021 21:05:32.548 * Background saving terminated with success
2572:M 07 Mar 2021 21:13:15.268 * Replica 10.0.0.82:6379 asks for synchronization
2572:M 07 Mar 2021 21:13:15.268 * Partial resynchronization request from 10.0.0.82:6379 accepted. Sending 120 bytes of backlog starting from offset 27065. # 恢复连接后, 进行增量复制
查看slave-1上的数据和日志
127.0.0.1:6379> get color
"red"
127.0.0.1:6379> get car
"bmw"
# slave-1与主节点连接恢复, 并且继续为其他从节点提供数据复制, 其他的从节点也能收到更新的数据, 包括连接断开期间, 主节点新增的数据
[21:07:44 root@slave-1 ~]#tail /var/log/redis/redis.log
2926:S 07 Mar 2021 21:13:18.635 * MASTER <-> REPLICA sync started
2926:S 07 Mar 2021 21:13:18.635 * Non blocking connect for SYNC fired the event.
2926:S 07 Mar 2021 21:13:18.636 * Master replied to PING, replication can continue...
2926:S 07 Mar 2021 21:13:18.637 * Trying a partial resynchronization (request 1fd3b98646fe677575436b745b3e4e9d0a2c41e5:27065). # 这里的27065就是从节点目前数据的offset, 发送给主节点后, 主节点根据offset来发送增量的数据
2926:S 07 Mar 2021 21:13:18.637 * Successful partial resynchronization with master.
2926:S 07 Mar 2021 21:13:18.638 * MASTER <-> REPLICA sync: Master accepted a Partial Resynchronization.
2926:S 07 Mar 2021 21:13:18.982 * Replica 10.0.0.84:6379 asks for synchronization
2926:S 07 Mar 2021 21:13:18.982 * Partial resynchronization request from 10.0.0.84:6379 accepted. Sending 120 bytes of backlog starting from offset 27065.
2926:S 07 Mar 2021 21:13:19.206 * Replica 10.0.0.85:6379 asks for synchronization
2926:S 07 Mar 2021 21:13:19.206 * Partial resynchronization request from 10.0.0.85:6379 accepted. Sending 120 bytes of backlog starting from offset 27065.
但是, 如果主节点服务重启, 那么master_replid会发生变化, 这时从节点再想和主节点同步, 因为从节点还是保留的主节点重启服务前的master_replid, 那么master_replid是不一样的, 主节点就会发起全量复制. 全量复制对网络带宽消耗很大, 因为要把内存中的数据存到磁盘rdb文件, 还要通过网络传输, 资源消耗非常大.
案例: 验证主节点重启后, 会进行全量复制
- 先查看主节点当前的master_replid
[21:13:33 root@master ~]#redis-cli -a redis info | grep master_replid
master_replid:4ad48f028d3091f982c1209fcaf6dd30236c75c3
- stop主节点redis服务器
[21:45:24 root@master ~]#systemctl stop redis
- start主节点redis服务器, 查看新的master_replid
master_replid:3b784afa1cf1553ff1bbe90304ba26c9098ee8ac
- 此时, 两个master_replid不同, 因此, 主从连接恢复后, 从节点想和主节点同步时发送的master_replid已经失效了, 主节点会认为该从节点之前没和自己同步过, 因此会进行全量复制
# master节点日志
3031:M 07 Mar 2021 21:52:57.362 * Replica 10.0.0.82:6379 asks for synchronization
3031:M 07 Mar 2021 21:52:57.363 * Partial resynchronization not accepted: Replication ID mismatch (Replica asked for '4ad48f028d3091f982c1209fcaf6dd30236c75c3', my replication IDs are '0dff6cc4e8b95f7781f7707be285b07e39c832b9' and '0000000000000000000000000000000000000000')
3031:M 07 Mar 2021 21:52:57.363 * Starting BGSAVE for SYNC with target: disk
3031:M 07 Mar 2021 21:52:57.363 * Background saving started by pid 3035
3035:C 07 Mar 2021 21:52:57.368 * DB saved on disk
3035:C 07 Mar 2021 21:52:57.368 * RDB: 4 MB of memory used by copy-on-write
3031:M 07 Mar 2021 21:52:57.465 * Background saving terminated with success
3031:M 07 Mar 2021 21:52:57.465 * Synchronization with replica 10.0.0.82:6379 succeeded
# slave-1 10.0.0.82日志
# master一旦重启, 就会和中间从节点进行全量复制, 并且其他从节点从中间从节点同步数据也是全量复制
3284:S 07 Mar 2021 21:53:00.738 * Connecting to MASTER 10.0.0.83:6379
3284:S 07 Mar 2021 21:53:00.739 * MASTER <-> REPLICA sync started
3284:S 07 Mar 2021 21:53:00.739 * Non blocking connect for SYNC fired the event.
3284:S 07 Mar 2021 21:53:00.740 * Master replied to PING, replication can continue...
3284:S 07 Mar 2021 21:53:00.741 * Trying a partial resynchronization (request 4ad48f028d3091f982c1209fcaf6dd30236c75c3:127).
3284:S 07 Mar 2021 21:53:00.743 * Full resync from master: 3b784afa1cf1553ff1bbe90304ba26c9098ee8ac:0
3284:S 07 Mar 2021 21:53:00.743 * Discarding previously cached master state.
3284:S 07 Mar 2021 21:53:00.743 # Connection with replica 10.0.0.84:6379 lost.
3284:S 07 Mar 2021 21:53:00.743 # Connection with replica 10.0.0.85:6379 lost.
3284:S 07 Mar 2021 21:53:00.845 * MASTER <-> REPLICA sync: receiving 507 bytes from master
3284:S 07 Mar 2021 21:53:00.845 * MASTER <-> REPLICA sync: Flushing old data
3284:S 07 Mar 2021 21:53:00.845 * MASTER <-> REPLICA sync: Loading DB in memory
3284:S 07 Mar 2021 21:53:00.846 * MASTER <-> REPLICA sync: Finished with success
3284:S 07 Mar 2021 21:53:00.847 * Background append only file rewriting started by pid 3299
3284:S 07 Mar 2021 21:53:00.925 * AOF rewrite child asks to stop sending diffs.
3299:C 07 Mar 2021 21:53:00.926 * Parent agreed to stop sending diffs. Finalizing AOF...
3299:C 07 Mar 2021 21:53:00.926 * Concatenating 0.00 MB of AOF diff received from parent.
3299:C 07 Mar 2021 21:53:00.926 * SYNC append only file rewrite performed
3299:C 07 Mar 2021 21:53:00.926 * AOF rewrite: 2 MB of memory used by copy-on-write
3284:S 07 Mar 2021 21:53:00.944 * Background AOF rewrite terminated with success
3284:S 07 Mar 2021 21:53:00.944 * Residual parent diff successfully flushed to the rewritten AOF (0.00 MB)
3284:S 07 Mar 2021 21:53:00.944 * Background AOF rewrite finished successfully
3284:S 07 Mar 2021 21:53:01.195 * Replica 10.0.0.84:6379 asks for synchronization
3284:S 07 Mar 2021 21:53:01.195 * Partial resynchronization not accepted: Replication ID mismatch (Replica asked for '4ad48f028d3091f982c1209fcaf6dd30236c75c3', my replication IDs are '3b784afa1cf1553ff1bbe90304ba26c9098ee8ac' and '0000000000000000000000000000000000000000')
3284:S 07 Mar 2021 21:53:01.195 * Starting BGSAVE for SYNC with target: disk
3284:S 07 Mar 2021 21:53:01.196 * Background saving started by pid 3300
3300:C 07 Mar 2021 21:53:01.200 * DB saved on disk
3300:C 07 Mar 2021 21:53:01.200 * RDB: 2 MB of memory used by copy-on-write
3284:S 07 Mar 2021 21:53:01.252 * Background saving terminated with success
3284:S 07 Mar 2021 21:53:01.253 * Synchronization with replica 10.0.0.84:6379 succeeded
3284:S 07 Mar 2021 21:53:01.369 * Replica 10.0.0.85:6379 asks for synchronization
3284:S 07 Mar 2021 21:53:01.369 * Partial resynchronization not accepted: Replication ID mismatch (Replica asked for '4ad48f028d3091f982c1209fcaf6dd30236c75c3', my replication IDs are '3b784afa1cf1553ff1bbe90304ba26c9098ee8ac' and '0000000000000000000000000000000000000000')
3284:S 07 Mar 2021 21:53:01.369 * Starting BGSAVE for SYNC with target: disk
3284:S 07 Mar 2021 21:53:01.370 * Background saving started by pid 3301
3301:C 07 Mar 2021 21:53:01.375 * DB saved on disk
3301:C 07 Mar 2021 21:53:01.375 * RDB: 2 MB of memory used by copy-on-write
3284:S 07 Mar 2021 21:53:01.458 * Background saving terminated with success
3284:S 07 Mar 2021 21:53:01.458 * Synchronization with replica 10.0.0.85:6379 succeeded
9.6.3 主从复制完整过程(全量+增量)
具体的主从同步过程如下:
1. 从服务器连接主服务器, 发送psync命令
2. 主服务器接收到psync命令后, 开始执行bgsave命令生成rdb快照文件, 并用缓冲区记录执行bgsave期间的所有新增命令
3. 主服务器bgsave执行完后, 向所有从服务器发送rdb快照文件, 并在发送期间继续记录被执行的新增命令
4. 从服务器收到快照文件后, 丢弃所有旧数据, 载入收到的快照至内存
5. 主武器快照发送完毕后, 开始向从服务器发送缓冲区中的写命令
6. 从服务器完成对快照的载入, 开始接受命令请求, 并执行来自主服务器缓冲区的写命令
7. 后期同步从服务器会先发送自己slave_repl_offset位置, 只同步新增加的数据, 不在全量同步
9.6.4 主从同步优化
- 缓冲区环形队列配置
# 复制缓冲区大小, 建议设置足够大, 否则一旦缓冲区慢, 新的数据会冲掉旧的数据, 造成主从始终同步不一致
repl_backlog_size 1mb
# redis同时也提供了当没有slave需要同步的时候, 多久可以释放环形队列的设置
repl_backlog_ttl 3600
- 性能优化配置
redis在2.8版本之前, 没有提供增量部分复制的功能, 当网络闪断或者slave redis重启后, 会导致主从之间的全量同步, 即从2.8版本开始, 增加了增量复制功能
repl-diskless-sync no
# 是否使用无盘同步RDB文件,默认为no,no为不使用无盘,需要将RDB文件保存到磁盘后
# 再发送给slave,yes为支持无盘,支持无盘就是RDB文件不需要保存至本地磁盘,而且直接通过socket文件发送给slave
# diskless效率高, 但是如果有多个从节点, 那么需要主节点直接在内容, 通过网络给多个从节点发送rdb, 会加大服务器和网络的负载
repl-diskless-sync-delay 5 # diskless时复制的服务器等待的延迟时间
repl-ping-slave-period 10 # slave端向server端发送ping的时间区间设置,默认为10秒
repl-timeout 60 # 设置超时时间
repl-disable-tcp-nodelay no # 是否启用TCP_NODELAY,如设置成yes,则redis会合并小的TCP包从而节省带宽, 但会增加同步延迟(40ms),造成master与slave数据不一致,假如设置成no,则redis master会立即发送同步数据,没有延迟,前者关注性能,后者关注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不接收写操作(拒绝写入), 不要太短, 否则如果有延迟, 很容易造成主节点无法写入数据
9.6.5 避免复制风暴
- 一主节点复制风暴
当主节点重启, 因为master_replid会变化, 会向所有的从节点进行全量同步
解决方法: 利用级联复制, 主节点只和中间节点进行全量同步, 中间节点在和其余节点同步
- 单机器复制风暴
多见于测试环境
在同一个主机上, 搭建多个redis主从, 如果把master都放在一个主机上, 那么一旦该服务器本身重启, 也会造成redis重启, 并且产生全量复制, 大量的全量复制都从一个主机发向其他的主机
解决方法: 分散式部署主从, 让两个主机上, 都有master和slave, 互相错开
9.6.6 避免全量复制
- 第一次全量复制无法避免, 后续的全量复制可以利用小主节点(内存小), 业务低峰时进行全量复制
- 节点master_replid不匹配, 主节点重启导致master_replid发生变化, 可能触发全量复制, 可以利用故障转移, 例如哨兵或集群, 不要由于故障或者突发情况关闭的redis自动启动, 而是提升一个可用从节点为主节点避免全量复制
- 复制buffer缓冲区大小不足, 当主节点生成的新数据大于缓冲区大小, 从节点恢复和主节点连接后, 会导致全量复制, 因此, repl_backlog_size要调大
9.6.7 主从复制故障汇总
- master密码不对
从节点配置的masterauth和主节点的requirepass一致
所有节点的requirepass一致
- redis版本不一致
不同的redis 大版本之间存在兼容性问题,比如:3和4,4和5之间,因此各master和slave之间必须保持版本一致
- 无法远程连接
在开启了安全模式情况下,没有设置bind地址或者密码。
- 主从配置不一致
主从节点的maxmemory不一致, 主节点内存大于从节点内存, 主从复制可能会丢失数据
rename-command命令不一致, 如在主节点定义了flushdb的别名, 从节点没有定义, 结果同步数据, 从节点执行别名时, 不生效, 造成数据不同步