Redis 的主从复制模式下,⼀旦主节点由于故障不能提供服务,需要⼈⼯进⾏主从切换,同时⼤量的客⼾端需要被通知切换到新的主节点上,对于上了⼀定规模的应⽤来说,这种⽅案是⽆法接受的,于是 Redis 从 2.8 开始提供了 Redis Sentinel(哨兵)加个来解决这个问题。本章主要内容如下:
Redis Sentinel 的概念Redis Sentinel 的部署Redis Sentinel 命令Redis Sentinel 客⼾端Redis Sentinel 实现原理哨兵机制,是通过独立的进程来体现的,和之前的 redis server 是不同的进程
![[Pasted Image 20250323223036_347.png]]
redis-sentinel:不负责存储数据,只是对其他的 redis-server 进程起到监控作用在实际开发中,对于服务器后端开发,监控程序是非常必要的
7*24 运行往往还需要搭配“报警程序”

运维人员通过监控系统,发现 redis 主节点故障宕机,程序员如何恢复?
slaveof no one,自立山头slaveof 的主节点 ip port,连上新的主节点只要是涉及到人工干预,不说繁琐,至少很烦人。另外,这个操作过程如果出错了的话,可能会导致问题更加严重。通过人工干预的做法,就算程序员第一时间看到了报警信息,第一时间处理,也需要消耗较长时间

redis sentinel 进程(部署在三台不同的服务器上)redis master 和 slave TCP 长连接,通过这样的长连接,定期发送心跳包leader,由这个 leader 负责从现有的节点中,挑选一个作为新的主节点slaveof no one,并且控制其他从节点,修改 slaveof 到新的主节点上
redis哨兵的核心功能:
redis 哨兵节点,有一个也是可以完场上述的需求的
但是:
redis 节点也挂了,就无法进行自动的恢复过程了
哨兵节点最好是奇数个,所以最少也应该是 3 个
基本的原则:在分布式系统中,应该避免使用“单点”(冗余)
我们搭建的结构:

按理说,这六个节点是要要六个不同的服务器主机上。此时我们只有一个服务器,我们就在一个服务器上,来完成这里的环境搭建
由于这些节点,还挺多的,相互之间容易大家,依赖的端口号/配置文件/数据文件… 如果我们直接部署,就需要小心翼翼的去避免这些冲突
虚拟机:通过软件,在电脑上模拟出另外的一些硬件(构造了另一个虚拟的电脑)
相比之下,使用 docker 就可以有效的解决上述问题。
docker 可以认为是一个“轻量级”的虚拟机,起到了虚拟机这样的隔离环境的效果,但是又没有吃很多的硬件资源。即使是配置比较拉胯的服务器,也能构造出好几个这样的虚拟的环境
https://www.runoob.com/docker/macos-docker-install.html
docker 和 docker-compose
检查是否安装docker --version
docker-compose --versionredis 服务器避免之后出现一些冲突
docker 获取到 redis 的镜像docker 中的“镜像”和“容器”类似于“可执行程序“和“进程“的关系
docker hub(github) 包含了很多其他大佬们构建好的镜像,也提供了 redis 官方提供的镜像,可以直接拖下来使用获取 redis 镜像的命令:
docker pull redis:5.0.9git pull 使用 git 从中央仓库拉取代码docker pull 使用 docker 从中央仓库(默认就是从 docker hub)来拉取镜像redis:5.0.9 是镜像的版本拉取到的镜像,里面包含一个精简的 Linux 操作系统,并且上面会安装 redis。只要直接基于这个镜像创建一个容器跑起来,此时,redis 服务器就搭建好了

docker 的镜像,大小为 92.9MB随后我们就基于这个 docker 镜像,搭建 redis 哨兵环境
如果遇到网络问题,就要换一下国内的镜像仓库,加速
{
"registry-mirrors": [
"https://registry.docker-cn.com",
"http://hub-mirror.c.163.com",
"https://docker.mirrors.ustc.edu.cn",
"https://dockerhub.azk8s.cn",
"https://mirror.ccs.tencentyun.com",
"https://registry.cn-hangzhou.aliyuncs.com",
"https://docker.mirrors.ustc.edu.cn",
"https://docker.1panel.live",
"https://atomhub.openatom.cn/",
"https://hub.uuuadc.top",
"https://docker.anyhub.us.kg",
"https://dockerhub.jobcher.com",
"https://dockerhub.icu",
"https://docker.ckyl.me",
"https://docker.awsl9527.cn"
]
}此处我们直接用 docker-compose 来进行容器编排
redis server,也有多个 redis 哨兵节点,每一个 redis server 或者每一个 redis 哨兵节点都是作为一个单独的容器了docker 手动创建容器,就比较麻烦,相比来说是用“容器编排”的方式就比较合理容器编排就是,通过一个配置文件,把具体要创建哪些容器,每个容器运行的各种参数描述清楚。后续通过一个简单的命令,就能够批量的启动/停止这些容器了
yml 这样的格式来作为配置文件经典的配置文件格式:xml
<student>
<id>1</id>
<name>张三</name>
<age>18</age>
</student><> 成对出现的,就叫做标签html 中的标签,都是标准规定的xml 里面的标签都是自定义的 后来又有了 JSON
{
id: 1,
name: '张三',
age: 18
}yml 格式和 json 有一些相似之处,yml 虽然没有 json 这么火,但是还是挺广泛的
student:
id: 1
name: "张三"
age: 18json 都是这种比较直观的键值对结构,json 是使用 {} 来表示层级结构,yml 则是使用缩进来表示yml 相对于 json 的优势:对于格式要求更严格,可读性会更好,更有助于人来理解
分为两组 yml,先后启动
我们其实也可以用于一个 yml 文件,直接启动 6 个容器,但是:
redis 的数据节点(一主两从)

将下面的配置复制进去
version: '3.7'
services:
master:
image: 'redis:5.0.9'
container_name: redis-master
restart: always
command: redis-server --appendonly yes
ports:
- 6379:6379
slave1:
image: 'redis:5.0.9'
container_name: redis-slave1
restart: always
command: redis-server --appendonly yes --slaveof redis-master 6379
ports:
- 6380:6379
slave2:
image: 'redis:5.0.9'
container_name: redis-slave2
restart: always
commandcommand: redis-server --appendonly yes --slaveof redis-master 6379
ports:
- 6381:6379sevices:我们将要启动哪几个容器 master、slave1、slave2 (这些名字自定义)image:当前的容器是基于哪个镜像创建的container_name:容器名字restart:异常情况终止后,是否重启command:在启动 redis 服务器的时候,所用到的命令行选项 slaveof 后面不必写主节点 ip,直接写主节点的容器名就行了ip 是什么,也不知道ports:端口映射,宿主机:容器内部端口 docker 容器可以理解成一个轻量的虚拟机。在这个容器里,端口号和外面宿主机的端口号,是两个体系。如果容器外面用了个 5000 端口,容器内也能用 5000 端口,并不冲突6379 和容器 2 的 6379 之间是不会有冲突的这种映射过程,就非常像
NAT
NAT 功能的路由器,这个路由器能管理好几个局域网里面的机器NAT 设备上的某个端口,从而访问到局域网内部的某个设备的某个端口了docker-compose up -d


6379 端口的 redis 服务器之后,可以看到这是个主节点,下面还有两个从节点
redis 的哨兵节点redis 哨兵节点,是单独的 redis 服务器进程

在哨兵节点的配置文件中,粘贴下面的配置
version: '3.7'
services:
sentinel1:
image: 'redis:5.0.9'
container_name: redis-sentinel-1
restart: always
command: redis-sentinel /etc/redis/sentinel.conf
volumes:
- ./sentinel1.conf:/etc/redis/sentinel.conf
ports:
- 26379:26379
sentinel2:
image: 'redis:5.0.9'
container_name: redis-sentinel-2
restart: always
command: redis-sentinel /etc/redis/sentinel.conf
volumes:
- ./sentinel2.conf:/etc/redis/sentinel.conf
ports:
- 26380:26379
sentinel3:
image: 'redis:5.0.9'
container_name: redis-sentinel-3
restart: always
command: redis-sentinel /etc/redis/sentinel.conf
volumes:
- ./sentinel3.conf:/etc/redis/sentinel.conf
ports:
- 26381:26379volumes:在当前目录下创建出 sentinel1.conf、sentinel2.conf、sentinel3.conf 这三个配置文件,然后把这三个配置文件映射到即将创建的这三个容器中 port:docker 的一个核心功能,能够进行端口的映射,容器内部乐意使用什么端口都行,映射出去之后,还是需要确保端口不能重复我们看一下这三个配置文件的具体细节
bind 0.0.0.0
port 26379
sentinel monitor redis-master redis-master 6379 2
sentinel down-after-milliseconds redis-master 1000bind:绑定一个 ip 地址,允许其他端口进行访问port:主节点端口号sentinel monitor:告诉 redis 节点,去监控哪个服务器 ip,但是使用 docker,docker 会自动进行域名解析redis 是否挂了,超过法定票数个哨兵意见一致,才确认挂了(更稳健,避免网络波动的影响造成误判)sentinel down-after-milliseconds:心跳包的超时时间。超过超时时间之后包还没回来,就认为是挂了

启动容器后,我们使用命令:
docker-compose logsredis-master) redis-master 相当于一个域名,docker 会进行域名解析,将其解析成对应容器的 ip(ip 可能会变,但是容器名字是不变的)使用 docker-compose 一下,启动了 N 个容器,此时 N 个容器都处于同一个“局域网”中
redis-server 节点,是一个局域网;三个哨兵节点,是另一个局域网解决方案:
可以使用 docker-compose 把此处的两组服务放到同一个局域网中
docker network ls 列出当前 docker 中的局域网
redis server 节点,就相当于自动创建了第一个局域网。再启动后面三个哨兵节点,就直接让这三个节点加入到上面的局域网中,而不是创建新的局域网在刚刚的 yml 配置文件中,最后再加上
networks:
default:
external:
name: redis-data_default完整配置为:
version: '3.7'
services:
sentinel1:
image: 'redis:5.0.9'
container_name: redis-sentinel-1
restart: always
command: redis-sentinel /etc/redis/sentinel.conf
volumes:
- ./sentinel1.conf:/etc/redis/sentinel.conf
ports:
- 26379:26379
sentinel2:
image: 'redis:5.0.9'
container_name: redis-sentinel-2
restart: always
command: redis-sentinel /etc/redis/sentinel.conf
volumes:
- ./sentinel2.conf:/etc/redis/sentinel.conf
ports:
- 26380:26379
sentinel3:
image: 'redis:5.0.9'
container_name: redis-sentinel-3
restart: always
command: redis-sentinel /etc/redis/sentinel.conf
volumes:
- ./sentinel3.conf:/etc/redis/sentinel.conf
ports:
- 26381:26379
networks:
default:
external:
name: redis-data_default
使用 docker network inspect redis-data_default 命令,可以查看 redis-data_default 网络被哪些容器连接

上述的操作,就完成了此处的配置
我们再打开 sentinel.conf 这三个配置问价,发现里面发生了变化
bind 0.0.0.0
port 26379
sentinel myid f1fd960a20a1ba19973fb8fee67b666e00b85f27
sentinel deny-scripts-reconfig yes
# Generated by CONFIG REWRITE
dir "/data"
sentinel monitor redis-master 172.18.0.2 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.3 6379
sentinel known-replica redis-master 172.18.0.4 6379
sentinel known-sentinel redis-master 172.18.0.7 26379 3437afc8086d37ea3204d3522a0640db2028e045
sentinel known-sentinel redis-master 172.18.0.6 26379 f1fd960a20a1ba19973fb8fee67b666e00b85f27
sentinel known-sentinel redis-master 172.18.0.5 26379 a6a389363f12cbb25085a22680575d297589c46a
sentinel current-epoch 0哨兵存在的意义:能够在 redis 主从结构出现问题的时候(比如主节点挂了),此时哨兵节点就能帮我们选出一个主节点,来代替之前挂了的节点,保证整个 redis 仍然是可用状态
查看各节点状态 docker ps -a

我们模拟一下主节点挂了的情况。手动干掉主节点 docker stop redis-master


redis-master 是一个 sdown 状态,于是开始投票 sdown:主观下线,本哨兵认为,主节点挂了(单方面)+odown 状态 +odown:客观下线,好几个哨兵都认为,主节点挂了,达成一致(法定票数)switch:进行切换,从 0.2 切换到了 0.4我们尝试练一下原来的主节点,端口号为:6379

再连一下 6380

再连一下 6381

我们重启原来的主节点 redis-master 之后

#高频面试
哨兵节点通过心跳包,判定 redis 服务器是否正常工作。如果心跳包没有如约而至,就说明 redis 服务器挂了
此时还不能排除网络波动的影响,因此就只能是单方面认为这个 redis 节点挂了
多个哨兵都认为主节点挂了。认为挂了的哨兵数目达到法定票数,哨兵们就认为这个主节点是客观下线
是否可能出现非常严重的网络波动,导致所有的哨兵都联系不上
redis主节点,误判成挂了呢?
redis 主节点了“挂了”不一定是进程崩了,只要是无法正常访问,都可以视为是挂了
leader让多个哨兵节点,选出一个 leader 节点,由这个 leader 负责选出一个从节点作为新的主节点

sentinel1 的编号(在 conf 配置文件里面可以看到)leader
leader 是要为大家服务的,当 1 号哨兵说他想为大家服务的时候,2 和 3 可能是纷纷举手赞成的(谁发现客观下线,谁就先给自己投)上面投票过程,就是看谁反应快(网络延时小)
此时 leader 选举完毕,leader 就要挑选一个从节点,作为新的主节点
redis 数据节点,都会在配置文件中,有一个优先级的设置
slave-priority,不去改就都是一样的offset:优先级一样,谁的 offset 更大,就胜出
run id:优先级和 offset 都一样,选 run id 更小的(随机)
redis 节点启动的时候随机生成的一串数字,大小全凭缘分把新的主节点指定好之后,leader 就会控制这个节点,执行 slave no one,成为 master,再控制其他节点,执行 slave of,让这些其他节点,以新的 master 作为主节点了
尤其是注意选举的过程。不是直接选出新的主节点,而是先选
leader,再由leader负责后续的主节点指定
leader,得票更容易超过半数redis 主从节点负责存储