八、redis哨兵模式

基本概念

使用主从复制能够将主节点的数据同步到从节点,一定程度上保证了数据的完整性,也提供了扩展读的能力。
但是主从复制有以下问题:

  • 一旦主节点宕机,需要手动提升从节点为主节点
  • 主节点的写能力受到单机的限制
  • 主节点的存储能力也受到单机的限制

高可用

Redis主从模式下一定程度上解决了高可用问题,但是全程需要人去干预,非常不方便,考虑到这点,有些公司把一些
流程自动化,但是会存在一些问题,比如判断节点不可用的机制是否完善、如何从多个从节点中选出一个晋升为主节点等等,
Redis Sentinel方案正式用于解决这些问题。

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

Redis Sentinel是一个分布式架构,其中包含多个Sentinel节点和数据节点,每个Sentinel节点都会实时监控数据节点和
其他sentinel节点,并维护它们,整个过程都是自动完成的。
哨兵模式与主从模式的区别

整个过程如下:

  1. Sentinel发现主节点出现故障
  2. 多个Sentinel节点对主节点的故障达成一致,选举出某个sentinel节点作为leader负责故障转移
  3. leader完成故障转移
    • 1)提升从节点
    • 2)设置之前的从节点设置新的主节点
    • 3)通知客户端
    • 4)等之前的主节点恢复后,设置为从节点

安装和部署

  1. 部署主从节点
    redis-server redis-6379.conf 主节点

    1
    2
    3
    4
    redis-server redis-6378.conf  # 从节点1
    slaveof 127.0.0.1 6379
    redis-server redis-6380.conf #从节点2
    slaveof 127.0.0.1 6379
  2. 部署Sentinel节点

    1
    2
    3
    sentinel-26379.conf
    port 26379
    sentinel monitor mymaster 127.0.0.1 6379 2
  3. 启动Sentinel节点
    两种方式:

    • 一种是使用redis-server:redis-server sentinel-26379.conf --sentinel
    • 另一种是使用redis-sentinel:redis-sentinel sentinel-26379.conf
  4. 检查

    1
    2
    3
    4
    5
    6
    7
    8
    127.0.0.1:26379> info sentinel
    # Sentinel
    sentinel_masters:1
    sentinel_tilt:0
    sentinel_running_scripts:0
    sentinel_scripts_queue_length:0
    sentinel_simulate_failure_flags:0
    master0:name=mymaster,status=ok,address=100.101.71.99:6379,slaves=2,sentinels=3
  5. 重要配置选项

    • sentinel monitor {master-name} {ip} {port} {quorum}
      sentinel节点会定期监控主节点,quorum代表判定主节点不可用需要的票数,这里只配置了一个ip,是因为,sentinel节点会自动从主节点中获取其他从节点信息。
    • sentinel down-after-milliseconds
      每个sentinel节点要定期ping命令来判断数据节点和哨兵节点是否可达,超过了这个值,就表示不可达。
    • sentinelparallel-syns
      这个值表示在故障转移确定了新的主节点后,每次向新的主节点发起复制操作的从节点个数,如果过大,则可能在故障转移时发生阻塞
    • sentinel failover-timeout
      故障转移的超时时间
    • sentinel notification-script
      在故障转移期间,当一些警告级别的sentinel事件发生的时候会触发脚本

哨兵模式下特殊API

  1. sentinel master 展示主节点状态及其统计信息
  2. sentinel slaves {master name} 展示从节点信息
  3. sentinel reset {pattern} 对符合pattern的主节点的配置进行重置
  4. sentinel failover{mastername} 对指定主节点进行强制故障转移
  5. sentinel flushconfig,将哨兵节点的配置刷新到磁盘上,对于磁盘配置文件损坏的情况很有用
  6. sentinel remove {master-name} 取消该哨兵对于master节点的监控
  7. sentinel monitor {master-name} {ip}相反的命令
  8. sentinel set 动态设置相关配置

实现原理

三个定时任务

Redis Sentinel 通过三个定时监控任务完成对各个节点的发现与监控

  1. 每隔10秒,每个Sentinel 节点会向主节点和从节点发送info命令获取最新的拓扑结构

    1
    2
    3
    4
    5
    $> info replication
    role:master
    connected_slaves:2
    slave1:ip=127.0.0.1,port=6380,state=online,mymaster,offset=0,lag=0
    slave2:ip=127.0.0.1,port=6381,state=online,mymaster,offset=0,lag=0

    该定时任务的作用:

    • 通过主节点执行info,获取从节点的信息,所以无需显式配置监控从节点
    • 当有新的从节点加入,能够及时感知到
    • 节点不可达或者故障转移后,能及时更新拓扑结构
  2. 每隔2秒,都会向Redis数据节点的sentinel:hello频道上发送该Sentinel节点对于主节点的判断以及当前sentinel节点的信息
    同时sentinel节点也会订阅该频道,互相交换主节点状态,以及版本信息

    1
    2
    3
    4
    $:SUBSCRIBE __sentinel__:hello
    1) "message"
    2) "__sentinel__:hello"
    3) "100.101.71.99,26380,13da260e2869a5758a089a2e6fdf50a2e2c23d6e,1,mymaster,100.101.71.99,6380,1

    该定时任务的作用:

    1. 发现新的sentinel节点
    2. 更新配置,如果发现自己的版本低于其他节点的版本,则会更新,有点类似zookeeper的主节点选举,都用到了epoch
  3. 每隔1秒每个Sentinel节点会向主节点、从节点、其余Sentinel节点发送一条ping命令做一次心跳检测
    该定时任务的作用是判断节点是否健康的重要依据,也就是ping之后,如果失败则主观下线

主观下线和客观下线

  1. 主观下线
    每隔1秒ping命令做心跳检测时,如果没有在down-after-milliseconds时间内有效回复,则会判定该节点失败。
  2. 客观下线
    当Sentinel节点主观下线的节点是master节点时,该Sentinel节点会通过Sentinel is-master-down-by-addr命令向其他Sentinel节点询问对主节点的判断,
    当超过quorum个达成一致时,则会主观下线master节点

Sentinel领导者选举

当Sentinel节点对于master节点做了客观下线后,需要确定哨兵节点的leader,因为故障转移只需要一个Sentinel节点来完成。

选举的过程相对比较简单:

  • 每个Sentinel节点只有一票

  • 每个在线的Sentinel节点都有可能成为leader,当它确认master主观下线时,会想起他Sentinel节点发送成为leader的请求

  • 收到命令的Sentinel节点,如果没有投过票,则会同意该请求,也就是投票

  • 如果该Sentinel节点发现自己的票数大于等于max(quorum,num(sentinels)/2+1),则会成为leader

    基于此原则,可以判断谁最先向各个节点发出请求,则最有可能会成为leader

故障转移

  1. 从follower节点中选出一个可用节点(存活、优先级最高、复制偏移量最大、runid最小(优先级依次降低)的从节点)
  2. 对从节点执行slaveof no one
  3. Sentinel节点向其他从节点发送命令,让它们复制新的主节点
  4. Sentinel节点集合将原来的master节点更新为从节点

运维提示

  1. 主要分析日志,整个过程都有日志打印
  2. 需要对主节点下线时,比较合理的做法是使用sentinel failover命令,主动进行故障转移。
  3. 如果想指定某个slave节点成为master节点,则可以设置其他节点的优先级slave-priority为0,再执行sentinel failover命令,最后将优先级调回来。config get slave-priority

网络隔离的一致性

如果某个节点上sentinel和redis实例在同一个网络,其他的在另一个网络,当发送网络中断时会发生意外:

1
2
3
4
5
6
7
8
9
10
            +-------------+
| Sentinel 1 | <--- Client A
| Redis 1 (M) |
+-------------+
|
|
+-------------+ | +------------+
| Sentinel 2 |-----+-- / partition / ----| Sentinel 3 | <--- Client B
| Redis 2 (S) | | Redis 3 (M)|
+-------------+ +------------+

Redis3一开始是master,网络断开后,Redis1成为了新的主节点,Sentinel1和Sentinel2更新了配置,但这是Sentinel3还是原来的配置,这个时候我们出现了常说的脑裂现象(分布式常见的问题),ClientB仍然向Redis3写数据。
当网络恢复后,Sentinel3恢复更新配置,这时Redis3成为了slave,之前ClientB写入的数据将会丢失。
如果把redis当成缓存,也许可以容忍,但如果是一个存储系统,则无法容忍。

如果看过zookeeper的源码分析就知道,zookeeper有机制防止脑裂现象,它是通过持续交换信息,大于一般才正常工作,但这里的redis不是,为什么redis像zookeeper那样,保证
节点只有在超过半数以上才可用呢?我个人感觉根据cpa和base理论,Redis为了保证可用性,使用的是最终一致性,牺牲了高度一致性,尤其是其异步机制,也就是PA+最终一致性。另外在网络发生分区时A和P基本不可实现。也就是说当网络分区发生时只有PA和PC,由于被断开网络的那小部分不可用(如下文所说的配置)又在一定程度上变成了PC。

另外在分布式系统中,只要是不是在一个节点上部署,那么分区的情况将不可避免,所以如果满足CA,当数据分区时则最终会退化为只有A或者C,所以系统一般做成PA或者PC,而不是CA。

解决方法
保证主节点至少有一个slave节点,但因为复制过程是异步的,所以需要两个配置:

1
2
min-replicas-to-write 1
min-replicas-max-lag 10

min-replicas-to-write保证了至少一个slave节点
min-replicas-max-lag,保证了slave节点复制同步最多10秒的延迟,如果10秒内没有同步成功,则代表slave失败。

但还是有10秒的数据丢失