Redis 哨兵

目录

[Redis 哨兵机制](#Redis 哨兵机制)

人工恢复主节点故障

哨兵自动恢复主节点故障

[Redis Sentinel 部署](#Redis Sentinel 部署)

[docker 安装](#docker 安装)

[Redis 数据节点](#Redis 数据节点)

[Redis 哨兵节点](#Redis 哨兵节点)

重新选举

[Sentinel 选举原理](#Sentinel 选举原理)

小结


Redis 哨兵机制

Redis 主从复制-CSDN博客 中,我们学习了 Redis 的主从复制模式 ,将主节点数据修改同步给从节点 ,从而让节点分担主节点上的读压力 ,并作为主节点的备份,保证数据尽量不丢失,且一旦主节点出现故障,可以在从节点中选出新主节点顶上,从而继续提供服务

但是在 Redis 主从复制模式 下,一旦主节点由于故障不能提供服务,需要我们手动进行主从切换,也就是需要从原有从节点中选出一个新的主节点,并修改其他从节点的 master,还需要通知客户端。

在此过程中,会出现以下问题:

  1. 若主节点宕机,从节点只能提供读服务,写服务完全中断,需要等待人工介入切换

  2. 当切换主节点时,客户端仍连接原有主节点,需要通知客户端新主节点地址

  3. 当主节点出现故障时,需要人工手动 ping监控脚本 判断主节点状态,在此过程中,网络抖动容易导致误判,且人工确认流程长 ,确认后,手动切换操作复杂( 需要在新主节点上执行 slaveof no one ,其他从节点上执行slave {newMasterIp} {newMasterPort} 以及 通知客户端)

基于上述问题,Redis 提供了哨兵(Sentinel)机制哨兵节点 作为独立进程运行 ,本身不存储数据 ,专门负责监控主从节点状态 ,并进行自动故障转移客户端服务发现

我们先来看Redis Sentinel中的不同节点:

节点 逻辑结构 物理结构
主节点 Redis 主服务,能够读写数据 一个独立的 redis-server 进程
从节点 Redis 从服务,只能读数据,不能写 一个独立的 redis-server 进程
数据节点 Redis 主从节点统称 主节点和从节点进程
哨兵节点 监控 Redis 主从节点的节点 一个独立的 redis-sentinel 进程

接下来,我们先来看人工恢复主节点故障流程

人工恢复主节点故障

Redis 主从复制模式下,主节点故障后需要进行人工切换主节点,这个过程比较繁琐:

  1. Redis 主从节点正常提供服务,处理客户端读写命令:
  1. 主节点出现故障,从节点继续对外提供读服务,写服务完全中断:
  1. 运维人员通过监控系统发现 master 节点故障宕机,在从节点中选择一个执行 slaveof no one,让其成为新主节点:
  1. 运维人员在其他从节点上执行 slaveof {newMasterIp} {newMasterPort},从新主节点上同步数据:

5.更新客户端连接的主节点信息为 {newMasterIp} {newMasterPort}

  1. 对原主节点进行故障排查和修复,节点恢复后,执行 **slave {newMasterIp} {newMasterPort}**让其成为一个从节点:

可以看到,上述人工切换操作复杂,且流程较长,在切换过程中,无法对外提供写服务

接下来,我们来看 Redis Sentinel是如何自动恢复主节点故障的

哨兵自动恢复主节点故障

Redis Sentinel能够在主节点出现故障时,自动完成故障发现和故障转移,并通知应用方,从而实现真正的高可用

Redis Sentinel 是一个分布式架构, 其中包含了若干个 Sentinel 节点数据节点 ,每个 Sentinel 节点会对数据节点和其他 Sentinel 节点进行监控,当它发现某个节点不可达时,将其标记为下线状态 ,若下线的是主节点 ,会和其他的 Sentinel 节点进行协商 ,当超过一定数量的 Sentinel 节点都认为主节点不可达时,Sentinel 节点之间选举出一个 leader节点来完成自动故障转移工作

上述整个过程是完全自动的,不需要人工介入处理,整体架构图为:

Redis Sentinel 相比于主从复制模式多了 若干 Sentinel 节点,Sentinel节点会监控数据节点和其他哨兵节点,当主节点出现故障时,会进行故障转移:

  1. 主节点故障,主节点停止对外提供服务,从节点同步连接中断,主从复制停止

  2. 哨兵节点发现主节点出现故障,与其他哨兵节点进行协商 ,达成主节点故障的共识(防止单个哨兵节点在网络抖动情况下出现误判)

  3. 哨兵节点之间 选举出一个 leader节点来完成自动故障转移工作

  4. 哨兵 leader 节点开始进行故障转移:

a. leader 在从节点中选择一个作为新主节点,并向其发送 slaveof no one,让其解除从属关系,晋升为主节点

b. leader 向其他从节点发送 slaveof {newMasterIp} {newMasterPort},同步新主节点

c. 更新内部状态,通知其他 Sentinel 同步更新 master 地址

更新后 Sentinel 并不会主动推送新 master 地址给客户端 ,而是当客户端发现旧 master 不可达(捕获连接异常或命令超时)时,会从配置的 Sentinel 中随机/轮询选择一个 Sentinel,

发送SENTINEL get-master-addr-by-name mymaster 查询 Master 地址,获取地址后连接新 Master

Redis Sentinel 部署

我们部署1 个主节点,2个从节点,3 个哨兵节点,构成以下架构:

由于设备限制,在这里,我们使用 docker来进行部署

docker 安装

首先安装 docker

java 复制代码
# 1. 更新包索引并安装 curl 依赖
apt-get update
apt-get install -y ca-certificates curl gnupg
# 2. 添加 Docker 官方 GPG 密钥
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
# 3. 配置 Docker 仓库地址
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  tee /etc/apt/sources.list.d/docker.list > /dev/null
# 4. 再次更新包索引(这一步不做,系统不知道刚才加的仓库里有新软件)
apt-get update
# 5. 安装 Docker
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# 6. 配置加载
systemctl daemon-reload
# 7. 启动服务
systemctl start docker
# 8. 开启启动
systemctl enable docker
# 9. 查看服务状态
systemctl status docker

查看 docker 安装结果:

接着,安装 docker-compose

java 复制代码
apt install docker-compose

停止之前的 redis-server 服务

java 复制代码
service redis-server stop

然后使用 docker 获取 Redis 镜像

java 复制代码
docker pull redis:5.0.9

接下来,我们先部署 Redis 数据节点,再部署 Redis 哨兵节点

Redis 数据节点

创建 Redis 数据节点目录 (如 /root/redis/redis-data),并在目录下创建docker-compose.yml 文件,并在文件中配置主从节点信息:

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

启动所有容器:

java 复制代码
docker-compose up -d

若启动后发现配置存在问题,需要进行修改,可以使用 docker-compose down来停止并删除刚才创建好的容器

查看运行日志:

java 复制代码
docker-compose logs

我们连接主节点,查看其状态:

主节点成功启动

再分别连接从节点,查看其状态:

主节点和从节点都启动成功,接下来,我们继续部署哨兵节点

Redis 哨兵节点

创建Redis 哨兵节点目录 (如 /root/redis/redis-sentinel),并在目录下创建docker-compose.yml 文件,并在文件中配置哨兵节点信息

此外,为了让哨兵节点和数据节点位于同一局域网,我们先查看 docker 中的Redis 数据节点的局域网:

java 复制代码
docker network ls

并在 docker-compose.yml 中进行配置:

java 复制代码
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 # 数据节点的局域网名称

我们在当前目录下创建sentinel1.conf、sentinel2.conf、sentinel3.conf,三份文件内容相同:

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

其中:

bind 0.0.0.0:监听所有网卡

port 26379:sentinel 端口

sentinel monitor redis-master redis-master 6379 2 :第一个redis-maste r 是集群别名 ,第二个redis-masterDocker 的服务名 (docker compose 网络自带DNS 解析,当 Sentinel 启动时,它会将 redis-master 域名解析为 Master 容器的真实 IP),2 是 Quorum 法定人数,也就是说必须至少有 2 个 Sentinel 认为 Master 挂了,才会触发故障转移

sentinel down-after-milliseconds redis-master 1000:主观下线时间为 1 秒,主节点和哨兵之间通过心跳包来进行沟通,若心跳包未在指定时间回来,则任务主节点出现故障。在这里设置的时间非常快,我们后续手动下线主节点,就可立即观察到故障转移

启动所有容器:

java 复制代码
docker-compose up -d

查看运行日志:

java 复制代码
docker-compose logs

Redis Sentinel 集群启动成功,我们查看 sentinel1.conf文件:

sentinel1.conf文件已经被 sentinel 自动重写了,可以看到哨兵节点已经通过主节点认识到对应的从节点,且哨兵节点自动发现了其他哨兵节点

重新选举

我们手动停掉 master 节点:

java 复制代码
docker stop redis-master

观察哨兵日志:

主节点下线后,哨兵节点进行故障检测和选举:

+sdown master redis-master 172.19.0.4 6379 :Sentinel-3 发现 Master 1 秒(配置的 down-after-milliseconds )无响应,标记为SDOWN(主观下线)

+new-epoch 1:配置版本升级,开始新一轮故障转移

+vote-for-leader f09664216f387dee1430d3b8fb4e802daf8c8a5e 1:Sentinel-3 投票给 Sentinel-1作为 Leader 来执行切换

+odown master redis-master 172.19.0.4 6379 #quorum 3/2 :有 3 个 Sentinel 参与投票,2 个同意下线,达到了 quorum 阈值,确认 Master 真的挂了。

故障转移:

+config-update-from sentinel f09664216f... :Sentinel-3 从**Leader (Sentinel-1)**那里收到了配置更新

+switch-master redis-master 172.19.0.4 6379 172.19.0.3 6379 :切换成功,旧 master 地址 172.19.0.4 6379,新 master 地址 172.19.0.3 6379,原 slave2 晋升为新 master

+slave slave 172.19.0.2:6379 ... @ redis-master 172.19.0.3 6379:原 Slave 1(172.19.0.2) 继续作为从节点,但主节点指向了新 Master(172.19.0.3)

+slave slave 172.19.0.4:6379 ... @ redis-master 172.19.0.3 6379:原 master(172.19.0.4)被降级为从节点,主节点指向了新 Master(172.19.0.3)

+sdown slave 172.19.0.4:6379 ...:原 master(172.19.0.4)被降级后,Sentinel-3 认为它又 SDOWN(主观下线)了,这是因为我们手动停止了原 master 容器,还没重启

可以看到哨兵节点发现了主节点故障,进行主观下线 sdown ,并与其他哨兵节点进行协商,进一步主节点宕机票数达到 3 票,大于法定票数 2 票,于是 master 被认定为客观下线 odown

**主观下线:**哨兵感知到主节点没心跳,判定为主观下线

**客观下线:**多个哨兵达成一致意见,认为 master 确实下线

接下来,我们手动重启原master:

java 复制代码
docker start redis-master

观察哨兵日志:

可以看到原master重启后,被加入到哨兵的监控中,但此时作为从节点使用

我们也可以使用 redis-cli 连接查看原主节点信息:

和新主节点信息:

Sentinel 选举原理

我们来看上述 Redis 主节点出现故障后,触发的一系列过程:

1. 主观下线 :master 节点宕机,此时 master 与三个哨兵之间的心跳包就无响应。三个哨兵认为 master 出现严重故障,并将其判定为主观下线(SDown)

2. 客观下线 :哨兵会向所有已知的其他 Sentinel 发送协商命令,其他 Sentinel 收到请求后,会检查查自身监控状态(是否也认为 Master 不可达),并回复是否认为 master 故障,若故障票数 >= 配置的法定票数,说明 master 节点确实故障,触发客观下线(ODown)

3. 选举出哨兵 leader :Sentinel 之间通过发送SENTINEL is-master-down-by-addr命令来互相拉票,判断谁有资格当选 leader,主要看其中的两个指标:

  1. Config Epoch (配置纪元): 集群的配置版本号。每次发生切换,纪元 +1,纪元大者胜(代表数据/状态更新)

  2. RunID (运行标识): Sentinel 启动时生成的唯一随机 ID(40位 SHA1),纪元相同时,RunID 小者胜(保证确定性,防死锁)

先比纪元(谁大谁赢),纪元平局比 ID(谁小谁赢)

我们通过一个示例来看选举的详细流程:

假设此时有 3 个哨兵节点,S1,S2,S3:

S1 发现 Master 客观下线,且自己想做 Leader。它会向 S2,S3 发送拉票命令:我是 Sentinel A,我认为我是新 Leader,我的纪元是 5,我的 RunID 是 xxx..,请投票给我

其他哨兵节点(如 S2)收到请求后,会进行以下检查:

  1. 检查是否已投票

若 S2 在当前纪元已经投过票了(比如投给了 S3),则拒绝再次投票

若未投过票,则进入下一步判断

  1. 检查纪元:

若 S1 的纪元 > S2 的纪元,S2 认为 S1 的更新,投票给 A

若 S1 的纪元 < S2 的纪元,S2 认为 S1 过期了,拒绝投票

若 S1 的纪元 = S2 的纪元,进入下一步判断

  1. 检查 RunID

若 S1 的RunID < S2 的RunID,投票给 A

若 S1 的RunID > S2 的RunID,拒绝投票(S2 认为自己更合适)

一轮投票完成后,若 S1 收到的赞成票 > 总 Sentinel 数 / 2 (即绝对多数,注意这里总票数,不是 quorum,例如5 个节点,必须拿到 3 票),得到大多数节点同意后,S1 正式成为 leader

但是,若出现平票的情况,就需要重新再投一次 ,因此,哨兵节点最好设置为奇数个,若是偶数个,就会增大平票的概率,导致无法达成共识,进而开启新一轮投票,带来不必要的开销

例如,若此时有 4 个哨兵节点,就很可能会出现 2: 2 的票数对半开情况,也就是说没有任何候选者获得 >50% 的票 ,导致当前轮次(Epoch)超时仍未选出 Leader,此时 Sentinel 会自动将 epoch +1,清空上一轮的投票状态,所有节点基于新纪元重新发起拉票

而在进入新一轮投票 时,由于 Redis Sentinel 内部实现了随机延迟(Randomized Delay)机制, 即不同节点的超时时间不同,进入新一轮的时间也就不同,epoch +1 的时间自然也就不同**,** 那么,就能确保一个 Sentinel 率先拿到发起权 ,其他节点看到新 Epoch 后自动退居投票者

也就是说,即使第一轮出现 2:2 分裂,进入第二轮(Epoch+1)后,遵循 "先到先得 + RunID 择优 " 原则投票,投票结果也能快速收敛,最终凑齐多数派,这个过程不需要人工介入,但会消耗额外的故障转移时间(通常多几秒到十几秒),因此我们还是更推荐使用奇数节点部署

4. leader 节点进行故障转移:leader 节点会挑选一个 slave 作为新 master,在挑选时遵循以下原则:

  1. 比较优先级, 比较配置文件中的配置项 slave-priority 或 replica-priority ,**优先级高(配置数值小)**的上位

  2. 比较replication offset ,谁的偏移量大(该节点同步更完整,数据丢失少),谁上位

  3. 比较RunID ,谁的RunID 小,谁上位(作为确定性兜底,确保即使前两项完全相同,也能选出唯一节点)

当 leader 选出最优 slave 后,立即执行:

1. 向新 Master 发送晋升命令

redis-cli -h <new-master-ip> -p 6379 SLAVEOF NO ONE

2. 等待新 Master 回复 +OK

3. 更新 Sentinel 内部状态,广播**+promoted-slave** 事件,通知新主节点挑选成功

4. 向其他 Slave 发送SLAVEOF <new-master> <port> 重定向

小结

可以看到,Redis Sentinel自动完成了主节点故障检测和故障转移,无需人工干预,提高了系统的稳定性和可用性

但需要注意的是:

  1. 哨兵节点最好不止一个,否则哨兵节点挂了也会影响系统的可用性

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

  3. Redis Sentinel 解决的问题是 提高可用性,哨兵节点不负责存储数据,因此不能提高数据的存储容量

要想存储更多的数据,需要引入集群,我们在下一篇文章中继续学习Redis 集群

相关推荐
arronKler1 小时前
数据库设计三大范式
数据库·oracle
敲代码的嘎仔1 小时前
力扣高频SQL基础50题详解
开发语言·数据库·笔记·sql·算法·leetcode·后端开发
jran-1 小时前
MySQL多表操作 查询&子查询&外键约束
数据库·mysql
橙子圆1231 小时前
Redis知识6之事务
数据库·redis·缓存
不会摸鱼的小鱼1 小时前
WSL 安装 Ubuntu 22.04 到指定磁盘
数据库·postgresql·php
m0_702036531 小时前
mysql如何导出特定条件的查询数据_使用mysqldump加where参数
jvm·数据库·python
正在走向自律2 小时前
标量子查询消除:数据库优化器的一场“等价变戏法”
数据库·sql 优化·金仓数据库·数据库性能调优·标量子查询·数据库优化器
逻极2 小时前
SQLite 从入门到精通:深入理解嵌入式数据库的艺术与科学
数据库·sqlite·记忆·sqlite从入门到精通
未来之窗软件服务2 小时前
数据库优化(九)随机抽选系统数据表 ——东方仙盟
大数据·数据库·数据库优化·仙盟创梦ide·东方仙盟