Redis的分布式部署方案-哨兵

Redis 的主从复制模式下,⼀旦主节点由于故障不能提供服务,需要⼈⼯进⾏主从切换,同时⼤量 的客⼾端需要被通知切换到新的主节点上,对于上了⼀定规模的应⽤来说,这种⽅案是⽆法接受的, 于是 Redis 从 2.8 开始提供了 Redis Sentinel(哨兵)加个来解决这个问题。

哨兵机制的介绍

由于对 Redis 的许多概念都有不同的名词解释,所以在介绍 Redis Sentinel 之前,先对⼏个名词 概念进⾏必要的说明,如表所⽰。

|--------------------|--------------------|-------------------------|
| 名词 | 逻辑结构 | 物理结构 |
| 主节点 | Redis 主服务 | ⼀个独⽴的 redis-server 进程 |
| 从节点 | Redis 从服务 | ⼀个独⽴的 redis-server 进程 |
| Redis 数据节点 | 主从节点 | 主节点和从节点的进程 |
| 哨兵节点 | 监控 Redis 数据节点的节点 ⼀ | ⼀个独⽴的 redis-sentinel 进程 |
| 哨兵节点集合 | 若⼲哨兵节点的抽象组合 | 若⼲ redis-sentinel 进程 |
| Redis 哨兵(Sentinel) | Redis 提供的⾼可⽤⽅案 | 哨兵节点集合 和 Redis 主从节点 |
| 应⽤⽅ | 泛指⼀个多多个客⼾端 | ⼀个或多个连接 Redis 的进程 |

手动恢复redis主从复制的流程

哨兵机制,是通过独立的进程来体现的,和之前的redis-server是不同的进程

redis-sentinel不负责存储数据,只是对其他的redis-server进程起到监控的作用

手动恢复redis主从复制的流程

服务器,要求要有比较高的可用性,7*24运行;服务器长期运行,总会有一些"意外",具体啥时候出意外,咱们也不知道,同时也不能全靠人工来盯着服务器运行;我们可以写一个程序,用程序来盯着服务器的运行状态(监控程序,发现服务器的运行出现状态异常了;往往还需要搭配"报警程序")

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

如果操作过程出错了咋办,并且恢复的过程也得半个小时以上,这显然并不合适。

自动恢复redis主从复制的流程

监控:这些进程之间,会建立tcp长连接,通过这样的长连接,定期发送心跳包;借助上图的监控机制,就可以及时发现某个主机是否是挂了,如果是从节点挂了,其实没关系;如果是主节点挂了,哨兵就会发挥作用

Redis Sentinel 相⽐于主从复制模式是多了若⼲(建议保持奇数)Sentinel 节点⽤于实现监控数据节 点,哨兵节点会定期监控所有节点(包含数据节点和其他哨兵节点)。针对主节点故障的情况,故障转移流程⼤致如下:

  1. 主节点故障,从节点同步连接中断,主从复制停⽌
  2. 哨兵节点通过定期监控发现主节点出现故障。哨兵节点与其他哨兵节点进⾏协商,达成多数认同主 节点故障的共识。这步主要是防⽌该情况:出故障的不是主节点,⽽是发现故障的哨兵节点,该情况经常发⽣于哨兵节点的⽹络被孤⽴的场景下
  3. 哨兵节点之间使⽤ Raft 算法选举出⼀个领导⻆⾊,由该节点负责后续的故障转移⼯作
  4. 哨兵领导者开始执⾏故障转移:从节点中选择⼀个作为新主节点;让其他从节点同步新主节点;通知应⽤层(客户端)转移到新主节点。

redis哨兵核心功能:

  • 监控
  • 自动的故障转移
  • 通知

redis哨兵节点,有一个也是可以的,但是:

  1. 如果哨兵节点只有一个,它自身也是容易出现问题的,万一这个哨兵节点挂了,后续redis节点也挂了,就无法进行自动的恢复过程了
  2. 出现误判的概率也比较高,网络传输数据是容易出现抖动或者延迟或者丢包的,如果只有一个哨兵节点,出现上述问题之后,影响就比较大
  • 基本原则:在分布式系统中,应该避免使用"单点"
  • 哨兵节点,最好要搞奇数个,最少也应该是3个(和选举机制有关)

使用docker搭建环境

类似下图的结构,按理说这6个节点要在6个不同的服务器主机上的,此时只有一个云服务器,就在一个云服务器上,来完成这里的环境搭建(在工作中,把上述节点放到一个服务器上,是没有意义的)

由于这些节点有点多,相互之间依赖的端口号/配置文件/数据文件可能会冲突,如果直接部署,就需要小心翼翼的取避开这些冲突-使用docker就可以有效避免上述问题

虚拟机,通过软件,在一个电脑上模拟出另外的一些硬件(构造了另一个虚拟的电脑),虚拟机这样的软件,就可以使用一个电脑,来模拟出多个电脑的情况;但是虚拟机比较吃配置

docker可以认为是一个轻量级"虚拟机",起到了虚拟机这样的隔离环境的效果,但是又没有吃很多的硬件资源,即使是配置比较拉跨的云服务器,也能构造出好几个这样的虚拟环境

  • 安装docker和docker-compose

docker中关键概念"容器",每一个容器都可以看做一个轻量级的虚拟机

docker-compose 的安装

bash 复制代码
# ubuntu
apt install docker-compose
# centos
yum install docker-compose
  • 停⽌之前的 redis-server
bash 复制代码
# 停⽌ redis-server
service redis-server stop
# 停⽌ redis-sentinel 如果已经有的话.
service redis-sentinel stop
  • 使⽤ docker 获取 redis 镜像
bash 复制代码
 docker pull redis:5.0.9
#类似于git clone从远程仓库拉取代码

docker中的"镜像"和"容器"类似于"可执行程序"和"进程"的关系;镜像可以自己创建,也可以直接拿别人已经构建好的,docker hub(类似于Github)包含了很多其他大佬构建好的镜像

拉取到的镜像,里面包含一个精简的Linux操作系统,并且上面会安装redis,只要直接基于这个镜像创建一个容器跑起来,此时redis服务器就搭建好了

docker-compose来进行容器编排,此处涉及到多个redis-server也有多个redis哨兵节点,每一个redis-server或者每一个redis哨兵节点都是作为一个单独的容器了(6个容器)

通过一个配置文件,把具体要创建哪些容器,每个容器运行的各种参数,描述清楚,后续通过一个简单的命令,就能够批量的启动/停止这些容器了

使用yml这样的格式来作为配置文件,spring也是使用yml来作为配置文件的

  • 创建三个容器,作为redis的数据节点(一主两从)
  • 创建三个容器,作为redis的哨兵节点

其实也是可以用一个yml文件,直接启动6个容器;如果把这6个容器同时启动,可能是哨兵先启动完成,数据节点后启动完成,哨兵可能就会先认为是数据节点挂了,虽然对于大局不影响,但是会影响到观察执行日志的过程

bash 复制代码
version: '3.7'
services:
#名字是自己起的
 master:
#基于哪个镜像创建的
 image: 'redis:5.0.9'
 container_name: redis-master
#是否自动重启
 restart: always
 command: redis-server --appendonly yes
 ports:
#端口映射,也就是隧道
#也就是XShell中的隧道命令
#宿主机的端口:容器内部的端口
 - 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
 command: redis-server --appendonly yes --slaveof redis-master 6379
 ports:
 - 6381:6379
#这里格式似乎不太正确 请大家自行抄下面的


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
    command: redis-server --appendonly yes --slaveof redis-master 6379
    ports:
      - 6381:6379
  • 启动所有容器
bash 复制代码
docker-compose up -d
#-d表示后台运行

此步骤一定要把redis三个服务器彻底关闭,不然无法运行成功

​​​​​​

此处启动的redis就是容器中的redis

  • 查看运⾏⽇志
bash 复制代码
docker-compose logs

redis哨兵节点是单独的redis服务器进程

  • 在redis-sentienl中写docker-compose.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:
#映射文件,将配置文件映射到/etc/redis/sentinel.conf
#哨兵节点,会在运行过程中,对配置文件进行自动的修改,因此就不能拿一个配置文件,给三个容器分别映射
      - ./sentinel1.conf:/etc/redis/sentinel.conf
    ports:
#端口映射:docker核心功能
      - 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
  • 创建配置⽂件

创建 sentinel1.conf sentinel2.conf sentinel3.conf . 三份⽂件的内容是完全相同 的,都放到 /root/redis-sentinel/ ⽬录中.

bash 复制代码
bind 0.0.0.0
#允许其他节点来访问
port 26379
#容器内部端口号
sentinel monitor redis-master redis-master 6379 2
#让当前的哨兵节点监控哪个redis服务器,redis-master本来是IP,但是docker会自动进行域名解析
#6379为端口号,2是法定票数(达到法定票数就可以判定redis主节点挂了)
sentinel down-after-milliseconds redis-master 1000
#down-after-milliseconds明确心跳包的超时时间,设定1000ms之内,如果不返回,就认为挂了,此时投票

报错:哨兵节点不认识redis-master,redis-master相当于一个域名,docker会进行域名解析;docker-compose一下启动了N个容器,此时N个容器处于同一个"局域网"中,可以使这N个容器之间可以相互访问,三个redis-server节点是一个局域网,三个哨兵节点是另一个局域网,默认情况下,这俩网络不是互通的

解决方案:使用docker-compose 把此处的两组服务放到同一个局域网中

bash 复制代码
docker network ls
  • 在docker-compose.yml中加入配置局域网
bash 复制代码
networks:
  default:
    external:
      name: redis-data_default

Question:是否可以把6个容器,都写到同一个yml配置中,一次全都启动,不就保证互通问题了吗?


Answer:如果使用这种方案,由于docker-compose启动容器的顺序不确定,就不能保证redis-server一定是在哨兵之前启动的,分成两组就可以保证上述顺序,观察到的日志是比较可控的

在sentinel1.conf文件中产生了新的内容,这些内容都是哨兵节点启动之后,自动进行修改的

哨兵节点的作用演示

哨兵存在的意义,能够在redis主从结构出现问题的时候(比如主节点挂了),此时哨兵节点就能够自动的帮我们重新选出一个主节点,来代替之前挂了的节点,保证整个redis仍然是可用状态

手动把主节点干掉

当主节点挂了之后,哨兵节点开始工作

  • sdown:主观下线,本哨兵节点,认为该节点挂了
  • odown:客观下线,好几个哨兵都认为该节点挂了,达成了一致(法定票数)

此时,主节点挂了这个事情就被实锤了

此时就需要哨兵节点选出一个从节点,作为新的主节点,此处就需要提拔出一个新的主节点

即使原主节点启动,也是作为从节点

主从切换的具体流程

哨兵重新选取主节点的流程

  • 主观下线:哨兵节点通过心跳包,判定redis服务器是否正常工作,如果心跳包没有如约而至,就说明redis服务器挂了,此时还不能排除网络波动的影响,因此就只能是单方面认为这个redis节点挂了
  • 客观下线:多个哨兵都认为主节点挂了(认为挂了的哨兵节点数目达到法定票数),哨兵们就认为这个主节点是客观下线

Question:是否有可能出现非常严重的网络波动,导致所有的哨兵都联系不上redis主节点,误判成挂了呢?

当然是有的,如果出现这个情况,怕是用户的客户端也连不上redis主节点了,此时这个主节点基本也就无法正常工作了

  • 要让多个哨兵节点,选出一个leader,由这个leader负责选一个从节点作为新的主节点

日志中可以看到

三个哨兵节点id

  • 一号哨兵立即给自己投了一票
  • 二号哨兵也立即给了一号哨兵投了一票
  • 三号哨兵也立即给了二号哨兵投了一票
bash 复制代码
redis-sentinel-1  | 1:X 15 Aug 2024 04:10:18.300 # +vote-for-leader 8727c0b46d6aaaee1d5f00ef3ef100e81e73de34 1
redis-sentinel-1  | 1:X 15 Aug 2024 04:10:18.301 # bbd99057b11998a9d52786d2164eaecfaae64441 voted for bbd99057b11998a9d52786d2164eaecfaae64441 1
redis-sentinel-1  | 1:X 15 Aug 2024 04:10:18.308 # 72f040cb2884e70c8a5faa3c7d0baad4da53fce5 voted for 8727c0b46d6aaaee1d5f00ef3ef100e81e73de34 1

每个哨兵手里只有一票,当哨兵1第一个发现客观下线之后,就立即给自己投了一票,并且告诉2,3,我来负责这个事情;2 3反应慢了半拍,才发现是客观下线,一看1乐意负责这个事情,立即投了赞成票(如果总的票数超过哨兵总数的一半,选举完成了->把哨兵个数设置为奇数个节点,就是为了方便投票)

leader选主节点

  • 此时leader选举完毕,leader就需要挑选一个从节点,作为新的主节点
  1. 优先级:每个redis数据节点,都会在配置文件中,有一个优先级的设置,slave-priority,优先级高的从节点就会胜出
  2. offset:offset最大,就胜出,offset从节点从主节点这边同步数据的进度,数值越大,说明从节点的数据和主节点就越接近
  3. run id:每个redis节点启动的时候随机生成的一串数字(大小全凭缘分)

把新的主节点指定好了之后,leader就会控制这个节点,执行slaveof no one,成为master,再控制其他节点,执行slaveof,让这些其他节点,以新的master作为主节点了

先选leader,再选主节点

小结

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

⼀些注意事项

  • 哨兵节点不能只有⼀个. 否则哨兵节点挂了也会影响系统可⽤性
  • 哨兵节点最好是奇数个. ⽅便选举 leader, 得票更容易超过半数
  • 哨兵节点不负责存储数据. 仍然是 redis 主从节点负责存储
  • 哨兵 + 主从复制解决的问题是 "提⾼可⽤性", 不能解决 "数据极端情况下写丢失" 的问题
  • 哨兵 + 主从复制不能提⾼数据的存储容量. 当我们需要存的数据接近或者超过机器的物理内存,这样的结构就难以胜任了.
  • 为了能存储更多的数据, 就引⼊了集群.
相关推荐
pblh1239 分钟前
2023_Spark_实验十五:SparkSQL进阶操作
大数据·分布式·spark
李少兄42 分钟前
解决Spring Boot整合Redis时的连接问题
spring boot·redis·后端
日里安1 小时前
8. 基于 Redis 实现限流
数据库·redis·缓存
silver98861 小时前
分布式相关杂项
分布式
EasyCVR1 小时前
ISUP协议视频平台EasyCVR视频设备轨迹回放平台智慧农业视频远程监控管理方案
服务器·网络·数据库·音视频
Elastic 中国社区官方博客2 小时前
使用真实 Elasticsearch 进行更快的集成测试
大数据·运维·服务器·数据库·elasticsearch·搜索引擎·集成测试
明月与玄武2 小时前
关于性能测试:数据库的 SQL 性能优化实战
数据库·sql·性能优化
PGCCC4 小时前
【PGCCC】Postgresql 存储设计
数据库·postgresql
PcVue China6 小时前
PcVue + SQL Grid : 释放数据的无限潜力
大数据·服务器·数据库·sql·科技·安全·oracle
魔道不误砍柴功7 小时前
简单叙述 Spring Boot 启动过程
java·数据库·spring boot