redis中的哨兵
- 一、哨兵机制的概念
- 二、redis哨兵的部署
- 2.1 docker的安装
- 2.2 编排redis主从节点
- 2.3 配置哨兵节点
- 三、redis哨兵的选举机制
- 3.1 redis-master宕机之后的情况
- 3.2 重启redis-master后的情况
- 四、redis哨兵机制的原理
- 4.1主观下线
- 4.2客观下线
- 4.3选举leader节点
- 4.4选出合适的从节点
- 4.5哨兵机制小结
Redis 的主从复制模式下,⼀旦主节点由于故障不能提供服务,需要⼈⼯进⾏主从切换,同时⼤量的客⼾端需要被通知切换到新的主节点上,对于上了⼀定规模的应⽤来说,这种⽅案是⽆法接受的,于是 Redis 从 2.8 开始提供了 Redis Sentinel(哨兵)来解决这个问题
一、哨兵机制的概念
Redis Sentinel 相关名词解释 :
名词 | 逻辑结构 | 物理结构 |
---|---|---|
主节点 | redis主服务 | 一个独立的redis-server进程 |
从节点 | redis从服务 | 一个独立的redis-server进程 |
redis数据节点 | 主从节点 | 主节点和从节点的进程 |
哨兵节点 | 监控redis数据节点的节点 | 一个独立的redis-sentinel进程 |
哨兵节点集合 | 若干哨兵节点的抽象组合 | 若干redis-server进程 |
redis哨兵(sentinel) | redis提供的高可用方案 | 哨兵节点集合和redis主从节点 |
应用方 | 泛指一个或多个客户端 | 一个或多个连接redis的进程 |
Redis Sentinel 是 Redis 的⾼可⽤实现⽅案,在实际的⽣产环境中,对提⾼整个系统的⾼可⽤是⾮常有帮助的,本节⾸先整体梳理主从复制模式下故障处理可能产⽣的问题,⽽后引出⾼可⽤的概念,最后重点分析 Redis Sentinel 的基本架构、优势,以及是如何实现⾼可⽤的
哨兵⾃动恢复主节点故障 :
提供多个sentinel哨兵进程监控现有的redis master和slave。这些进程之间会建立tcp长连接,通过这样的长连接,定期发送心跳包。然后就可发现监控的节点是否挂了。
-
如果从节点挂了,那么哨兵节点不予理会,认为对服务没有什么影响。
-
如果一个哨兵发现主节点挂了,那么其它哨兵也会进行判断主节点是否挂了,如果超过法定票数个哨兵都认为主节点挂了,那么此时就会在这些从节点中,挑选一个节点作为新的主节点,挑选出的新节点,哨兵会自动控制该节点执行slaveof no one让其成为主节点,并且控制其他从节点,修改slaveof到新的主节点上。
-
最后哨兵会自动通知客户端程序,告知新的主节点是谁,让客户端后续在进行写操作时,能够在新的主节点上操作,当原来的主节点恢复,执⾏
slaveof {newMasterIp} {newMasterPort}
让其成为⼀个从节点
补充:一般哨兵节点会弄奇数个。
二、redis哨兵的部署
2.1 docker的安装
部署多个节点在一台主机上是非常麻烦的,所以这里使用docker来进行演示。
在Linux上安装docker可看这篇博客
2.2 编排redis主从节点
上述操作必须保证⼯作⽬录在yml的同级⽬录中, 才能⼯作
1.配置文件
在配置redis主从节点之前,先创建一个redis-slave文件夹,然后进入该文件夹,写一下配置文件docker-compose.yml:
version: '3.7' #版本号
services: #表示启动的服务器master: #服务名image: 'redis:5.0.9' #容器镜像名称container_name: redis-master #自定义名称(也可以理解为ip地址)restart: always #表示总是自动重启command: redis-server --appendonly yes #启动redis时启动的选项ports:- 6379:6379 #端口映射 前面是宿主机端口号 后面是虚拟机的端口号slave1: #服务名image: 'redis:5.0.9'container_name: redis-slave1restart: alwayscommand: redis-server --appendonly yes --slaveof redis-master 6379ports:- 6380:6379slave2: #服务名image: 'redis:5.0.9'container_name: redis-slave2restart: alwayscommand: redis-server --appendonly yes --slaveof redis-master 6379ports:- 6381:6379
补充:端口映射
docker容器,可以理解为一个轻量的虚拟机,在这个容器里,端口号和外面的宿主机的端口号是两个体系。如果容器外面使用了5000端口,在容器内部也可以使用5000端口,有时候希望在容器外面能够访问到容器内部的端口号,所以就把容器内部的端口和容器外面的端口进行映射。
容器使用相同的端口号不会冲突吗?
每个容器内部的端口号是不会互相影响的,可以把每个容器理解为一个独立的主机
2.执行指令,启动该配置文件中的docker容器
sudo docker-compose up -d
如果启动后发现前⾯的配置有误, 需要重新操作, 使⽤ docker-compose down 即可停⽌并删除刚才创建好的容器.
sudo docker-compose down
3.查看一下是否成功启动:
sudo docker ps -a
netstat -anp | grep -E '6379|6380|6381'
4.查看运⾏⽇志
sudo docker-compose logs
现在客户端就可以连接redis服务器了。
redis-cli -p 6379
redis-cli -p 6380
redis-cli -p 6381
补充:
也可以把 redis-sentinel 放到和上⾯的 redis 的同⼀个 yml 中进⾏容器编排. 此处分成两组, 主要是为了两⽅⾯:
-
观察⽇志⽅便
-
确保 redis 主从节点启动之后才启动 redis-sentinel. 如果先启动 redis-sentinel 的话, 可能触发额外的选举过程, 混淆视听. (不是说先启动哨兵不⾏, ⽽是观察的结果可能存在⼀定随机性)
2.3 配置哨兵节点
1.回到上级目录,新创一个文件夹sentinel,然后进到该文件夹,创建docker-compose.yml文件,然后配置:
version: '3.7'
services:sentinel1:image: 'redis:5.0.9'container_name: redis-sentinel-1restart: alwayscommand: redis-sentinel /etc/redis/sentinel.confvolumes:- ./sentinel1.conf:/etc/redis/sentinel.confports:- 26379:26379sentinel2:image: 'redis:5.0.9'container_name: redis-sentinel-2restart: alwayscommand: redis-sentinel /etc/redis/sentinel.confvolumes:- ./sentinel2.conf:/etc/redis/sentinel.confports:- 26380:26379sentinel3:image: 'redis:5.0.9'container_name: redis-sentinel-3restart: alwayscommand: redis-sentinel /etc/redis/sentinel.confvolumes:- ./sentinel3.conf:/etc/redis/sentinel.confports:- 26381:26379
networks:default:external:name: redis-slave_default #加入到的局域网名,后面解释
2.创建 sentinel1.conf sentinel2.conf sentinel3.conf . 三份⽂件的内容是完全相同的
bind 0.0.0.0
port 26379
sentinel monitor redis-master redis-master 6379 2
sentinel down-after-milliseconds redis-master 1000
理解 sentinel monitor:
sentinel monitor 主节点名 主节点ip 主节点端⼝ 法定票数
- 主节点名:这个是哨兵内部⾃⼰起的名字
- 主节点 ip:部署 redis-master 的设备 ip. 此处由于是使⽤ docker, 可以直接写 docker 的容器名, 会被⾃动 DNS 成对应的容器 ip
- 主节点端⼝:主节点的端口号
- 法定票数:哨兵需要判定主节点是否挂了. 但是有的时候可能因为特殊情况, ⽐如主节点仍然⼯作正常, 但是哨兵节点⾃⼰⽹络出问题了, ⽆法访问到主节点了. 此时就可能会使该哨兵节点认为主节点下线, 出现误判. 使⽤投票的⽅式来确定主节点是否真的挂了是更稳妥的做法. 需要多个哨兵都认为主节点挂了, 票数 >= 法定票数 之后, 才会真的认为主节点是挂了
理解 sentinel down-after-milliseconds
主节点和哨兵之间通过⼼跳包来进⾏沟通. 如果⼼跳包在指定的时间内还没回来, 就视为是节点出现故障
既然内容相同, 为啥要创建多份配置⽂件?
redis-sentinel 在运⾏中可能会对配置进⾏ rewrite, 修改⽂件内容. 如果⽤⼀份⽂件, 就可能出现修改混乱的情况
3.启动该配置文件中的docker容器
sudo docker-compose up -d
如果启动后发现前⾯的配置有误, 需要重新操作, 使⽤ docker-compose down 即可停⽌并删除刚才创建好的容器
4.查看运⾏⽇志
docker-compose logs
出现“WARNING: Sentinel was not able to save the new configuration on disk!!!: Is a directory”错误的原因通常是因为文件权限问题。 在Docker中部署Redis Sentinel时,如果配置文件没有正确的读写权限,Sentinel就无法保存新的配置到磁盘上
修改配置文件的权限:确保配置文件有适当的读写权限。可以通过以下命令修改权限:
sudo chmod 777 ./sentinel1.conf ./sentinel2.conf ./sentinel3.conf
这会将配置文件的权限设置为可读、可写、可执行,确保Sentinel能够正常保存配置
出现Reading the configuration file, at line 3 redis-sentinel-2 | >>> ‘sentinel monitor redis-master redis-master 6379 2’
redis-sentinel-2 | Can’t resolve master instance hostname.错误时,表示无法正确解析到主节点的ip地址
主要原因docker启动容器的时候,会把容器分配在不同的虚拟局域网中,此时主从节点和哨兵节点不在同一个虚拟局域网中,所以也就解析不了主节点的ip地址了。
可通过如下指令查看当前docker容器有哪些虚拟局域网:
docker network ls
主从节点与哨兵节点不在同一个局域网中,此时需要修改用于启动哨兵节点的配置文件docker-compose.yml文件。将虚拟局域网部分改为
networks:default:external:name: redis-slave_default
然后执行docker-compose down 命令将对应的哨兵节点关闭移除掉,然后在调用docker-compose up -d创建启动哨兵节点:
#关闭移除哨兵节点
sudo docker-compose down
#创建启动哨兵节点
sudo docker-compose up -d
哨兵节点启动成功后的日志
也可以通过如下指令,查看单个哨兵的日志:
#查看docker容器中启动的容器,可以得到哨兵节点的名称,ID
sudo docker ps -a
#得到sentinel名称,ID以后
sudo docker logs 容器的名称(或ID)
5.观察 redis-sentinel 的配置 rewrite
再次打开哨兵的配置⽂件, 发现⽂件内容已经被⾃动修改了
#配置文件信息
bind 0.0.0.0
port 26379
sentinel myid ccdae87d81bf00f23ca6d4ab869571ef046be71f
sentinel deny-scripts-reconfig yes
# Generated by CONFIG REWRITE
dir "/data"
sentinel monitor redis-master 172.18.0.3 6379 2
sentinel down-after-milliseconds redis-master 1000
sentinel config-epoch redis-master 0
sentinel leader-epoch redis-master 0
sentinel known-replica redis-master 172.18.0.4 6379
sentinel known-replica redis-master 172.18.0.2 6379
sentinel known-sentinel redis-master 172.18.0.6 26379 3b77e18de5fb4ce0bc214f6aa2665b77429a80f2
sentinel known-sentinel redis-master 172.18.0.5 26379 0ccfa9143d636099702b86b36daee36275818d1b
sentinel current-epoch 0
三、redis哨兵的选举机制
3.1 redis-master宕机之后的情况
为了让主节点宕机,我们可以手动把它关掉:
sudo docker stop redis-master
观察哨兵的⽇志:
可以看到哨兵发现了主节点 sdown, 进⼀步的由于主节点宕机得票达到 3/2 , 达到法定得票, 于是 master 被判定为 odown
-
主观下线 (Subjectively Down, sdown): 哨兵感知到主节点没⼼跳了. 判定为主观下线
-
客观下线 (Objectively Down, odown): 多个哨兵达成⼀致意⻅, 才能认为 master 确实下线了
接下来, 哨兵们挑选出了⼀个新的 master. 在上图中, 是 172.18.0.2:6379
这个节点
此时, 对于 Redis 来说仍然是可以正常使⽤的
3.2 重启redis-master后的情况
⼿动把 redis-master 启动起来:
sudo docker start redis-master
观察哨兵⽇志,可以看到刚才新启动的redis-master 被当成了 slave
可以登录该客户端执行验证:
redis-cli -p 6379
然后执行写的命令:
set key 'hello'
此时的redis-master(从节点)已经没有了写的权限。
总结:
-
Redis 主节点如果宕机, 哨兵会主观的认为主节点下线了,当主观认为主节点下线的票数超过一半以后,哨兵节点就会客观认为主节点下线了。
-
然后哨兵节点会进行投票选举出一个leader哨兵节点(票数需要大于等于二分之一),来让从节点变成主节点。
-
当之前的 Redis 主节点重启之后, 这个主节点被加⼊到哨兵的监控中, 但是只会被作为从节点使⽤.
四、redis哨兵机制的原理
假定当前环境如上⽅介绍, 三个哨兵(sentinel1, sentinel2, sentinel3), ⼀个主节点(redis-master), 两个从节点(redis-slave1, redis-slave2),当主节点出现故障, 就会触发⼀系列过程
4.1主观下线
当 redis-master 宕机, 此时 redis-master 和三个哨兵之间的⼼跳包就没有了,此时,站在三个哨兵的⻆度来看,redis-master 出现严重故障,因此三个哨兵均会把 redis-master 判定为主观下线 (sdown)
4.2客观下线
此时, 哨兵 sentinel1, sentinel2, sentinel3 均会对主节点故障这件事情进⾏投票. 当认为主节点下线的票数 >= 配置的法定票数
之后,此时意味着 redis-master 故障这个事情被做实了,此时触发客观下线 (odown)
配置的法定票数
sentinel monitor redis-master 172.22.0.4 6379 2
在我们写哨兵节点的三个配置文件的时候,此处所写的2就是法定票数
4.3选举leader节点
接下来需要哨兵把剩余的 slave 中挑选出⼀个新的 master. 这个⼯作不需要所有的哨兵都参与. 只需要选出个代表 (称为 leader), 由 leader 负责进⾏ slave 升级到 master 的提拔过程,这个选举的过程涉及到 Raft 算法
假定⼀共三个哨兵节点, S1, S2, S3,选举过程如下:
- 每个哨兵节点都给其他所有哨兵节点, 发起⼀个 “拉票请求”. (S1 -> S2, S1 -> S3, S2 -> S1, S2 -> S3, S3 -> S1, S3 -> S2)
- 收到拉票请求的节点, 会回复⼀个 “投票响应”. 响应的结果有两种可能, 投 or 不投,每个节点只有一次投票机会,投了就不能再投了
- ⼀轮投票完成之后, 发现得票超过法定票数的节点, ⾃动成为 leader。如果出现平票的情况 (S1 投 S2, S2 投 S3, S3 投 S1, 每⼈⼀票), 就重新再投⼀次即可,这也是为啥建议哨兵节点设置成奇数个的原因. 如果是偶数个, 则增⼤了平票的概率, 带来不必要的开销.
- leader 节点负责挑选⼀个 slave 成为新的 master. 当其他的 sentenal 发现新的 master 出现了, 就说明选举结束了
简⽽⾔之, Raft 算法的核⼼就是 “先下⼿为强”. 谁率先发出了拉票请求, 谁就有更⼤的概率成为 leader,这⾥的决定因素成了 “⽹络延时”. ⽹络延时本⾝就带有⼀定随机性,所以哪个节点成为leader是不确定的,当然这也是无关紧要的,成为leader的目的就是为了提拔一个从节点让其成为主节点。
4.4选出合适的从节点
从节点变成主节点的选举规则:
- ⽐较优先级. 优先级⾼(数值⼩的)的上位,优先级是配置⽂件中的配置项( slave-priority 或者 replica-priority )
- ⽐较 replication offset 谁复制的数据多,⾼的上位
- ⽐较 run id , 谁的 id ⼩, 谁上位
当某个 slave 节点被指定为 master 之后,
- leader 指定该节点执⾏ slave no one , 成为 master
- leader 指定剩余的 slave 节点, 都依附于这个新 master
4.5哨兵机制小结
上述过程, 都是 “⽆⼈值守” , Redis ⾃动完成的. 这样做就解决了主节点宕机之后需要⼈⼯⼲预的问题, 提⾼了系统的稳定性和可⽤性
⼀些注意事项:
-
哨兵节点不能只有⼀个,否则哨兵节点挂了也会影响系统可⽤性
-
哨兵节点最好是奇数个,⽅便选举 leader,得票更容易超过半数
-
哨兵节点不负责存储数据,仍然是 redis 主从节点负责存储
-
哨兵 + 主从复制解决的问题是 “提⾼可⽤性”, 不能解决 “数据极端情况下写丢失” 的问题
-
哨兵 + 主从复制不能提⾼数据的存储容量,当我们需要存的数据接近或者超过机器的物理内存,这样的结构就难以胜任了,此时就需要引入集群结构了。