Redis - 哨兵(Sentinel)

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

  • RedisSentinel的概念
  • RedisSentinel的部署
  • RedisSentinel命令
  • RedisSentinel客⼾端
  • RedisSentinel实现原理

一、基本概念

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

Redis Sentinel 相关名词解释

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

Redis Sentinel 是Redis 的⾼可⽤实现⽅案,在实际的⽣产环境中,对提⾼整个系统的⾼可⽤是⾮常有 帮助的,本节⾸先整体梳理主从复制模式下故障处理可能产⽣的问题,⽽后引出⾼可⽤的概念,最后 重点分析RedisSentinel的基本架构、优势,以及是如何实现⾼可⽤的。

1.1、主从复制的问题

Redis 的主从复制模式可以将主节点的数据改变同步给从节点,这样从节点就可以起到两个作⽤: 第⼀,作为主节点的⼀个备份,⼀旦主节点出了故障不可达的情况,从节点可以作为后备"顶"上 来,并且保证数据尽量不丢失(主从复制表现为最终⼀致性)。第⼆,从节点可以分担主节点上的读 压⼒,让主节点只承担写请求的处理,将所有的读请求负载均衡到各个从节点上。

  1. 主节点发⽣故障时,进⾏主备切换的过程是复杂的,需要完全的⼈⼯参与,导致故障恢复时间⽆法 保障。
  2. 主节点可以将读压⼒分散出去,但写压⼒/存储压⼒是⽆法被分担的,还是受到单机的限制。

其中第⼀个问题是⾼可⽤问题,即Redis哨兵主要解决的问题。第⼆个问题是属于存储分布式的问 题,留给Redis集群去解决,本章我们集中讨论第⼀个问题。

1.2、⼈⼯恢复主节点故障

Redis 主从复制模式下,主节点故障后需要进⾏的⼈⼯⼯作是⽐较繁琐的,在图中⼤致展⽰了整体过程。

Redis 主节点故障后需要进⾏的操作

1)运维⼈员通过监控系统,发现Redis主节点故障宕机。

2)运维⼈员从所有节点中,选择⼀个(此处选择了slave1)执⾏slaveofnoone,使其作为新的主 节点。

3)运维⼈员让剩余从节点(此处为slave2)执⾏slaveof{newMasterIp}{newMasterPort}从新主节 点开始数据同步。

4)更新应⽤⽅连接的主节点信息到{newMasterIp}{newMasterPort}。

5)如果原来的主节点恢复,执⾏slaveof{newMasterIp}{newMasterPort}让其成为⼀个从节点。 上述过程可以看到基本需要⼈⼯介⼊,⽆法被认为架构是⾼可⽤的。⽽这就是RedisSentinel所要做 的。

1.3、哨兵⾃动恢复主节点故障

当主节点出现故障时,RedisSentinel能⾃动完成故障发现和故障转移,并通知应⽤⽅,从⽽实现 真正的⾼可⽤。

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

这⾥的分布式架构是指:Redis数据节点、Sentinel节点集合、客⼾端分布在多个物理节点上,不要与后边介绍的RedisCluster分布式混淆。

Redis Sentinel 架构

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

1)主节点故障,从节点同步连接中断,主从复制停⽌。

2)哨兵节点通过定期监控发现主节点出现故障。哨兵节点与其他哨兵节点进⾏协商,达成多数认 同主 节点故障的共识。这步主要是防⽌该情况:出故障的不是主节点,⽽是发现故障的哨兵节 点,该情况 经常发⽣于哨兵节点的⽹络被孤⽴的场景下。

3)哨兵节点之间使⽤Raft算法选举出⼀个领导⻆⾊,由该节点负责后续的故障转移⼯作。

4)哨兵领导者开始执⾏故障转移:从节点中选择⼀个作为新主节点;让其他从节点同步新主节点;通知应⽤层转移到新主节点。

通过上⾯的介绍,可以看出RedisSentinel具有以下⼏个功能:

  • 监控:Sentinel节点会定期检测Redis数据节点、其余哨兵节点是否可达。
  • 故障转移:实现从节点晋升(promotion)为主节点并维护后续正确的主从关系。
  • 通知:Sentinel节点会将故障转移的结果通知给应⽤⽅。

二、安装部署(基于docker)

2.1、准备⼯作

  1. 安装docker和docker-compose

docker-compose 的安装

bash 复制代码
 # ubuntu
 apt install docker-compose

 # centos
 yum install docker-compose
  1. 停⽌之前的redis-server
bash 复制代码
 # 停⽌
 redis-server
 service redis-server stop

 # 停⽌ redis-sentinel 如果已经有的话. 
 service redis-sentinel stop
  1. 使⽤docker获取redis镜像
bash 复制代码
 docker pull redis:5.0.

2.2、编排redis主从节点

1) 编写 docker-compose.yml

创建 /root/redis/docker-compose.yml ,同时cd到yml所在⽬录中.

**注意:**docker中可以通过容器名字,作为ip地址,进⾏相互之间的访问.

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

也可以直接在windows上使⽤vscode编辑好yml,然后在上传到 linux 上.

2) 启动所有容器

bash 复制代码
docker-compose up -d

如果启动后发现前⾯的配置有误,需要重新操作,使⽤ docker-compose down 即可停⽌并删除 刚才创建好的容器.

3) 查看运⾏⽇志

bash 复制代码
 docker-compose logs

上 述操作必须保证⼯作⽬录在yml的同级⽬录中,才能⼯作.

4) 验证

连接主节点

bash 复制代码
redis-cli -p 6379
bash 复制代码
 127.0.0.1:6379> info replication
 # Replication
 role:master
 connected_slaves:2
 slave0:ip=172.22.0.3,port=6379,state=online,offset=348,lag=1
 slave1:ip=172.22.0.4,port=6379,state=online,offset=348,lag=1
 master_replid:a22196b425ab42ddfd222cc5a64d53acffeb3e63
 master_replid2:0000000000000000000000000000000000000000
 master_repl_offset:348
 second_repl_offset:-1
 repl_backlog_active:1
 repl_backlog_size:1048576
 repl_backlog_first_byte_offset:1
 repl_backlog_histlen:348

连接从节点

bash 复制代码
redis-cli -p 6380
bash 复制代码
 127.0.0.1:6380> info replication
 # Replication
 role:slave
 master_host:redis-master
 master_port:6379
 master_link_status:up
 master_last_io_seconds_ago:10
 master_sync_in_progress:0
 slave_repl_offset:446
 slave_priority:100
 slave_read_only:1
 connected_slaves:0
 master_replid:a22196b425ab42ddfd222cc5a64d53acffeb3e63
 master_replid2:0000000000000000000000000000000000000000
 master_repl_offset:446
 second_repl_offset:-1
 repl_backlog_active:1
 repl_backlog_size:1048576
 repl_backlog_first_byte_offset:1
 repl_backlog_histlen:446
bash 复制代码
redis-cli -p 6381
bash 复制代码
 127.0.0.1:6381> info replication
 # Replication
 role:slave
 master_host:redis-master
 master_port:6379
 master_link_status:up
 master_last_io_seconds_ago:7
 master_sync_in_progress:0
 slave_repl_offset:516
 slave_priority:100
 slave_read_only:1
 connected_slaves:0
 master_replid:a22196b425ab42ddfd222cc5a64d53acffeb3e63
 master_replid2:0000000000000000000000000000000000000000
 master_repl_offset:516
 second_repl_offset:-1
 repl_backlog_active:1
 repl_backlog_size:1048576
 repl_backlog_first_byte_offset:1
 repl_backlog_histlen:516

2.3、编排 redis-sentinel节点

也可以把redis-sentinel放到和上⾯的redis的同⼀个yml中进⾏容器编排.此处分成两组,主要是为 了两⽅⾯:

  • 观察⽇志⽅便
  • 确保redis主从节点启动之后才启动redis-sentinel.如果先启动redis-sentinel的话,可能触发额 外的选举过程,混淆视听.(不是说先启动哨兵不⾏,⽽是观察的结果可能存在⼀定随机性).

1) 编写 docker-compose.yml

创建 /root/redis-sentinel/docker-compose.yml ,同时cd到yml所在⽬录中.

注意: 每个⽬录中只能存在⼀个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:
            - ./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  

也可以直接在windows上使⽤vscode编辑好yml,然后在上传到linux上.

2) 创建配置⽂件

创建 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
 sentinel down-after-milliseconds redis-master 1000

理解 sentinel monitor

bash 复制代码
sentinel monitor 主节点名 主节点ip 主节点端⼝ 法定票数
  • 主节点名,这个是哨兵内部⾃⼰起的名字.
  • 主节点ip,部署redis-master的设备ip.此处由于是使⽤docker,可以直接写docker的容器名,会 被⾃动 DNS 成对应的容器ip
  • 主节点端⼝,不解释.
  • 法定票数,哨兵需要判定主节点是否挂了.但是有的时候可能因为特殊情况,⽐如主节点仍然⼯作正 常,但是哨兵节点⾃⼰⽹络出问题了,⽆法访问到主节点了.此时就可能会使该哨兵节点认为主节点 下线,出现误判.使⽤投票的⽅式来确定主节点是否真的挂了是更稳妥的做法.需要多个哨兵都认为 主节点挂了,票数>=法定票数之后,才会真的认为主节点是挂了.

理解 sentinel down-after-milliseconds

  • 主节点和哨兵之间通过⼼跳包来进⾏沟通.如果⼼跳包在指定的时间内还没回来,就视为是节点出现 故障.

既然内容相同,为啥要创建多份配置⽂件?

redis-sentinel 在运⾏中可能会对配置进⾏rewrite,修改⽂件内容.如果⽤⼀份⽂件,就可能出现修改 混乱的情况.

3) 启动所有容器

bash 复制代码
docker-compose up -d

如果启动后发现前⾯的配置有误,需要重新操作,使⽤docker-compose down 即可停⽌并删除刚才创建好的容器.

4) 查看运⾏⽇志

bash 复制代码
docker-compose logs

上述操作必须保证⼯作⽬录在yml的同级⽬录中,才能⼯作.

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

5) 观察redis-sentinel 的配置rewrite

再次打开哨兵的配置⽂件,发现⽂件内容已经被⾃动修改了.

bash 复制代码
 bind 0.0.0.0
 port 26379
 sentinel myid 4d2d562860b4cdd478e56494a01e5c787246b6aa
 sentinel deny-scripts-reconfig yes
 # Generated by CONFIG REWRITE
 dir "/data"
 sentinel monitor redis-master 172.22.0.4 6379 2
 sentinel down-after-milliseconds redis-master 1000
 sentinel config-epoch redis-master 1
 sentinel leader-epoch redis-master 1
 sentinel known-replica redis-master 172.22.0.2 6379
 sentinel known-replica redis-master 172.22.0.3 6379
 sentinel known-sentinel redis-master 172.22.0.7 26379 
 f718caed536d178f5ea6d1316d09407cfae43dd2
 sentinel known-sentinel redis-master 172.22.0.5 26379 
 2ab6de82279bb77f8397c309d36238f51273e80a
 sentinel current-epoch 1

Generated by CONFIG REWRITE 这⾥的内容就是⾃动修改的.

对⽐这三份⽂件,可以看到配置内容是存在差异的.

三、重新选举

3.1、redis-master 宕机之后

⼿动把 redis-master ⼲掉

bash 复制代码
docker stop redis-master

观察哨兵的⽇志,可以看到哨兵发现了主节点sdown,进⼀步的由于主节点宕机得票达到 master 被判定为odown.

  • 主观下线(SubjectivelyDown,SDown):哨兵感知到主节点没⼼跳了.判定为主观下线
  • 客观下线(ObjectivelyDown,ODown):多个哨兵达成⼀致意⻅,才能认为master确实下线了.

接下来,哨兵们挑选出了⼀个新的master.

此时,对于Redis来说仍然是可以正常使⽤的.

3.2、redis-master 重启之后

⼿动把 redis-master 启动起来

bash 复制代码
docker start redis-master

观察哨兵⽇志

可以看到刚才新启动的 redis-master 被当成了slave

使⽤redis-cli 也可以进⼀步的验证这⼀点

bash 复制代码
 127.0.0.1:6379> info replication
 # Replication
 role:slave
 master_host:172.22.0.4
 master_port:6379
 master_link_status:up
 master_last_io_seconds_ago:0
 master_sync_in_progress:0
 slave_repl_offset:324475
 slave_priority:100
 slave_read_only:1
 connected_slaves:0
 master_replid:ececc285a2892fba157318c77ebe1409f9c2254e
 master_replid2:0000000000000000000000000000000000000000
 master_repl_offset:324475
 second_repl_offset:-1
 repl_backlog_active:1
 repl_backlog_size:1048576
 repl_backlog_first_byte_offset:318295
 repl_backlog_histlen:6181

3.3、结论

  • Redis主节点如果宕机,哨兵会把其中的⼀个从节点,提拔成主节点.
  • 当之前的Redis主节点重启之后,这个主节点被加⼊到哨兵的监控中,但是只会被作为从节点使⽤.

四、选举原理

假定当前环境如上⽅介绍,三个哨兵(sentenal1,sentenal2,sentenal3),⼀个主节点(redis-master),两 个从节点(redis-slave1,redis-slave2).

当主节点出现故障,就会触发重新⼀系列过程.

4.1、 主观下线

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

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

4.2、客观下线

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

bash 复制代码
sentinel monitor redis-master 172.22.0.4 6379 2

在这个地⽅配置的2,即为法定票数

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

4.3、选举出哨兵的leader

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

这个选举的过程涉及到 Raft 算法

假定一共三个哨兵节点,S1, S2, S3

  1. 每个哨兵节点都给其他所有哨兵节点,发起⼀个"拉票请求".(S1->S2,S1->S3,S2->S1,S2->S3, S3->S1,S3->S2)
  2. 收到拉票请求的节点,会回复⼀个"投票响应".响应的结果有两种可能,投or不投;⽐如S1给S2发了个投票请求,S2就会给S1返回投票响应. 到底S2是否要投S1呢?取决于S2是否给别⼈投过票了.(每个哨兵只有⼀票). 如果S2没有给别⼈投过票,换⽽⾔之,S1是第⼀个向S2拉票的,那么S2就会投S1.否则则不投.
  3. ⼀轮投票完成之后,发现得票超过半数的节点,⾃动成为leader;如果出现平票的情况(S1投S2,S2投S3,S3投S1,每⼈⼀票),就重新再投⼀次即可,这也是为啥建议哨兵节点设置成奇数个的原因.如果是偶数个,则增⼤了平票的概率,带来不必要的开销.
  4. leader 节点负责挑选⼀个slave成为新的master.当其他的sentenal发现新的master出现了,就 说明选举结束了.

简⽽⾔之,Raft算法的核⼼就是"先下⼿为强".谁率先发出了拉票请求,谁就有更⼤的概率成为leader.

这里的决定因素成了"⽹络延时".⽹络延时本⾝就带有⼀定随机性.

具体选出的哪个节点是leader,这个不重要,重要的是能选出⼀个节点即可.

4.4、leader 挑选出合适的slave成为新的 master

挑选规则:

  1. ⽐较优先级.优先级⾼(数值⼩的)的上位.优先级是配置⽂件中的配置项(slave-priority 或者 replica-priority ).
  2. ⽐较 replication offset 谁复制的数据多,⾼的上位.
  3. ⽐较 run id ,谁的id⼩,谁上位

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

  1. leader 指定该节点执⾏ slave no one ,成为master
  2. leader 指定剩余的slave节点,都依附于这个新master

五、⼩结

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

⼀些注意事项:

  • 哨兵节点不能只有⼀个.否则哨兵节点挂了也会影响系统可⽤性.
  • 哨兵节点最好是奇数个.⽅便选举leader,得票更容易超过半数.
  • 哨兵节点不负责存储数据.仍然是redis主从节点负责存储.
  • 哨兵+主从复制解决的问题是"提⾼可⽤性",不能解决"数据极端情况下写丢失"的问题.
  • 哨兵+主从复制不能提⾼数据的存储容量.当我们需要存的数据接近或者超过机器的物理内存,这样 的结构就难以胜任了.

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

相关推荐
白云偷星子21 小时前
MySQL笔记13
数据库·笔记·mysql
施嘉伟21 小时前
静默安装金仓数据库,到底有多简单?
数据库
Tapdata21 小时前
实时物化视图的新路径:从传统 Join 到跨源实时查询
数据库
optimistic_chen21 小时前
【Java EE进阶 --- SpringBoot】Mybatis - plus 操作数据库
数据库·spring boot·笔记·java-ee·mybatis·mybatis-plus
FJW0208141 天前
关系型数据库大王Mysql——DDL语句操作示例
数据库·mysql
言之。1 天前
Chroma 开源的 AI 应用搜索与检索数据库(即向量数据库)
数据库·人工智能·开源
来旺1 天前
互联网大厂Java面试全解析及三轮问答专项
java·数据库·spring boot·安全·缓存·微服务·面试
摇滚侠1 天前
Spring Boot 3零基础教程,yml文件中配置和类的属性绑定,笔记15
spring boot·redis·笔记
cr7xin1 天前
基于Session和Redis实现短信验证码登录
数据库·redis·缓存
乌暮1 天前
数据库--视图、索引
数据库