【Redis】哨兵

🐼主从复制的问题

我们说redis既要保证效率,也要保证高可用性。单单使用主从复制做到了吗?从节点做到了,任意一个从节点挂了,其他的从节点仍然可以读,但是如果主节点挂了呢?那么这个redis集群就只能读了,再好长一段时间都不能写!直到redis主节点启动起来。并没有保证redis主节点的高可用性

如果主节点发生故障时,进行主备切换的过程是复杂的,需要完全的人⼯参与,导致故障恢复时间无法保障。

这个过程大概分为如下几步:

  1. 运维⼈员通过监控系统,发现 Redis 主节点故障宕机
  2. 运维⼈员从所有节点中,选择⼀个(此处选择了 slave 1)执行slaveof no one,使其作为新的主节点。
  3. 运维⼈员让剩余从节点(此处为 slave 2)执⾏ slaveof {newMasterIp} {newMasterPort} 从新主节点开始数据同步
  4. 更新应⽤方连接的主节点信息到 {newMasterIp} {newMasterPort}。
  5. 如果原来的主节点 恢复,执行slaveof {newMasterIp} {newMasterPort} 让其成为⼀个从节点
  6. 上述过程可以看到基本需要⼈⼯介⼊,⽆法被认为架构是⾼可用的。而这就是 Redis Sentinel 所要做的。

Redis 哨兵主要解决的问题就是redis的高可用 问题!


🐼什么是哨兵

首先什么是哨兵?就是一个进程!哨兵自动恢复主节点故障

哨兵的作用:当主节点出现故障时,Redis Sentinel 能自动完成故障发现和故障转移,并通知应用方,从而实现真正的高可⽤。

哨兵的结构:Redis Sentinel 是⼀个分布式架构,其中包含若干个 Sentinel 节点和 Redis 数据节点,每个 Sentinel 节点会对数据节点和其余 Sentinel 节点进⾏监控,当它发现节点不可达时,会对节点做下线表示。如果下线的是主节点,它还会和其他的 Sentinel 节点进行**"协商"** ,当大多数 Sentinel 节点对主节点不可达这个结论达成共识之后,它们会在内部 "选举" 出⼀个领导节点来完成自动故障转移的⼯作,同时将这个变化实时通知给 Redis 应用方。整个过程是完全自动的,不需要⼈⼯介⼊。整体的架构如图所示。

✅sentinel节点如何进行监控的呢?sentinel会和数据节点以及其他哨兵节点之间建立TCP长连接,以来定期发送tcp的心跳包。借助上面的监控机制,主节点就知道哪个节点挂了,哪个节点没有挂。

Redis Sentinel 相比于主从复制模式是多了若干个哨兵节点(建议保持奇数 )

如果从节点挂了,那么没有关系。

针对主节点故障的情况,故障转移流程大致如下:

如果主节点挂了,就是上面所说的协商和推举,这步主要是防止该情况:出故障的不是主节点,而是发现故障的哨兵节点,比如网络问题卡了,导致一个哨兵"误判",该情况经常发⽣于哨兵节点的⽹络被孤立的场景下。

然后在当前节点中,选出一个leader,即从众多从节点中选中一个节点作为主节点。让其他从节点同步新主节点;通知应用层(客户端)转移到新主节点。

✅为什么哨兵也要多个呢?避免单点,实现高可用,是reids的核心任务!


🐼实操部署哨兵节点

我们以上面数据节点集合为例,搞三个redis节点,一个作为主节点,2个从节点。另外搞三个哨兵节点。

按理来说,这6个哨兵节点是在不同的主机间的,他们之间是通过网络或者局域网通信的。但是我们这里由于资源限制,都搞在一个主机了(不过,这是毫无意义的,在实际开发中)。

这里会有一个问题,如果这6个节点在同一台主机上部署,可能会有端口号,配置文件,数据文件的冲突,而如果在6台主机上部署,不用考虑这么多乱七八糟的事情,那我们这么干不纯属是给自已找事嘛!

有没有一种方法,模拟出6台主机的效果?搞6个虚拟机?显然不太现实!毕竟很吃配置。

而docker虚拟化容器技术就能很好的解决上述的问题。他可以以很轻量化的配置就完成虚拟化效果。

这里就不得不谈一下docker中最重要的两个概念,镜像和容器。

镜像和容器就**类似于"可执行程序"和"进程"**之间的关系。我们可以根据一个"可执行程序"(镜像)启动很多个"进程"(容器)。

而镜像网上已经有很多现成的大佬们已经写好的了,包括redis镜像,我们拉取下来直接就能用

ok,介绍完以上。下面是安装redis镜像具体的操作了:

cpp 复制代码
准备⼯作 
1) 安装 docker 和 docker-compose (可以网上查一下部署教程)
2) 停⽌之前的 redis-server ,一定要停止,防止对现在的redis服务器产生干扰,比如占用了端口号
3) 使⽤ docker 获取 redis 镜像  docker pull redis:5.0.9
拉取到的镜像,里面有一个轻量化的Linux系统并且有redis,只要基于这个镜像创建一个redis容器,就算好了一个reids服务器

关于docker命令的使用敲一下docker

基于docker来搭建redis哨兵环境

我们使用docker-compose来统一容器编排,通过一个**.yml配置文件**,来配置批量化的容器,和容器的各种参数,后面我们仅需要一个命令,就能批量化的启动和停止这些容器。

什么是.yml文件,类似于json格式的,便于网络发送的,但相比于json,对格式要求更严格,可读性更好

下面,我们来启动3个redis服务器容器和三个redis哨兵容器。

.yml的编排这里我们就把redis一个主节点和2个从节点统一放到数据节点的.yml文件中了。

这里我们就把三个哨兵节点统一放到哨兵节点的.yml文件中了。

总共两个.yml文件。如图:

其实一个.yml文件完全可以启动6个容器,但是谁先起来,不好说,会影响看日志。

注意,.yml文件必须以docker-compose命名

先看数据节点的.yml文件

bash 复制代码
version: '3.7'

services:
  # Redis Master (主节点)
  master:
    image: 'redis:5.0.9'
    container_name: redis-master
    restart: always
    # 启动命令:开启 AOF 持久化
    command: redis-server --appendonly yes
    ports:
      - "6379:6379"

  # Redis Slave 1 (从节点 1)
  slave1:
    image: 'redis:5.0.9'
    container_name: redis-slave1
    restart: always
    # 启动命令:作为 master 的从节点
    command: redis-server --appendonly yes --slaveof redis-master 6379
    ports:
      - "6380:6379"
    # 依赖于 master 服务,确保 master 先启动
    depends_on:
      - master

  # Redis Slave 2 (从节点 2)
  slave2:
    image: 'redis:5.0.9'
    container_name: redis-slave2
    restart: always
    # 启动命令:作为 master 的从节点
    command: redis-server --appendonly yes --slaveof redis-master 6379
    ports:
      - "6381:6379"
    # 依赖于 master 服务
    depends_on:
      - master

这个文件内容很容易理解,也不需要我们记住。

唯一需要注意的两点:

1.端口号,为什么有两个?其中,第一个是**外面的的端口号,不能重复,里面的是容器内部的端口号,可以重复。**这样我们可以通过容器外的端口号访问到容器内的端口号。这有点向反向代理的感觉了,有点像内网穿透ftp的哪个例子了哈哈。(容器外的端口号像外网的ip,不能重复,容器内的像内网的ip,可以重复)

2.我们这里没有显示写redis-master的ip,这里的映射非常像NAT,容器名类似于域名一样,docker会自动解析,我们无需考虑了。

下面我们一次性批量化的启动所有redis数据节点容器:

cpp 复制代码
docker-compose up -d

如果启动后发现前面的配置有误, 需要重新操作, 使用docker-compose down 即可停止并删除

刚才创建好的容器.

查看运行⽇志

bash 复制代码
docker-compose logs

我们可以使用redis-cli连接不同的redis服务器了。


下面我们来配置哨兵节点,由于哨兵节点,会在运行过程中,对配置文件进行自动的修改,因此,就不能用一个配置文件,做三个哨兵的映射,我们需要创建三个哨兵的配置文件。

这三个哨兵的配置文件内容可以是一样的,内容为:

bash 复制代码
bind 0.0.0.0
port 26379
sentinel monitor redis-master 172.18.0.2 6379 2
sentinel down-after-milliseconds redis-master 1000

具体含义如下:

目录结构如图:

再看哨兵节点的yml文件:

bash 复制代码
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"

和数据节点的yml文件类似。

在哨兵的目录下加载哨兵的配置文件,启动三个哨兵节点:

cpp 复制代码
docker-compose up -d

发现启动失败,原因是哨兵节点,不认识redis-master,这是为啥?其实我们使用docker-compose启动的一批容器,其中redis服务器数据节点的容器属于一个局域网,而三个哨兵节点,属于另一个局域网,默认情况下,两个局域网是不通的

解决方案,可以使用docker-compose把两个局域网放到同一个局域网中。

我们先使用docker network ls列出当前docker中的局域网,

其中第一个是redis服务器容器创建的局域网,第二个是三个哨兵节点创建的局域网。现在我们只需要让哨兵节点加入redis服务器的局域网即可,而不是创建新的局域网。

在哨兵节点的.yml配置文件中加入以下语句:

cpp 复制代码
networks:
  default:
    external:
      name: redis-data_default
                     

再次启动,

可以看到, 哨兵节点已经通过主节点, 认识到了对应的从节点.

并且哨兵节点的配置文件自动发生了修改了。对⽐这三份⽂件, 可以看到配置内容是存在差异的.

最后注意,你要启动谁的.yml文件使用docker-compose,就去谁的目录下。此时docker-compose这个命令就执行的哪个配置文件。跟使用makefile得在Makefile的目录下同理。


🐼哨兵节点存在的意义

下面我们来看看哨兵节点存在的意义,到底一个主节点挂了,哨兵节点能不能自动 的给我们选举出一个主节点,来代替之前的主节点 ,保证redis仍然是高可用状态

手动把 redis-master 干掉。

docker stop redis-master

此时我们进入哨兵的配置目录,使用docker-compost logs来观察哨兵的日志:

这里以三号哨兵节点为例:

我们可以看到其中有两个字段,sdown和odown。

主观下线 (Subjectively Down, SDown): 哨兵感知到主节点没心跳了. 判定为主观下线.

客观下线 (Objectively Down, ODown): 多个哨兵达成⼀致意见, 才能认为 master 确实下线了

可是,有没有自动的选举出一个主节点代替挂了的主节点呢?

我们分别使用redis-cli连接剩余两个redis服务器,并使用info replication来查看节点信息。

并且6381可以完成写的任务。

如果我们把原本的 redis-master 启动起来,发现它会自动变成从节点了。

所以,依赖于哨兵,只要我们把配置文件配置好了,尽管其中一个主节点挂了,还可以用从节点代替主节点,而原本挂的主节点变为从节点,和新的主节点进行数据同步,实现高可用性,这就是哨兵的意义。


🐼哨兵重新选举主节点的流程

下面我们结合日志来说一下哨兵重新选举主节点的流程。

1.主观下线

当 redis-master 宕机, 此时 redis-master 和三个哨兵之间的⼼跳包就没有了.

此时, 站在三个哨兵的⻆度来看, redis-master 出现严重故障. 因此三个哨兵均会把 redis-master 判定为主观下线 (SDown)

2.客观下线

此时, 哨兵 sentenal1, sentenal2, sentenal3 均会对主节点故障这件事情进⾏投票. 当故障得票数 >= 配置的法定票数之后,就是我们在sentinel.conf中配置的。

此时意味着 redis-master 故障这个事情被做实了. 此时触发客观下线 (ODown)

3) 选举出哨兵的 leader

接下来需要哨兵把剩余的 slave 中挑选出⼀个新的 master. 这个⼯作不需要所有的哨兵都参与. 只需要选出个代表 (称为 leader), 由 leader 负责进⾏ slave 升级到 master 的提拔过程 .所以要先从哨兵选leader,由leader再决定!

这个选举的过程涉及到 Raft 算法。Raft 算法的核⼼就是 "先下⼿为强". 谁率先发出了拉票请求, 谁就有更大的概率成为 leader.这⾥的决定因素成了 "网络延时". 网络延时本⾝就带有⼀定随机性.具体选出的哪个节点是 leader, 这个不重要, 重要的是能选出⼀个节点即可.

leader 节点负责挑选⼀个 slave 成为新的 master. 当其他的 sentenal 发现新的 master 出现了, 就

说明选举结束了.

如果总的票数超过哨兵总数的一半,就算结束了。所以为啥哨兵的个数就是奇数,目的就是为了选举方便投票选leader。上面的投票过程,就是看谁的网络号,手速快。

4)leader 挑选出合适的 slave 成为新的 master

选举的规则如下:

⽐较优先级. 优先级高(数值小的)的上位. 优先级是配置⽂件中的配置项( slave-priority 或者replica-priority ).

  1. 比较 replication offset 谁复制的数据多, 高的上位.

  2. 比较 run id , 谁的 id 小, 谁上位.

当某个 slave 节点被指定为 master 之后,

  1. leader 指定该节点执⾏ slave no one , 成为 master

  2. leader 指定剩余的 slave 节点, 都依附于这个新 master

总结,上述过程, 都是 "⽆⼈值守" , Redis ⾃动完成的. 这样做就解决了主节点宕机之后需要⼈⼯⼲预的问题, 提⾼了系统的稳定性和可⽤性.

⼀些注意事项:

• 哨兵节点不能只有⼀个. 否则哨兵节点挂了也会影响系统可用性.

• 哨兵节点最好是奇数个. 方便选举 leader, 得票更容易超过半数.

• 哨兵节点不负责存储数据. 仍然是 redis 主从节点负责存储.

•选举的时候,不是直接选出主节点,而是先从哨兵节点中选出leader,再由leader来完成后续主节点的指定

• 哨兵 + 主从复制解决的问题是 "提⾼可⽤性", 不能解决 "数据极端情况下写丢失" 的问题.

• 哨兵 + 主从复制不能提⾼数据的存储容量. 当我们需要存的数据接近或者超过机器的物理内存, 这样的结构就难以胜任了.

为了能存储更多的数据, 就引⼊了集群.

相关推荐
小高不会迪斯科1 天前
CMU 15445学习心得(二) 内存管理及数据移动--数据库系统如何玩转内存
数据库·oracle
e***8901 天前
MySQL 8.0版本JDBC驱动Jar包
数据库·mysql·jar
l1t1 天前
在wsl的python 3.14.3容器中使用databend包
开发语言·数据库·python·databend
失忆爆表症1 天前
03_数据库配置指南:PostgreSQL 17 + pgvector 向量存储
数据库·postgresql
AI_56781 天前
Excel数据透视表提速:Power Query预处理百万数据
数据库·excel
SQL必知必会1 天前
SQL 窗口帧:ROWS vs RANGE 深度解析
数据库·sql·性能优化
Gauss松鼠会1 天前
【GaussDB】GaussDB数据库开发设计之JDBC高可用性
数据库·数据库开发·gaussdb
+VX:Fegn08951 天前
计算机毕业设计|基于springboot + vue鲜花商城系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
程序猿阿伟1 天前
《GraphQL批处理与全局缓存共享的底层逻辑》
后端·缓存·graphql
识君啊1 天前
SpringBoot 事务管理解析 - @Transactional 的正确用法与常见坑
java·数据库·spring boot·后端