Cluster-Sentinel
# Sentinel哨兵模式
# Sentinel简单介绍
# 1. 主从模式问题
前面搭建了主从模式,当主服务器宕机后,需要手动把一台从服务器切换为主服务器。人工干预不仅费事费力,还会造成一段时间内服务不可用。
# 2. 哨兵模式介绍
Redis提供了哨兵的命令,它是一个独立的进程。
- 原理:哨兵通过发送命令给多个节点,等待Redis服务器响应,从而监控运行的多个Redis实例的运行情况。
- 职能:
- 监控(Monitoring):Sentinel会不断地检查主服务器和从服务器是否运作正常。
- 提醒(Notification):当被监控的某个Redis服务器出现问题时,Sentinel可以通过API向管理员或者其他应用程序发送通知。
- 自动故障迁移(Automatic failover):当一个主服务器不能正常工作时,Sentinel会开始一次自动故障迁移操作。它会将失效主服务器的其中一个从服务器升级为新的主服务器,并让失效主服务器的其他从服务器改为复制新的主服务器。当客户端试图连接失效的主服务器时,集群也会向客户端返回新主服务器的地址,使得集群可以使用新主服务器代替失效服务器。
# 3. Sentinel集群
一个哨兵进程对Redis服务器进行监控,可能会出现问题。一般是使用多个哨兵进行监控,各个哨兵之间还会进行监控,形成多哨兵模式。
# Sentinel集群搭建
# 一键部署脚本
Centos
yum install -y gcc-c++ autoconf automake
cd /usr/local/
wget http://download.redis.io/redis-stable.tar.gz
tar xvzf redis-stable.tar.gz
cd redis-stable
make
firewall-cmd --zone=public --add-port=26379/tcp --permanent
firewall-cmd --reload
echo "redis installed"
2
3
4
5
6
7
8
9
Ubuntu版本
sudo apt-get update
sudo apt-get install -y g++ autoconf automake
cd /usr/local/
sudo wget http://download.redis.io/redis-stable.tar.gz
sudo tar xvzf redis-stable.tar.gz
cd /usr/local/redis-stable
sudo make
sudo ufw allow 26379
sudo ufw reload
echo "redis installed"
2
3
4
5
6
7
8
9
10
# 节点信息
- Sentinel-1:31.110(leader)
- Sentinel-2:31.111
- Sentinel-3:31.112
- Master:31.102
- Slave1:31.103
- Slave2:31.104
# Sentinel配置及启动
注意:默认Sentinel默认开放26379端口
- Sentinel-1配置及启动
sudo tee /usr/local/redis-stable/sentinel-1.conf <<-'EOF'
port 26379
bind 0.0.0.0
daemonize yes
logfile "./sentinel-1.log"
dir "./"
sentinel monitor mymaster 192.168.31.102 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel auth-pass mymaster 123456
sentinel failover-timeout mymaster 30000
EOF
./src/redis-server ./sentinel-1.conf --sentinel
tail -f sentinel-1.log
2
3
4
5
6
7
8
9
10
11
12
13
- 配置项说明
sentinel monitor
:当有2个Sentinel节点无法访问192.168.31.102:6379 ,就客观认为该Master不可用,将master做下线处理,并进行故障转移(failover)。mymaster是Sentinel分组名称,同一组sentinel必须设置相同的名字mymaster。sentinel down-after-milliseconds
:当ping超过5000毫秒没响应,则主观判定为下线。sentinel failover-timeout
:当超过30秒还没有完成故障转移,则认为故障转移失败。sentinel auth-pass
:连接到masterauth时设置的密码。
- Sentinel-2配置及启动
sudo tee /usr/local/redis-stable/sentinel-2.conf <<-'EOF'
port 26379
bind 0.0.0.0
daemonize yes
logfile "./sentinel-2.log"
dir "./"
sentinel monitor mymaster 192.168.31.102 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel auth-pass mymaster 123456
sentinel failover-timeout mymaster 30000
EOF
./src/redis-server ./sentinel-2.conf --sentinel
tail -f sentinel-2.log
2
3
4
5
6
7
8
9
10
11
12
13
- Sentinel-3配置及启动
sudo tee /usr/local/redis-stable/sentinel-3.conf <<-'EOF'
port 26379
bind 0.0.0.0
daemonize yes
logfile "./sentinel-3.log"
dir "./"
sentinel monitor mymaster 192.168.31.102 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel auth-pass mymaster 123456
sentinel failover-timeout mymaster 30000
EOF
./src/redis-server ./sentinel-3.conf --sentinel
tail -f sentinel-3.log
./src/redis-server ./sentinel-3.conf --sentinel
redis-cli -p 26379 info
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 集群 docker compose 版本
version: '3'
services:
master:
image: redis:7.0-alpine
command: redis-server --appendonly yes --requirepass 123456
ports:
- "6379:6379"
networks:
- redis-net
volumes:
- ./master-data:/data
slave1:
image: redis:7.0-alpine
command: redis-server --appendonly yes --replicaof master 6379 --masterauth 123456
ports:
- "6380:6379"
networks:
- redis-net
volumes:
- ./slave1-data:/data
slave2:
image: redis:7.0-alpine
command: redis-server --appendonly yes --replicaof master 6379 --masterauth 123456
ports:
- "6381:6379"
networks:
- redis-net
volumes:
- ./slave2-data:/data
sentinel1:
image: redis:7.0-alpine
command: >
redis-sentinel
--sentinel monitor mymaster master 6379 2
--sentinel auth-pass mymaster 123456
--sentinel down-after-milliseconds mymaster 5000
--sentinel failover-timeout mymaster 30000
--bind 0.0.0.0
--dir /data
ports:
- "26379:26379"
networks:
- redis-net
volumes:
- ./sentinel1-data:/data
depends_on:
- master
sentinel2:
image: redis:7.0-alpine
command: >
redis-sentinal
--sentinel monitor mymaster master 6379 2
--sentinel auth-pass mymaster 123456
--sentinel down-after-milliseconds mymaster 5000
--sentinel failover-timeout mymaster 30000
--bind 0.0.0.0
--dir /data
ports:
- "26380:26379"
networks:
- redis-net
volumes:
- ./sentinel2-data:/data
depends_on:
- master
sentinel3:
image: redis:7.0-alpine
command: >
redis-sentinel
--sentinel monitor mymaster master 6379 2
--sentinel auth-pass mymaster 123456
--sentinel down-after-milliseconds mymaster 5000
--sentinel failover-timeout mymaster 30000
--bind 0.0.0.0
--dir /data
ports:
- "26381:26379"
networks:
- redis-net
volumes:
- ./sentinel3-data:/data
depends_on:
- master
networks:
redis-net:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
sudo tee /usr/local/redis-stable/sentinel-1.conf <<-'EOF'
port 26379
bind 0.0.0.0
daemonize yes
logfile "./sentinel-1.log"
dir "./"
sentinel monitor mymaster 124.222.168.33 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel auth-pass mymaster 123456
sentinel failover-timeout mymaster 30000
EOF
cd /usr/local/redis-stable
./src/redis-server ./sentinel-1.conf --sentinel
tail -f sentinel-1.log
2
3
4
5
6
7
8
9
10
11
12
13
14
# Sentinel实现原理
# 哨兵实现原理
# 一、哨兵模式的基本工作流程
Redis在运行时会开启一个哨兵进程,主要负责监控实例、选举主实例、通知其他实例新的主实例的工作。
- 监控实例:判断实例是否正常,主要通过哨兵的监控。它会周期性地给所有的实例发送PING命令,如果实例没有在对应的时间响应,那么哨兵就会把该实例标记为下线状态。如果该实例为主实例,那么哨兵在把该实例标记为下线状态后,开始进行重新选举主实例的工作。
- 选举主实例:主实例挂了后,会由哨兵进行重新选举主实例的工作,哨兵会根据具体的规则和算法选择一个健康的从实例作为新的主实例。具体的规则在下文中会提到。
- 通知实例:选举完实例后,哨兵会通知其他实例谁是新的主实例。哨兵主要通过将新的主实例的连接信息发送给其他从实例,在从库中执行replicaof命令以此来成为新主库的从库,并从主库中进行数据复制。另外,哨兵也会把新的主库的信息同步给客户端,让客户端把新的请求操作发送给新主库。
# 二、判断实例下线
实例的下线状态分为主观下线和客观下线。哨兵会通过PING命令检测所有的实例,没有响应的实例就会被哨兵标记为主观下线状态。如果该实例为从库,哨兵会直接将它标记为主观下线。 如果实例为主库,在单哨兵模式的情况下,该主库会被哨兵直接标记为主观下线,然后开始新主库的选举工作。如果是哨兵集群模式,需要多个哨兵一起判断该主库是否无响应,如果超过一定值的哨兵实例判断该主库为主观下线,那么这个主库就正式被标记为客观下线,开始进行新主库的选举工作。哨兵集群模式会在下文进行分析。
# 三、选举新主库
- 筛选:选举新主库的第一步会进行筛选操作,主要是为了筛选出正常运行且运行良好的从库,目的是为了防止选举出来的新主库又由于网络故障等原因导致哨兵又得重新选举新出库的现象。所以要对这些从库当前的在线状态和之前的网络状态进行筛选。 筛选规则:配置项sentinel.conf中的down-after-milliseconds表示设置的主从库断连的最大连接超时时间,默认为30秒。如果在30秒内,主从节点都没通过网络连接和响应并且发生的次数超过了10次,就说明该从库的网络状况不好,不适合作为新主库。
- 打分:接下来需要对剩余的从库进行打分,打分的目的是通过规则来选举出分数最高的从库作为新主库。
- 第一轮打分:配置项slave-priority最高的从库得分最高。通过从库配置的优先级来进行打分,默认都是100,如果有一个从库的优先级最高,那么该从库就是新的主库了,不需要进行第二轮打分比拼。如果当前打分都一样,那么进行第二轮打分。
- 第二轮打分:和原主库数据同步进度最接近的从库得分最高。主从复制之间存在增量复制缓冲区(repl_backlog_buffer),可以用于当从库出现闪断恢复后将闪断前的数据恢复到从库的操作。repl_backlog_buffer是一个环形缓冲区,并且有2个指针表示主库写的位置和从库读的位置分别是master_repl_offset和slave_repl_offset。判断从库和原主库同步进度就是通过这两个指针的位置来判断,只有从库的slave_repl_offset最接近master_repl_offset位置,表示同步进度最接近,得分就最高,获得最高分数的从库就可以被选举作为新主库。如果还是存在相同打分情况的从库,那么就会进入下一轮打分选举。
- 第三轮打分:从库id最小的得分最高。第三轮应该属于兜底的选举场景,只有第一轮和第二轮选举时的分数都完全一样时,才会进入第三轮打分。通过从库中的id来进行比较,id最小的从库得分最高,就会被选举为新主库。
# 四、哨兵模式弊端
哨兵主要的工作在于监控、选举和通知。但是单个哨兵模式也会有一定的弊端和问题,比如:
- 单个哨兵虽然可以判断主库主管下线,但是否可以减少误判情况?
- 这个哨兵如果挂了,redis的主库选举和切换该如何工作?
因此在实际应用中,我们可以选择哨兵集群的方式来进行部署,多个哨兵之间通过少数服从多数的原则来进行判断工作。
# 五、哨兵集群判断实例下线
上文在第二段“判断实例下线”中提到的主观下线和客观下线。主观下线是指某个哨兵通过ping的响应超时来判断主实例下线状态,而客观下线是指超过一定数量的哨兵实例都认为主库已下线,那么该主库就是客观下线状态。而客观下线一般存在哨兵集群中。
- 哨兵集群如何投票判断主库下线
- 哨兵集群中某一个实例判断主库为主观下线后,就会给其他哨兵实例发送is-master-down-by-addr命令。
- 其他哨兵实例接收到命令后,会根据自己和主库的连接情况作出响应,投出赞成票或反对票。
- 判断该主库为主观下线的哨兵实例如果获得了一定数值的赞成票,就会将该主库标记为客观下线。这里赞成票的阈值可配,可通过sentinel.conf中的quorum配置数量决定。
- 判断主库为客观下线的哨兵会向其他哨兵发送命令,表示由他自己来执行主从切换。该哨兵想成为执行主从切换的主哨兵,得由其他哨兵赞成才行,并且赞成数量得大于一半的哨兵数量。
- 如果赞成数量小于一半的哨兵数量,就不会操作主从切换,哨兵集群会等待一段时间再重新选举主哨兵进行主从切换。
# 六、哨兵集群判断实例下线详细工作过程
假设有三个哨兵S1、S2、S3,并且quorun配置数量为2。
- S1判断主库为主观下线状态,向其他哨兵发送is-master-down-by-addr命令,并且其他哨兵也同意主库为主观下线,S1将主库判断为客观下线开始进行主从切换。
- 同时,S2也判断主库为主观下线状态,并且也向其他哨兵发送is-master-down-by-addr命令,其他哨兵也同意主库为主观下线,S2也将主库判断为客观下线开始进行主从切换。
- 判断主库为客观下线的S1,想成为主哨兵,向其他哨兵发送命令,表示想成为主哨兵。
- 判断主库为客观下线的S2,也想成为主哨兵,向其他哨兵发送命令,表示想成为主哨兵。
- S3收到了S1想成为主哨兵的命令,由于S3没有投过票,所以会返回同意。S2收到了S1想成为主哨兵的命令,由于S2自己投给了自己,所以会返回不同意。此时S1已经获得了2票同意票,赞成票大于一半的哨兵数量,可成为主哨兵,进行主从切换。
- 后续S3收到了S2想成为主哨兵的命令,由于S3已经将票投给了S1,所以会返回不同意。S1也收到了S2想成为主哨兵的命令,由于S1自己投给了自己所以也返回不同意。此时S2只获得了1票同意票,赞成票小于一半的哨兵数量,不能成为主哨兵。
- 最后,如果S1获得的同意票小于一半的哨兵数量,会导致S1和S2的选举结果不会产生主哨兵,哨兵集群会等待一段时间再重新选举。
# 七、哨兵集群的通信
哨兵集群的通信机制,主要是通过redis提供的pub/sub机制,即发布/订阅机制。 哨兵如果想要在主库上发布消息,需要和主库建立连接。主库中有一个订阅频道“sentinel:hello”的频道,不同的哨兵之间就是通过这个默认频道来进行发布/订阅通讯。 比如,假设S1的ip和端口分别是192.168.23.01和6379,哨兵S2和哨兵S3是这样与S1建立网络连接的:
- 哨兵S1通过+sentinel sentinel 192.168.23.01:6379命令将自己的ip和端口发布到“sentinel:hello”频道中。
- 哨兵S2和哨兵S3由于已经订阅了该频道,因此可以获取这个订阅消息(+sentinel sentinel 192.168.23.01:6379),获取到S1的ip和端口。
- 哨兵S2和哨兵S3和哨兵S1建立网络连接。
- 任何一个哨兵可以通过向主库发送INFO命令获得所有主库对应的从库信息,和从库进行连接并进行监控。
- 其余的哨兵也是根据相同的INFO命令和从库进行连接。
- 至此,哨兵之间组成了集群并进行通信。哨兵也和主从库之间建立了连接并进行监控。
# 八、哨兵和客户端的通信
哨兵不仅需要和主从库之间进行通信,还需要和客户端进行连接通信,因为如果主库宕机后通过哨兵选举出来的新主库的信息也需要推送给客户端。
哨兵和客户端的通信实际上也是基于发布/订阅机制来进行的。
比如,当哨兵把新主库选举出来后,客户端可以收到switch-master事件(switch-master <master name> <old ip> <oldport> <new ip> <new port>)
,来表示主库已经切换。客户端就可以使用该事件中的新ip和新端口信息来进行与新主库的通信。
哨兵和客户端通信事件的一些重要频道:
- +sdown(实例进入主观下线状态)
- -sdown(实例退出主观下线状态)
- +odown(实例进入客观下线状态)
- -odown(实例退出客观下线状态)
- +swtich-master(主库地址发生变化)
- +slave-reconf-sent(哨兵发送SLAVEOF命令重新配置从库)
- +slave-reconf-inprog(从库配置了新主库,但尚未同步)
- +slave-reconf-done(从库配置了新主库,和新主库完成同步)
# 九、总结
aof和rdb的存在保证了数据的持久性。redis集群模式的存在,是数据可靠的基础保证。而哨兵模式的存在,是redis高可用的保证,即在主库发生故障时可通过选举和主从切换来保证redis服务不间断的可用性。
# 问题一
假设一个redis集群,是一主四从,同时配置了5个哨兵实例的集群,quorum值设为2。在运行过程中,如果有3个哨兵实例都发生故障了,此时,redis主库如果故障,还能正确地判断主库客观下线吗?如果可以的话,还能进行主从库自动切换吗? 首先哨兵判断主库客观下线是通过quorum值来进行的,只有认为主库主管下线的哨兵数量大于等于quorum值时,该哨兵才可以判断主库为客观下线。所以redis主库如果故障,仍可以正确的判断主库客观下线。 在哨兵集群中想要进行主库切换,需要选举出一个主哨兵来进行主从切换,但是主哨兵的选举的要求是同意该哨兵为主哨兵的赞成票要大于哨兵数量的一半,由于可运行哨兵只有2个了,所以无法选举主哨兵,无法进行主从自动切换。
# 问题二
哨兵实例是不是越多越好? 不一定,哨兵实例越多,虽然可以减少误判率。但是在判断主库客观下线和选举主哨兵时,该哨兵实例所需要拿到的赞成票也会越来越多,所以总体投票的时间也会增加,导致整体主从切换的时间变长,导致客户端堆积较多请求操作,导致请求溢出造成请求丢失。
# 问题三
如果调大down-after-milliseconds值,对减少误判是不是也有好处? 不一定,调大的结果可能会导致实际上已经发生故障的主库,由于值的调大导致哨兵过了down-after-milliseconds值的时间才能判断出来,反而会影响redis对业务的可用性。