这是本人学习的总结,主要学习资料如下
- B站狂神说,redis教程
目录
1、主从复制概述
主从复制是指将一台服务器的数据复制到其它服务器。前者称为主机,后者称为从机。只能从主机写到从机,不能反过来。主机以写为主,从机以读为主。
因为大部分请求都是以读,所以会有这样的设计。
一般主从的最低配置是『一主二从』。其实『一主一从』也可以,但是后面会涉及到哨兵模式,至少需要两台从机。
1.2、主从复制的用途
- 数据冗余:从机相当于主机的数据的热备份,是持久化的另一种备份方式。
- 故障修复:当主机发生严重问题时,就能用从机恢复数据。
- 负载均衡:主机负责写,从机负责读,可大大提高并发量。
1.3、redis主从复制的特点
- 全量复制和增量复制同时运行:全量复制是指,当一个主机开启一段时间以后,有一个从机突然关联这个主机,那主机会将现有的所有数据都复制给从机;增量复制是指,当一个主机会将增加的数据复制给从机。
- 主机断线后从机不丢数据:我做了实验,当主机shutdown以后,从机中的数据不会被删除,依旧可以读。
- 主机上线进行全量复制:当主机重新上线以后,会从持久化文件中备份数据,这时候会对从机进行一次全量复制。我做了实验,主机掉线,从机可以继续读。删除主机的
dump.rdb
文件,重启主机,从机的数据也没了。 - 主从复制具有传递性:从机可以为别人的主机,当该从机的主机复制数据给该从机时,该从机也会作为主机将数据复制给它的从机。尽管具有传递性,只要某个redis机子是别人的从机,那就决不允许该从机直接接受写入指令。
2、前期准备
2.1、开启多个本地redis服务模拟集群
- 复制两份
redis.conf
文件,这两份文件分别是两个从机的启动配置文件。改写这两个文件的端口号,我是改成了6380和6381。 - 改写配置文件的日志文件名,持久化文件名,避免三个redis服务产生的记录混杂。我将日志文件名改成『端口号.log』,持久化文件名改成『dump+端口号.rdb』。
logfile "6379.log"
dbfilename "dump6379.rdb"
- 现有三份
redis.conf
文件,redis.conf
原文件我当做主机,端口6379
,另外两个文件redis-slave1.conf
和redis-slave2.conf
我当从机,端口号分别是6380
和6381
。
之后cd到redis安装目录下,使用命令redis-server /*/redis.conf
指定指定配置文件开启redis服务即可。
- 配置主从关系:所有的redis服务默认都是主机,配置主从都是在从机上配置。使用指令
slaveof ip port
,指定本机是谁的从机,或者启动前在配置文件中修改。下面指令将slave1
和slave2
都设为6379
的从机。
MacBook-Pro:etc user$ redis-cli -p 6380
127.0.0.1:6380> slaveof 127.0.0.1 6379
OK
# another terminal
MacBook-Pro:etc user$ redis-cli -p 6381
127.0.0.1:6380> slaveof 127.0.0.1 6379
OK
另外我们可以使用info replication
查看主从配置信息。配置好之后,主机写的数据从机就能立刻读到,同时从机的任何写操作都是禁止的,主机可以随意读写。
这里我们在slave2
中执行info replication
,可以看到下面的信息。
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:5
master_sync_in_progress:0
slave_repl_offset:182
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:97385f0960597e07bbb059164b2ef7ebfd4aa6a0
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:182
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:182
这时候我们在主机写的数据,都可以在从节点读到,这部分就不演示了。同时从节点也无法写数据。
127.0.0.1:6380> set k1 v1
(error) READONLY You can't write against a read only replica.
3、主从复制流程
- 输入
slaveof ip port
以后,该命令会保存主机信息,等待定时任务调用信息建立连接。 - 定时任务读取
ip
和port
,根据这些信息想主机发送socket
连接;发送ping
命令查看连接是否正常;之后是一些权限校验的操作。 - 建立连接后,主机通过
RDB
文件全量复制所有信息到从节点。之后不停地复制增量信息。
在2.8版本以前,第五步的psync
是全量复制,但如果数据很大时,这里的速度就会变得比较慢。在2.8版本以后,psync
也开始支持部分数据复制。
下面是第五步psync
的具体流程。
3.1、psync
- 从节点发送
psync ? -1
,表示需要同步数据,-1
表示同步全部数据。 - 主节点发送确认信息
+FULLRESYNC
同意同步,并且发送ID,和offset。
这里的id
是replication id
,是这次复制的唯一标识。因为主从之间数据同步不止一次,所以会有多个replication id
。
我们可以通过info replication
查看id。
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=142534,lag=0
master_replid:97385f0960597e07bbb059164b2ef7ebfd4aa6a0
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:142534
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:142534
- 之后主节点开始执行
bgsave
指令开始持久化数据,生成RDB
文件。文件生成后发给从节点。
和其他持久化过程一样,在生成RDB
文件过程中有新数据写入时,为了防止丢失这些新数据,主节点会开辟一个缓冲区client-output-buffer
。这个缓冲区会存储这些新数据,之后会继续同步给从节点保证主从一致性。
这里是下图中低5、7
步的内容。 - 从节点接收到
RDB
文件后开始清楚旧数据并加载新的RDB
文件。如果主节点的缓冲区有新数据进来,新数据会在从节点加载完RDB
文件后更新到从节点。
-
client-output-buffer-limit replica 89mb 64mb 60
,在RDB
生成和发送时,该配置会对client-out-buffer
进行限制。89mb
,意味着client-output-buffer
的大小是89MB
,如果新数据超过了这个值,那本次主从复制也失败。64mb 60
,意味着如果在60s
内有64mb
的数据写入,那本次主从复制失败。
-
repl-timeout 60
,网络超时设置。这个配置意味着,当主节点开始向从节点传输RDB
文件后开始计时,如果时间超过60s
则判定这次主从复制失败,从节点清除已接收的部分RDB
数据。
3.2、部分复制
部分复制(partial resynchronization),这个机制是用在这个的一个情况。
全量复制中主从节点之间网络出现中断并且连接也超过repl-timeout
规定的时间,等到下次网络恢复后,从节点会开始请求部分复制。
- 在网络断开前从节点也接收到了一些数据。这些数据的大小就是偏移量
offset
。从节点将offset
作为参数发送psync
指令,表示希望主节点从offset
开始继续发送后面的数据。 - 主节点将偏移量靠后的数据放入
repl-backlog
缓存区中,成为复制积压缓冲区,将这个缓冲区塞满数据为止。 - 主节点发起判断,判断从节点的数据能否和
repl-backlog
中的数据衔接上。如果能则从repl-backlog
中将对应的数据同步给从节点;如果不能证明从节点所需要的数据太多了,于是主节点放弃这次部分复制转而开启全量复制。
repl-backlog-size 1mb
:用于控制repl-backlog
缓存区的大小,默认是1mb。如果值太小,那基本上不会有部分复制,全是全量复制;如果太大那
4、哨兵模式
现在有这样一个场景,如果主机挂掉了,而且短时间内有无法恢复,那就让redis服务只有读没有写吗?听起来有些浪费资源,也不太实际。事实上,当我们发现主机挂掉以后,可以指定一个新的节点为主机,继续维持redis的服务。
找到一个从机,执行指令slaveof no one
,这时候该从机就可以变成主机,可以提供写服务,同时其他从机的主从关系维持不变。
这样的解决方式也带来一个问题,如果原本的主机抢救回来了,那也是一个光杆司令。系统就会变成有两个主机,各自有自己的端口号。要想回到最初的状态又要使用指令slaveof ip port
,比较麻烦。所幸的是,redis提供了哨兵模式帮助我们自动完成这些事,服务更及时,更有效。
4.1、哨兵模式详解
首先,哨兵是指一个独立进程,他会监控指定的redis服务。当发现主机挂了以后,哨兵会通过一些条件选取一个从机,并将这个从机变成新的主机。
上图是一个简单的模型,实际上一个哨兵还不够,因为这个哨兵也有挂掉的风险,所以我们还会增加监控哨兵的哨兵,保证系统的运行。一般情况下一个最简单的哨兵模型也应该有三哨兵三机子。
4.1.1、哨兵判定节点下线
三个哨兵各自监控所有的redis服务,同时也互相监控。
-
每个哨兵有一个默认每秒一次的定时任务,去
ping
所有监控节点(包括哨兵节点),如果哨兵1
连续sentinel down-after-milliseconds
秒没有ping
通某个节点,那这个哨兵1
就认为这个节点已掉线。但目前为止只是哨兵1
认为这个节点下线,一般称这个节点是哨兵1
的主观下线。sentinel down-after-milliseconds 可配置,系统默认是60s -
这时系统不会立刻处理节点,因为
哨兵1
可能判断错误。哨兵1
会向其他哨兵发送is-master-down-by-addr
来询问他们的看法。其他哨兵也会重复这个流程,直到认为这个节点确实挂了的哨兵数量达到quorum
的时候,那系统才会开始处理,这时这个节点被称为客观下线。quorum的数量在sentinel moniter master 127.0.0.1 6379 quorum中配置
4.1.2、哨兵中选举领导者
在这个过程中,首先发现节点下线的哨兵1
会被推举成领导者,它在接下来负责主节点的选举。
4.1.3、选举主节点
如果下线的是主节点,那哨兵就会开始选举主节点。
首先哨兵会经过下面的判断来决定哪个从节点应该是主节点。
- 哨兵有维护一份从节点列表,记录着所有从节点的相关信息。第一步是先过滤掉不健康的节点,哨兵
ping
从节点经常要花比较长的时间,那这个从节点就算不健康,一般默认超过5s
就是不健康。 - 之后选择
slave-priority
最高的从节点,这个可在从节点的配置文件中配置。 - 如果还有多个从节点可选,那就选择复制最完整的节点。因为主节点复制数据到从节点是有
offset
记录数据量的,所以offset
最高的节点数据复制最完整。 - 如果还有多个节点可选,那就选择最先启动的节点。这里是通过
runId
判断,runId
越小就越早生成。
4.1.4、新主节点开始接手工作
新的主节点开始接手工作,之后哪怕旧的主节点重新上线,那也只能成为从节点。
- 新的主节点执行
slaveof no one
。 - 其他从节点执行
slaveof newMaster
更换主节点。 - 之后哨兵通知应用程序新的主节点的地址。
4.2、启动哨兵服务
4.2.1、编写哨兵配置文件
配置文件和redis.conf
一样,只不过启动的方式不一样。这里我将其命名为是sentinel.conf
,放在安装目录下,或者等会启动的时候用指定配置文件的方式启动。
需要注意下面这些配置信息。
# sentinel monitor 主机名(自定义) ip port n; n表示至少要有n个哨兵认为这个监视对象失效,那这台主机才失效。
sentinel monitor master 127.0.0.1 6379 1
# 监控的主机在60000毫秒内没有反馈,那哨兵就认为这台机子挂了
sentinel down-after-milliseconds master 60000
# 要使用空闲的端口号
port 16379
4.2.2、启动哨兵
执行指令redis-sentinel /*/sentinel.conf
。
fb
需要注意,哨兵监控一个主节点时,会自动监控其从节点。
MacBook-Pro:etc user$ redis-sentinel /usr/local/etc/sentinel.conf
77763:X 14 Feb 2023 00:14:01.952 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
77763:X 14 Feb 2023 00:14:01.952 # Redis version=5.0.6, bits=64, commit=00000000, modified=0, pid=77763, just started
77763:X 14 Feb 2023 00:14:01.952 # Configuration loaded
77763:X 14 Feb 2023 00:14:01.953 * Increased maximum number of open files to 10032 (it was originally set to 2560).
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 5.0.6 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in sentinel mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 16379
| `-._ `._ / _.-' | PID: 77763
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
77763:X 14 Feb 2023 00:14:01.955 # Sentinel ID is f14ee33db41e8430e3a13154ccfab5f4ea7ed4f6
77763:X 14 Feb 2023 00:14:01.955 # +monitor master reids 127.0.0.1 6379 quorum 1
77763:X 14 Feb 2023 00:14:01.957 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ reids 127.0.0.1 6379
4.2.3、哨兵中的定时任务
-
哨兵获取节点拓扑结构:默认哨兵每
10s
想所有监控节点发送一次info
信息,然后这些监控节点回应对应数据。哨兵直接监控的是主节点,配置中sentinel monitor master 127.0.0.1 6379 1
只写了主节点的地址,哨兵想要知道主节点有哪些从节点,就需要通过发送info
确定节点之间的拓扑结构,这样有新节点加入或者离开时,哨兵也能第一时间知道。
-
哨兵与主节点信息交换,默认每隔
2s
哨兵会向主节点发布数据,数据是关于哨兵本身的。同时哨兵也订阅主节点,会从主节点拿到信息。也就是说,哨兵和主节点通过相互订阅发布来交互信息。多个哨兵将自身信息发布到主节点,之后主节点将这些汇总的信息发布给这些哨兵实现信息交流。
- 检测节点是否在线:默认哨兵会向所有已知节点每秒发送一次
ping
信息,默认如果连续60s
没有ping
通一个节点,那就仍未这个节点下线。判定条件可通过sentinel down-after-milliseconds master 3000
修改,这条配置表示超过3s
没ping
通就判定下线。
4.3、实例测试
首先启动三个节点,port
分别是6379
,6380
,6381
。
redis-server /usr/local/etc/redis.conf
# another terminal
redis-server /usr/local/etc/redis-slave1.conf
# another terminal
redis-server /usr/local/etc/redis-slave2.conf
并且设6379
为主节点。
MacBook-Pro:etc fulanbin$ redis-cli -p 6380
127.0.0.1:6380> slaveof 127.0.0.1 6379
OK Already connected to specified master
# another terminal
MacBook-Pro:etc fulanbin$ redis-cli -p 6381
127.0.0.1:6380> slaveof 127.0.0.1 6379
OK Already connected to specified master
开启三个哨兵,port
分别是16379
,16480
和16381
。他们的配置文件都写上sentinel monitor mymaster 127.0.0.1 6379 2
表示监控主节点6379
。
redis-sentinel /usr/local/etc/redis-sentinel1.conf
# another terminal
redis-sentinel /usr/local/etc/redis-sentinel2.conf
# another terminal
redis-sentinel /usr/local/etc/redis-sentinel3.conf
之后手动退出6379
(关闭端口即可),过一段时间后哨兵16379
的terminal
窗口查看日志(随便找一个哨兵都可查看,他们信息共享),里面记录着选举的整个过程。
从整个日志中可以看到,6381
成为了新的主节点。
# 发现6379挂了
69695:X 14 Feb 2023 22:46:27.076 # +sdown master mymaster 127.0.0.1 6379
69695:X 14 Feb 2023 22:46:27.160 # +odown master mymaster 127.0.0.1 6379 #quorum 2/2
69695:X 14 Feb 2023 22:46:27.160 # +new-epoch 1
69695:X 14 Feb 2023 22:46:27.160 # +try-failover master mymaster 127.0.0.1 6379
# 通知到其它哨兵,其他哨兵开始投票,比较长的字符串是哨兵的id。哨兵id会自动生成到配置文件中,比如sentinel myid 6a4e23ddd42f1f748016af9318621ec93d59a720
69695:X 14 Feb 2023 22:46:27.164 # +vote-for-leader 6a4e23ddd42f1f748016af9318621ec93d59a720 1
69695:X 14 Feb 2023 22:46:27.167 # 4791869a77100cba19037050216512956d093423 voted for 6a4e23ddd42f1f748016af9318621ec93d59a720 1
69695:X 14 Feb 2023 22:46:27.167 # 5a280e3fc35ca740484ec931a3aa1544baf6f675 voted for 6a4e23ddd42f1f748016af9318621ec93d59a720 1
69695:X 14 Feb 2023 22:46:27.265 # +elected-leader master mymaster 127.0.0.1 6379
69695:X 14 Feb 2023 22:46:27.265 # +failover-state-select-slave master mymaster 127.0.0.1 6379
69695:X 14 Feb 2023 22:46:27.333 # +selected-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6379
69695:X 14 Feb 2023 22:46:27.333 * +failover-state-send-slaveof-noone slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6379
69695:X 14 Feb 2023 22:46:27.434 * +failover-state-wait-promotion slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6379
# 推举6381为新的主节点
69695:X 14 Feb 2023 22:46:27.553 # +promoted-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6379
69695:X 14 Feb 2023 22:46:27.553 # +failover-state-reconf-slaves master mymaster 127.0.0.1 6379
69695:X 14 Feb 2023 22:46:27.642 * +slave-reconf-sent slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379
69695:X 14 Feb 2023 22:46:28.272 # -odown master mymaster 127.0.0.1 6379
69695:X 14 Feb 2023 22:46:28.587 * +slave-reconf-inprog slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379
69695:X 14 Feb 2023 22:46:28.587 * +slave-reconf-done slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379
69695:X 14 Feb 2023 22:46:28.641 # +failover-end master mymaster 127.0.0.1 6379
69695:X 14 Feb 2023 22:46:28.641 # +switch-master mymaster 127.0.0.1 6379 127.0.0.1 6381
# 6381成为新的主节点
69695:X 14 Feb 2023 22:46:28.642 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6381
69695:X 14 Feb 2023 22:46:28.642 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6381
69695:X 14 Feb 2023 22:46:58.686 # +sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6381
最后连接6381
,验证日志内容。
MacBook-Pro:etc user$ redis-cli -p 6381
127.0.0.1:6381> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=3391124,lag=0
master_replid:fb0f6e5e77e4b460f3950dd43b34231297b253a5
master_replid2:97385f0960597e07bbb059164b2ef7ebfd4aa6a0
master_repl_offset:3391124
second_repl_offset:3208815
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2342549
repl_backlog_histlen:1048576
5、哨兵主从脑裂
现在我们有一主两从,三哨兵的结构。
当master
与哨兵之间突然网络波动,哨兵集体认为master
下线,于是开始选举新的master
。
在旧的master
和哨兵断线,新的master
上任前,客户端还能继续向旧的master
写数据,因为旧的master
只是和哨兵断线但它实际可能还能工作(尽管概率很小)。
那么客户端在旧的master
写的数据就会丢失。
这就是哨兵主从脑裂问题。主脑裂开时可能发生的数据丢失。
5.1、解决方式
我们有两个配置可以减少脑裂发生的概率。
min-replicas-to-write 2
min-replicas-max-lag 10
第一个min-replicas-to-write 2
,这个意思是写入的数据至少要同步到3台机器里才算成功。上面的例子中在旧的master
在突然掉线时,可能会导致同步失败,那这次写入就失败。
min-replicas-max-lag 10
,这时同步的时间,两者结合起来就是在10ms
内没有同步到2台机器上这次写就失败。