美文网首页
Redis的高可用机制 - 哨兵

Redis的高可用机制 - 哨兵

作者: 右耳菌 | 来源:发表于2022-09-02 00:35 被阅读0次

    点击查看官方介绍

    1. 哨兵(Sentinel)机制核心作用

    2. 核心运作流程

    • 服务发现和健康检查流程
    • 故障切换流程

    3. 实操的例子

    这里受限于服务器数量,所以所有的内容都弄在一台服务器上了

    1. 准备

    1. 3个redis服务

    192.168.1.7 6379
    192.168.1.7 6380
    192.168.1.7 6381

    1. 3个sentinel(哨兵)

    192.168.1.7 26379 #master
    192.168.1.7 26380 #slave1
    192.168.1.7 26381 #slave2

    2. 配置redis.conf文件
    6379.conf

    #配置文件进行了精简,完整配置可自行和官方提供的完整conf文件进行对照。端口号自>行对应修改
    #后台执行的意思
    daemonize yes
    #端口号
    port 6379
    #IP绑定,redis不建议对公网开放,直接绑定0.o.o.0没毛病
    bind 0.0.0.0
    # 这个文件会自动生成(如果统一台服务器上启动,注意要修改为不同的端口)
    pidfile "/var/run/redis_6379.pid"
    

    6380.conf

    #配置文件进行了精简,完整配置可自行和官方提供的完整conf文件进行对照。端口号自>行对应修改
    #后台执行的意思
    daemonize yes
    #端口号
    port 6380
    #IP绑定,redis不建议对公网开放,直接绑定0.o.o.0没毛病
    bind 0.0.0.0
    # 这个文件会自动生成(如果统一台服务器上启动,注意要修改为不同的端口)
    pidfile "/var/run/redis_6380.pid"
    # Generated by CONFIG REWRITE
    dir "/etc/softwares/redis-5.0.14/conf"
    # 这里我把该服务设置为6379的从服务了
    replicaof 192.168.1.7 6379
    

    6381.conf

    #配置文件进行了精简,完整配置可自行和官方提供的完整conf文件进行对照。端口号自>行对应修改
    #后台执行的意思
    daemonize yes
    #端口号
    port 6381
    #IP绑定,redis不建议对公网开放,直接绑定0.o.o.0没毛病
    bind 0.0.0.0
    # 这个文件会自动生成(如果统一台服务器上启动,注意要修改为不同的端口)
    pidfile "/var/run/redis_6381.pid"
    # Generated by CONFIG REWRITE
    dir "/etc/softwares/redis-5.0.14/conf"
    # 这里我把该服务设置为6379的从服务了
    replicaof 192.168.1.7 6379
    

    注意:以上的配置文件,其实在哨兵从新选举master后会自动添加一些内容,如配置slave等等,如下就是我将6379 停止后又重新启动后新的6379.conf文件的内容

    #配置文件进行了精简,完整配置可自行和官方提供的完整conf文件进行对照。端口号自>行对应修改
    #后台后动的意思
    daemonize yes
    #端口号
    port 6379
    #IP绑定,redis不建议对公网开放,直接绑定0.o.o.0没毛病
    bind 0.0.0.0
    # 这个文件会自动生成(如果统一台服务器上启动,注意要修改为不同的端口)
    pidfile "/var/run/redis_6379.pid"
    # Generated by CONFIG REWRITE
    dir "/etc/softwares/redis-5.0.14/conf"
    replicaof 192.168.1.7 6381
    

    3. 配置sentinel.conf文件

    • sentinel_26379.conf
    # 配置文件:sentinel.conf,在sentinel运行期间是会被动态修改的
    # sentinel如果重启时,根据这个配置来恢复其之前所监控的redis集群的状态
    # 绑定IP
    bind 0.0.0.0
    # 后台运行
    daemonize yes
    # 默认yes,没指定密码或者指定IP的情况下,外网无法访问
    protected-mode no
    # 哨兵的端口,客户端通过这个端口来发现redis
    port 26379
    # 哨兵自己的IP,手动设定也可自动发现,用于与其他哨兵通信
    # sentinel announce-ip
    # 临时文件夹
    dir /tmp
    # 日志
    logfile "/usr/local/redis/logs/sentinel-26379.log"
    # sentinel监控的master的名字叫做mymaster,初始地址为 192.168.1.7 6379,2代表两个及以上哨兵认定为死亡,才认为是真的死亡
    sentinel monitor mymaster 192.168.1.7 6379 2
    # 发送心跳PING来确认master是否存活
    # 如果master在“一定时间范围”内不回应PONG 或者是回复了一个错误消息,那么这个sentinel会主观地(单方面地)认为这个master已经不可用了
    sentinel down-after-milliseconds mymaster 1000
    # 如果在该时间(ms)内未能完成failover操作,则认为该failover失败
    sentinel failover-timeout mymaster 3000
    # 指定了在执行故障转移时,最多可以有多少个从Redis实例在同步新的主实例,在从Redis实例较多的情况下这个数字越小,同步的时间越长,完成故障转移所需的时间就越长
    sentinel parallel-syncs mymaster 1
    
    • 其他的两个哨兵的配置文件类似即可
      注意,以上哨兵的配置文件,其实在启动哨兵监控之后,后边也是会随着程序的运行而进行修改的。

    4. 启动redis服务和哨兵服务

    • 启动redis服务
    redis-server /etc/softwares/redis/conf/6379.conf
    redis-server /etc/softwares/redis/conf/6380.conf
    redis-server /etc/softwares/redis/conf/6381.conf
    
    • 启动哨兵服务
    redis-server /etc/softwares/redis/conf/sentinel_26379.conf --sentinel
    redis-server /etc/softwares/redis/conf/sentinel_26380.conf --sentinel
    redis-server /etc/softwares/redis/conf/sentinel_26381.conf --sentinel
    

    4. 哨兵启动和配置

    启动命令:

    redis-server /path/to/sentinel.conf --sentinel
    

    配置文件启动时指定,运行过程中会自动变更,记录哨兵的监测结果



    4. 7大核心概念

    1. 哨兵如何知道Redis主从信息(自动发现机制)

    哨兵配置文件中,保存着主从集群中master的信息,可以通过info命令,进行主从信息自动发现。

    2. 什么是master主观下线(sdown)


    主观下线:单个哨兵自身认为redis实例已经不能提供服务
    检测机制:哨兵向redis发送ping请求,+PONG、-LOADING、-MASTERDOWN这三种情况视为正常,其他回复均视为无效。
    对应配置文件的配置项:
    sentinel down-after-milliseconds mymaster 1000
    

    3. 什么是客观下线(odown)


    客观下线:一定数量值的哨兵认为master已经下线。
    检测机制:当哨兵主观认为master下线后,则会通过SENTINEL is-master-down-by-addr命令询问其他哨兵是否认为master已经下线,如果达成共识(达到quorum个数),就会认为master节点客观下线,开始故障转移流程。

    对应配置文件的配置项:

    sentinel monitor mymaster 192.168.1.7 63802
    

    4. 哨兵之间如何通信(哨兵之间的自动发现)

    • 哨兵之间的自动发现

    可以通过订阅__sentinel__:hello查看(这里是两个下划线)

    127.0.0.1:6381> subscribe __sentinel__:hello
    Reading messages... (press Ctrl-C to quit)
    1) "subscribe"
    2) "__sentinel__:hello"
    3) (integer) 1
    1) "message"
    2) "__sentinel__:hello"
    3) "192.168.1.7,26379,77c4eee636d8ebde940501ad29619aac9cbfe0a4,1,mymaster,192.168.1.7,6381,1"
    1) "message"
    2) "__sentinel__:hello"
    3) "192.168.1.7,26380,a8d706e8bb773379298a35260ddca1c191b6a1db,1,mymaster,192.168.1.7,6381,1"
    1) "message"
    2) "__sentinel__:hello"
    3) "192.168.1.7,26381,412a7b6262493ec06e792edb710b4fdfb2f08edf,1,mymaster,192.168.1.7,6381,1"
    1) "message"
    2) "__sentinel__:hello"
    3) "192.168.1.7,26379,77c4eee636d8ebde940501ad29619aac9cbfe0a4,1,mymaster,192.168.1.7,6381,1"
    1) "message"
    2) "__sentinel__:hello"
    3) "192.168.1.7,26380,a8d706e8bb773379298a35260ddca1c191b6a1db,1,mymaster,192.168.1.7,6381,1"
    1) "message"
    2) "__sentinel__:hello"
    3) "192.168.1.7,26381,412a7b6262493ec06e792edb710b4fdfb2f08edf,1,mymaster,192.168.1.7,6381,1"
    1) "message"
    2) "__sentinel__:hello"
    3) "192.168.1.7,26379,77c4eee636d8ebde940501ad29619aac9cbfe0a4,1,mymaster,192.168.1.7,6381,1"
    
    • 哨兵之间通过命令进行通信
    • 哨兵之间通过订阅发布进行通信

    5. 哪个哨兵负责故障转移?(哨兵领导选举机制)
    基于Raft算法实现的选举机制,流程简述如下:

    1. 拉票阶段:每个哨兵节点希望自己成为领导者;
    2. sentinel节点收到拉票命令后,如果没有收到或同意过其他sentinel节点的请求,就同意该sentinel节点的请求(每个sentinel只持有一个同意票数);
    3. 如果sentinel节点发现自己的票数已经超过一半的数值,那么它将成为领导者,去执行故障转移;
    4. 投票结束后,如果超过failover-timeout的时间内,没进行实际的故障转移操作,则重新拉票选举。

    其实为了更好的选出来领导者,他们的拉票请求正常来说都是错开时间段来发起的,这可以避免如三个哨兵同时都获得一票的情况,但如果真的出现这种情况了的话,其实也会从新发起拉票。

    注:以了解raft协议为主。https://raft.github.io/http://thesecretlivesofdata.com/

    6. slave选举机制(选举方案)

    7. 最终主从切换的过程

    • 针对即将成为master的slave节点,将其撤出主从集群自动执行:
    slaveof no one
    
    • 针对其他slave节点,使它们成为新master的从属自动执行:
    slaveof new_master_host new_master_port
    

    5. 简单分析 哨兵同步pubsub机制发出来的消息

    # https://redis.io/topics/sentinel#pubsub-messages
    +reset-master <instance details> -- 当master被重置时.
    +slave <instance details> -- 当检测到一个slave并添加进slave列表时.
    +failover-state-reconf-slaves <instance details> -- Failover状态变为reconf-slaves状态时
    +failover-detected <instance details> -- 当failover发生时
    +slave-reconf-sent <instance details> -- sentinel发送SLAVEOF命令把它重新配置时
    +slave-reconf-inprog <instance details> -- slave被重新配置为另外一个master的slave,但数据复制还未发生时。
    +slave-reconf-done <instance details> -- slave被重新配置为另外一个master的slave并且数据复制已经与master同步时。
    -dup-sentinel <instance details> -- 删除指定master上的冗余sentinel时 (当一个sentinel重新启动时,可能会发生这个事件).
    +sentinel <instance details> -- 当master增加了一个sentinel时。
    +sdown <instance details> -- 进入SDOWN状态时;
    -sdown <instance details> -- 离开SDOWN状态时。
    +odown <instance details> -- 进入ODOWN状态时。
    -odown <instance details> -- 离开ODOWN状态时。
    +new-epoch <instance details> -- 当前配置版本被更新时。
    +try-failover <instance details> -- 达到failover条件,正等待其他sentinel的选举。
    +elected-leader <instance details> -- 被选举为去执行failover的时候。
    +failover-state-select-slave <instance details> -- 开始要选择一个slave当选新master时。
    +no-good-slave <instance details> -- 没有合适的slave来担当新master
    +selected-slave <instance details> -- 找到了一个适合的slave来担当新master
    +promoted-slave -- 确认成功
    +failover-state-reconf-slaves -- 开始对slaves进行reconfig操作
    +slave-reconf-sent -- 向指定的slave发送“slaveof”指令,告知此slave跟随新的master
    +slave-reconf-inprog -- 此slave正在执行slaveof + SYNC过程,slave收到“+slave-reconf-sent”之后将会执行slaveof操作
    +slave-reconf-done -- 此slave同步完成,此后leader可以继续下一个slave的reconfig操作
    failover-state-send-slaveof-noone <instance details> -- 当把选择为新master的slave的身份进行切换的时候。
    failover-end-for-timeout <instance details> -- failover由于超时而失败时。
    failover-end <instance details> -- failover成功完成,故障转移结束
    switch-master <master name> <oldip> <oldport> <newip> <newport> -- 当master的地址发生变化时。通常这是客户端最感兴趣的消息了。
    +tilt -- 进入Tilt模式。
    -tilt -- 退出Tilt模式。
    

    6. 哨兵服务的部署方案


    7. 例子

    首先记得引入lettuce的依赖哦

            <dependency>
                <groupId>io.lettuce</groupId>
                <artifactId>lettuce-core</artifactId>
                <version>${lettuce.version}</version>
            </dependency>
    
    • 创建config
    package cn.lazyfennec.cache.redis;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Profile;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.connection.RedisSentinelConfiguration;
    import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
    import org.springframework.data.redis.core.StringRedisTemplate;
    
    @Configuration
    @Profile("sentinel")
    class SentinelRedisAppConfig {
    
        @Bean
        public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
            StringRedisTemplate stringRedisTemplate = new StringRedisTemplate(redisConnectionFactory);
            return stringRedisTemplate;
        }
    
        @Bean
        public LettuceConnectionFactory redisConnectionFactory() {
            System.out.println("使用哨兵版本");
            RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration()
                    .master("mymaster")
                    // 哨兵地址
                    .sentinel("192.168.1.7", 26379)
                    .sentinel("192.168.1.7", 26380)
                    .sentinel("192.168.1.7", 26381);
            return new LettuceConnectionFactory(sentinelConfig);
        }
    }
    
    • 创建测试类
    package cn.lazyfennec.cache.redis;
    
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.test.context.ActiveProfiles;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:applicationContext.xml")
    @ActiveProfiles("sentinel") // 设置profile
    public class SentinelTests {
    
        @Autowired
        StringRedisTemplate stringRedisTemplate;
    
        @Test
        public void test() throws InterruptedException {
            // 每个一秒钟,操作一下redis,看看最终效果
            int i = 0;
            while (true) {
                i++;
                stringRedisTemplate.opsForValue().set("test-value", String.valueOf(i));
                System.out.println("修改test-value值为: " + i);
                Thread.sleep(1000L);
            }
        }
    }
    

    如果觉得有收获,欢迎点赞和评论,更多知识,请点击关注查看我的主页信息哦~

    相关文章

      网友评论

          本文标题:Redis的高可用机制 - 哨兵

          本文链接:https://www.haomeiwen.com/subject/rwtenrtx.html