Redis高可用–Sentinel哨兵详解

Redis高可用–Sentinel哨兵详解

本文来自《Redis开发与运维》–付磊 / 张益军,读后收益匪浅。

Redis的主从复制模式下,一旦主节点由于故障不能提供服务,需要人工将从节点晋升为主节点,同时还要通知应用方更新主节点地址,对于很多应用场景这种故障处理的方式是无法接受的。可喜的是Redis从2.8开始正式提供了Redis Sentinel(哨兵)架构来解决这个问题

一、Redis Sentinel的概念

1.1 主从复制的问题
Redis的主从复制模式可以将主节点的数据改变同步给从节点,这样从节点就可以起到两个作用:第一,作为主节点的一个备份,一旦主节点出了故障不可达的情况,从节点可以作为后备“顶”上来,并且保证数据尽量不丢失(主从复制是最终一致性)。第二,从节点可以扩展主节点的读能力,一旦主节点不能支撑住大并发量的读操作,从节点可以在一定程度上帮助主节点分担读压力。

但是主从复制也带来了以下问题:

  • 一旦主节点出现故障,需要手动将一个从节点晋升为主节点,同时需要修改应用方的主节点地址,还需要命令其他从节点去复制新的主节点,整个过程都需要人工干预
  • 主节点的写能力受到单机的限制。
  • 主节点的存储能力受到单机的限制。
1.2 高可用
1.3 Redis Setinel的高可用性

当主节点出现故障时,Redis Sentinel能自动完成故障发现和故障转移,并通知应用方,从而实现真正的高可用。
Redis Sentinel是一个分布式架构,其中包含若干个Sentinel节点和Redis数据节点,每个Sentinel节点会对数据节点和其余Sentinel节点进行监控,当它发现节点不可达时,会对节点做下线标识。如果被标识的是主节点,它还会和其他Sentinel节点进行“协商”,当大多数Sentinel节点都认为主节点不可达时,它们会选举出一个Sentinel节点来完成自动故障转移的工作,同时会将这个变化实时通知给Redis应用方。整个过程完全是自动的,不需要人工来介入,所以这套方案很有效地解决了Redis的高可用问题。

注意
这里的分布式是指:Redis数据节点、Sentinel节点集合、客户端分布在多个物理节点的架构

二、Redis Sentinel安装部署

2.1 Sentinel配置
redis-sentinel-26379.conf
port 26379  
daemonize yes  
logfile "26379.log"  
dir /opt/soft/redis/data  
sentinel monitor mymaster 127.0.0.1 6379 2  
sentinel down-after-milliseconds mymaster 30000  
sentinel parallel-syncs mymaster 1  
sentinel failover-timeout mymaster 180000  
  • 1.Sentinel节点的默认端口是26379。
  • 2.sentinel monitor mymaster 127.0.0.1 6379 2 配置代表sentinel-1节点需要监控127.0.0.1:6379这个主节点,2代表主节点失败至少需要2个Sentinel节点同意,mymaster是主节点的别名
2.2 启动sentinel节点

Sentinel节点的启动方法有两种:

  • 方法一,使用redis-sentinel命令:
redis-sentinel redis-sentinel-26379.conf
  • 方法二,使用redis-server命令加–sentinel参数:
redis-server redis-sentinel-26379.conf --sentinel
2.3 确认

Sentinel节点本质上是一个特殊的Redis节点,所以也可以通过info命令来查询它的相关信息,从下面info的Sentinel片段来看,Sentinel节点找到了主节点127.0.0.1:6379,发现了它的两个从节点,同时发现Redis Sentinel一共有3个Sentinel节点。这里只需要了解Sentinel节点能够彼此感知到对方,同时能够感知到Redis数据节点就可以了:

$ redis-cli -h 127.0.0.1 -p 26379 info Sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
master0:name=mymaster,status=ok,address=127.0.0.1:6379,slaves=2,sentinels=3

有2点需要强调一下:

  • 1)生产环境中建议Redis Sentinel的所有节点应该分布在不同的物理机上。
  • 2)Redis Sentinel中的数据节点和普通的Redis数据节点在配置上没有任何区别,只不过是添加了一些Sentinel节点对它们进行监控。
2.4 配置优化

Redis安装目录下有一个sentinel.conf,是默认的Sentinel节点配置文件

2.4.1 配置说明和优化
port 26379
dir /opt/soft/redis/data        
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
#sentinel auth-pass <master-name> <password>
#sentinel notification-script <master-name> <script-path>
#sentinel client-reconfig-script <master-name> <script-path>

port和dir分别代表Sentinel节点的端口和工作目录,下面重点对sentinel相关配置进行详细说明。

sentinel monitor

配置如下:

sentinel monitor <master-name> <ip> <port> <quorum>  

Sentinel节点会定期监控主节点,所以从配置上必然也会有所体现,本配置说明Sentinel节点要监控的是一个名字叫做<master-name>,ip地址和端口为<ip><port>的主节点。<quorum>代表要判定主节点最终不可达所需要的票数。但实际上Sentinel节点会对所有节点进行监控,但是在Sentinel节点的配置中没有看到有关从节点和其余Sentinel节点的配置,那是因为Sentinel节点会从主节点中获取有关从节点以及其余Sentinel节点的相关信息

例如某个Sentinel初始节点配置如下:

port 26379
daemonize yes
logfile "26379.log"
dir /opt/soft/redis/data
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000

当所有节点启动后,配置文件中的内容发生了变化,体现在三个方面:

  • 1.Sentinel节点自动发现了从节点、其余Sentinel节点。
  • 2.去掉了默认配置,例如parallel-syncs、failover-timeout参数。
  • 3.添加了配置纪元相关参数。

启动后变化为:

port 26379
daemonize yes
logfile "26379.log"
dir "/opt/soft/redis/data"
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel config-epoch mymaster 0
sentinel leader-epoch mymaster 0
#发现两个slave节点
sentinel known-slave mymaster 127.0.0.1 6380
sentinel known-slave mymaster 127.0.0.1 6381
#发现两个sentinel节点
sentinel known-sentinel mymaster 127.0.0.1 26380 282a70ff56c36ed56e8f7ee6ada741
    24140d6f53
sentinel known-sentinel mymaster 127.0.0.1 26381 f714470d30a61a8e39ae031192f1fe
    ae7eb5b2be
sentinel current-epoch 0

同时还与Sentinel节点的领导者选举有关,至少要有max(quorum,num(sentinels)/2+1)个Sentinel节点参与选举,才能选出领导者Sentinel,从而完成故障转移。例如有5个Sentinel节点,quorum=4,那么至少要有max(quorum,num(sentinels)/2+1)=4个在线Sentinel节点才可以进行领导者选举

sentinel down-after-milliseconds

配置如下:

sentinel down-after-milliseconds <master-name> <times> 

每个sentinel节点都要通过定期发送ping命令来判断Redis数据节点和其余sentinel节点是否可达,如果超过了down-after-millisends配置的时间且没有有效回复,则判定节点不可达,<times>(单位为毫秒)就是超时时间。这个配置对是对节点失败判定的重要依据。

运维提示
down-after-milliseconds虽然以<master-name>为参数,但实际上对Sentinel节点、主节点、从节点的失败判定同时有效。

sentinel parallel-syncs

配置如下:

sentinel parallel-syncs <master-name> <nums> 

当sentinel节点集合对主节点故障判定达成一致时,sentinel领导者节点会做故障转移操作,选出新的主节点,原来的从节点会向新的主节点发起复制操作,parallel-syncs就是用来限制在一次故障转移之后,每次向新的主节点发起复制操作的从节点个数。如果这个参数配置的比较大,那么多个从节点会向新的主节点同时发起复制操作,尽管复制操作通常不会阻塞主节点,但是同时会向新的主节点发起复制,必然会对主节点所在的机器造成一定的网络和磁盘IO开销。

sentinel failover-time

配置如下:

sentinel failover-timeout <master-name> <times> 

failover-time通常被解释成故障转移超时时间,但实际上它作用于故障转移各个阶段:

  • a)选出合适从节点。
  • b)晋升选出的从节点为主节点。
  • c)命令其余从节点复制新的主节点。
  • d)等待原主节点恢复后命令它去复制新的主节点。

failover-timeout的作用具体体现在四个方面:

  • 1)如果Redis Sentinel对一个主节点故障转移失败,那么下次再对该主节点做故障转移的起始时间是failover-timeout的2倍。
  • 2)在b)阶段时,如果Sentinel节点向a)阶段选出来的从节点执行slaveof no one一直失败(例如该从节点此时出现故障),当此过程超过failover-timeout时,则故障转移失败。
  • 3)在b)阶段如果执行成功,Sentinel节点还会执行info命令来确认a)阶段选出来的节点确实晋升为主节点,如果此过程执行时间超过failover-timeout时,则故障转移失败。
  • 4)如果c)阶段执行时间超过了failover-timeout(不包含复制时间),则故障转移失败。注意即使超过了这个时间,Sentinel节点也会最终配置从节点去同步最新的主节点。
sentinel auth-pass

配置如下:

sentinel auth-pass <master-name> <password>

如果Sentinel监控的主节点配置了密码,sentinel auth-pass配置通过添加主节点的密码,防止Sentinel节点对主节点无法监控。

sentinel notification-script

配置如下:

sentinel notification-script <master-name> <script-path>
2.4.2 如何监控多个主节点
2.4.3 调整配置
sentinel set <param> <value>
参数使用方法
quorumsentinel set mymaster quorum 2
down-after-millisecondssentinel set mymaster down-afer-milliseconds 30000
failover-timesentinel set mymaster failover-time 360000
parallel-syncssentinel set mymaster parallel-syncs 2

有几点需要注意一下:
1)sentinel set命令只对当前Sentinel节点有效。
2)sentinel set命令如果执行成功会立即刷新配置文件,这点和Redis普通数据节点设置配置需要执行config rewrite刷新到配置文件不同。
3)建议所有Sentinel节点的配置尽可能一致,这样在故障发现和转移时比较容易达成一致。
4)Sentinel对外不支持config命令。

2.5 部署技巧

1)Sentinel节点不应该部署在一台物理“机器”上。
2)部署至少三个且奇数个的Sentinel节点。

三、Redis Sentinel API详解

sentinel master

展示所有被监控的主节点状态以及相关的统计信息

sentinel sentinels <master name>

展示指定的Sentinel节点集合(不包含当前Sentinel节点)

sentinel get-master-addr-by-name <master name>

返回指定主节点的IP地址和端口

sentinel reset <pattern>

当前Sentinel节点对符合(通配符风格)主节点的配置进行重置,包含清除主节点的相关状态(例如故障转移),重新发现从节点和Sentinel节点。

sentinel failover <master name>

对指定主节点进行强制故障转移(没有和其他Sentinel节点“协商”),当故障转移完成后,其他sentinel节点按照故障转移的结果更新自身配置,这个命令在Redis Sentinel的日常运维中非常有用

sentinel remove <master name>

取消当前Sentinel节点对于指定主节点的监控。

sentinel monitor <master name> <ip> <port> <quorum>

这个命令和配置文件中的含义是完全一样的,只不过是通过命令的形式来完成Sentinel节点对主节点的监控。

四、Redis Sentinel客户端连接

4.1 Redis Sentinel的客户端

Sentinel节点集合具备了监控、通知、自动故障转移、配置提供者若干功能,也就是说实际上最了解主节点信息的就是Sentinel节点集合,而各个主节点可以通过进行标识的,所以,无论是哪种编程语言的客户端,如果需要正确地连接Redis Sentinel,必须有Sentinel节点集合和masterName两个参数

4.2 Redis Sentinel客户端基本实现原理

实现一个Redis Sentinel客户端的基本步骤如下:

  • 1)遍历Sentinel节点集合获取一个可用的Sentinel节点,Sentinel节点之间可以共享数据,所以从任意一个Sentinel节点获取主节点信息都是可以的
  • 2)通过 sentinel get-master-addr-by-name master-name 这个API来获取对应主节点的相关信息
  • 3)验证当前获取的“主节点”是真正的主节点,这样做的目的是为了防止故障转移期间主节点变化
  • 4)保持和Sentinel节点集合的“联系”,时刻获取关于主节点的相关“信息”

从上面的模型可以看出,Redis Sentinel客户端只有在初始化和切换主节点时需要和Sentinel节点集合进行交互来获取主节点信息

4.3 Java操作Redis Sentinel

Jedis能够很好地支持Redis Sentinel,并且使用Jedis连接Redis Sentinel也很简单,按照Redis Sentinel的原理,需要有masterName和Sentinel节点集合两个参数

public JedisSentinelPool(String masterName, Set<String> sentinels,
    final GenericObjectPoolConfig poolConfig, final int connectionTimeout, 
    final int soTimeout,
    final String password, final int database, 
    final String clientName)

具体参数含义如下:
·masterName——主节点名。
·sentinels——Sentinel节点集合。
·poolConfig——common-pool连接池配置。
·connectTimeout——连接超时。
·soTimeout——读写超时。
·password——主节点密码。
·database——当前数据库索引。
·clientName——客户端名。

private HostAndPort initSentinels(Set<String> sentinels, final String masterName) {
    // 主节点
    HostAndPort master = null;
    // 遍历所有sentinel节点
    for (String sentinel : sentinels) {
        // 连接sentinel节点
        HostAndPort hap = toHostAndPort(Arrays.asList(sentinel.split(":")));
        Jedis jedis = new Jedis(hap.getHost(), hap.getPort());
        // 使用sentinel get-master-addr-by-name masterName获取主节点信息
        List<String> masterAddr = jedis.sentinelGetMasterAddrByName(masterName);
        // 命令返回列表为空或者长度不为2, 继续从下一个sentinel节点查询
        if (masterAddr == null || masterAddr.size() != 2) {
            continue;
        }
        // 解析masterAddr获取主节点信息
        master = toHostAndPort(masterAddr);
        // 找到后直接跳出for循环
        break;
    }
    if (master == null) {
        // 直接抛出异常, 
        throw new Exception();
    }
    // 为每个sentinel节点开启主节点switch的监控线程
    for (String sentinel : sentinels) {
        final HostAndPort hap = toHostAndPort(Arrays.asList(sentinel.split(":")));
        MasterListener masterListener = new MasterListener(masterName, hap.getHost(), 
            hap.getPort());
        masterListener.start();
    }
    // 返回结果
    return master;
}

具体过程如下:
1)遍历Sentinel节点集合,找到一个可用的Sentinel节点,如果找不到就从Sentinel节点集合中去找下一个,如果都找不到直接抛出异常给客户端:

new JedisException("Can connect to sentinel, but " + masterName + " seems to be not monitored...")

2)找到一个可用的Sentinel节点,执行sentinelGetMasterAddrByName(masterName),找到对应主节点信息:

List<String> masterAddr = jedis.sentinelGetMasterAddrByName(masterName);

3)JedisSentinelPool中没有发现对主节点角色验证的代码,这是因为get-master-addr-by-name master-name这个API本身就会自动获取真正的主节点(例如故障转移期间)。
4)为每一个Sentinel节点单独启动一个线程,利用Redis的发布订阅功能,每个线程订阅Sentinel节点上切换master的相关频道 +switch-master

for (String sentinel : sentinels) {
    final HostAndPort hap = toHostAndPort(Arrays.asList(sentinel.split(":")));
    MasterListener masterListener = new MasterListener(masterName, hap.
        getHost(), hap.getPort());
    masterListener.start();
}

下面代码就是MasterListener的核心监听代码,代码中比较重要的部分就是订阅Sentinel节点的+switch-master频道,它就是Redis Sentinel在结束对主节点故障转移后会发布切换主节点的消息,Sentinel节点基本将故障转移的各个阶段发生的行为都通过这种发布订阅的形式对外提供,开发者只需订阅感兴趣的频道即可

Jedis sentinelJedis = new Jedis(sentinelHost, sentinelPort);
// 客户端订阅Sentinel节点上"+switch-master"(切换主节点)频道
sentinelJedis.subscribe(new JedisPubSub() {
    @Override
    public void onMessage(String channel, String message) {
        String[] switchMasterMsg = message.split(" ");
        if (switchMasterMsg.length > 3) {
            // 判断是否为当前masterName
            if (masterName.equals(switchMasterMsg[0])) {
                // 发现当前masterName发生switch, 使用initPool重新初始化连接池
                initPool(toHostAndPort(switchMasterMsg[3], switchMasterMsg[4]));
            }
        }
    }
}, "+switch-master");

五、Redis Sentinel实现原理

5.1 Redis Sentinel的三个定时任务

一套合理的监控机制是Sentinel节点判定节点不可达的重要保证,Redis Sentinel通过三个定时监控任务完成对各个节点发现和监控:

  • 1)每隔10秒,每个Sentinel节点会向主节点和从节点发送info命令获取最新的拓扑结构
  • 2)每隔2秒,每个Sentinel节点会向Redis数据节点的 __sentinel__:hello 频道上发送该Sentinel节点对于主节点的判断以及当前Sentinel节点的信息,同时每个Sentinel节点也会订阅该频道,来了解其他Sentinel节点以及它们对主节点的判断,所以这个定时任务可以完成以下两个工作:
    • 发现新的Sentinel节点:通过订阅主节点的__sentinel__:hello了解其他的Sentinel节点信息,如果是新加入的Sentinel节点,将该Sentinel节点信息保存起来,并与该Sentinel节点创建连接。
    • Sentinel节点之间交换主节点的状态,作为后面客观下线以及领导者选举的依据和客观下线
  • 3)每隔1秒,每个Sentinel节点会向主节点、从节点、其余Sentinel节点发送一条ping命令做一次心跳检测,来确认这些节点当前是否可达
5.2 主观下线和客观下线
  • 主观下线:每个Sentinel节点会每隔1秒对主节点、从节点、其他Sentinel节点发送Ping命令做心跳检测,当这个节点超过down-after-milliseconds没有进行有效回复,Sentinel节点就会对该节点做失败判定,这个行为叫主观下线。从字面意思也可以很容易看出主观下线是当前Sentinel节点的一家之言,存在误判的可能。
  • 客观下线:当Sentinel下线的是主节点时,该Sentinel会通过sentinel is-master-down-by-addr命令向其他Sentinel节点询问对主节点的判断,当超过个数,Sentinel节点认为主节点确实有问题,这时该Sentinel节点会做客观下线的决定,这样客观下线的含义就比较明显了,也就是大部分Sentinel节点都会对主节点的下线做了同意判定,那么这个判定就是客观的。

注意
从节点、Sentinel节点在主观下线后,没有后续的故障转移操作。

这里有必要对sentinel is-master-down-by-addr命令做一个介绍,它的使用方法如下:

sentinel is-master-down-by-addr <ip> <port> <current_epoch> <runid>
  • ip:主节点IP
  • port:主节点端口
  • current_epoch:当前配置纪元
  • runid:此参数有两种类型,不同类型决定了次API作用的不同
    • 当runid等于“*”时,作用是Sentinel节点之间交换对主节点下线的判定
    • 但runid等于当前Sentinel节点的runid时,作用是当前Sentinel节点希望目标Sentinel同意自己称为领导者的请求。
sentinel is-master-down-by-addr 127.0.0.1 6379 0 *

返回结果包含三个参数,如下所示:

  • down_state:目标Sentinel节点对于主节点的下线判断,1是下线,0是在线。
  • leader_runid:当leader_runid等于“*”时,代表返回结果是用来做主节点是否不可达,当leader_runid等于具体的runid,代表目标节点同意runid成为领导者。
  • leader_epoch:领导者纪元。
5.3 Sentinel领导者选举

假如Sentinel节点对于主节点已经做了客观下线,那么是不是就可以立即进行故障转移了?当然不是,实际上故障转移的工作只需要一个Sentinel节点来完成即可,所以Sentinel节点之间会做一个领导者选举的工作,选出一个Sentinel节点作为领导者进行故障转移的工作。Redis使用了Raft算法实现领导者选举,因为Raft算法相对比较抽象和复杂,以及篇幅所限,所以这里给出一个Redis Sentinel进行领导者选举的大致思路:
1)每个在线的Sentinel节点都有资格成为领导者,当它确认主节点主观下线时候,会向其他Sentinel节点发送sentinel is-master-down-by-addr命令,要求将自己设置为领导者。
2)收到命令的Sentinel节点,如果没有同意过其他Sentinel节点的sentinel is-master-down-by-addr命令,将同意该请求,否则拒绝。
3)如果该Sentinel节点发现自己的票数已经大于等于max(quorum,num(sentinels)/2+1),那么它将成为领导者。
4)如果此过程没有选举出领导者,将进入下一次选举。

5.4 故障转移
  • 1)在从节点列表中选出一个节点作为新的主节点,具体步骤如下:
    • a)过滤:“不健康”(主观下线、断线)、5秒内没有回复过Sentinel节点ping响应、与主节点失联超过down-after-milliseconds * 10
    • b)选择slave-priority(从节点优先级)最高的从节点列表,如果存在则返回,不存在则继续
    • c)选择复制偏移量最大的从节点(复制的最完整),如果存在则返回,不存在则继续
    • d)选择runid最小的从节点
      在这里插入图片描述
    • 2)Sentinel领导者节点会对第一步选出来的从节点执行slaveof no one命令让其成为主节点
    • 3) Sentinel领导者节点会向剩余的从节点发送命令,让它们成为新主节点的从节点,复制规则和parallel-syncs参数有关
    • 4)Sentinel节点集合会将原来的主节点更新为从节点并保持对其关注,当其恢复后命令它去复制新的主节点

Redis Sentinel在故障转移一些重要的事件消息对应的频道(Redis pub-sub发布订阅模式):
在这里插入图片描述

六、Redis Sentinel开发运维实践

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值