Redis集群中的哨兵机制详解

艺帆风顺 发布于 2025-04-02 19 次阅读


四、哨兵(Sentinel)模式

1、哨兵是什么能干嘛?

反客为主的自动版,能够自动监控master是否发生故障,如果故障了会根据投票数从slave中挑选一个作为master,其他的slave会自动转向同步新的master,实现故障自动转义。

2、哨兵(Sentinel)模式原理

  • 第一:哨兵节点会以每秒一次的频率对每个 Redis 节点发送PING命令,并通过 Redis 节点的回复来判断其运行状态。

  • 第二:当哨兵监测到主服务器发生故障时,会自动在从节点中选择一台将机器,并其提升为主服务器,然后使用 PubSub 发布订阅模式,通知其他的从节点,修改配置文件,跟随新的主服务器。当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。

但是这种情况存在误判的可能,比如:可能master并没有挂,只是sentinel和master之间的网络不通导致,导致ping失败。为了避免误判,通常会启动多个sentinel,一般是奇数个,比如3个,那么可以指定当有多个sentinel都觉得master挂掉了,此时才断定master真的挂掉了,通常这个值设置为sentinel的一半,比如sentinel的数量是3个,那么这个量就可以设置为2个。

多个哨兵之间也存在互相监控,这就形成了多哨兵模式,现在对该模式的工作过程进行讲解,介绍如下:

  1. 主观下线: 主观下线,适用于主服务器和从服务器。如果在规定的时间内(配置参数:down-after-milliseconds),Sentinel节点没有收到目标服务器的有效回复,则判定该服务器为“主观下线”。比如 Sentinel1向主服务发送了PING命令,在规定时间内没收到主服务器PONG回复,则 Sentinel1 判定主服务器为“主观下线”。

  2. 客观下线: 客观(被动)下线,只适用于主服务器。Sentinel1 发现主服务器出现了故障,它会通过相应的命令,询问其它 Sentinel 节点对主服务器的状态判断。如果超过半数以上的 Sentinel 节点认为主服务器 down 掉,则 Sentinel1 节点判定主服务为“客观下线”。

  3. 投票选举: 投票选举,所有 Sentinel 节点会通过投票机制,按照谁发现谁去处理的原则,选举 Sentinel1 为领头节点去做 Failover(故障转移)操作。Sentinel1 节点则按照一定的规则在所有从节点中选择一个最优的作为主服务器,然后通过发布订阅功能通知其余的从节点(slave)更改配置文件,跟随新上任的主服务器(master)。至此就完成了主从切换的操作。

对上对述过程做简单总结:

Sentinel 负责监控主从节点的“健康”状态。当主节点挂掉时,自动选择一个最优的从节点切换为主节点。客户端来连接 Redis 集群时,会首先连接 Sentinel,通过 Sentinel 来查询主节点的地址,然后再去连接主节点进行数据交互。当主节点发生故障时,客户端会重新向 Sentinel 要地址,Sentinel 会将最新的主节点地址告诉客户端。因此应用程序无需重启即可自动完成主从节点切换。

3、搭建1主2从3个哨兵

下面我们来实现1主2从以及3个sentinel的配置,当从的挂掉之后,要求最少有2个sentinel认为主的挂掉了,才进行故障转移。

为了方便,我们在一台机器上进行模拟,通过端口来区分6个不同的节点(1个master、2个slave、3个sentinel),节点配置信息如下

3.1、创建3个sentinel配置文件

我们直接就是基于上面的 1主2从 的 主从复制 配置然后给他配置3个sentinel哨兵。

(1)创建sentinel1的配置文件:sentinel-26379.conf

在/opt/redis-stable/config目录创建 sentinel-26379.conf 文件,内容如下

注意:127.0.0.1需要替换成master主机的ip,不然windows连接哨兵会出现问题!

    cd /opt/redis-stable/config/touch sentinel-26379.confvi sentinel-26379.conf
      bind 0.0.0.0# 配置文件目录dir /opt/redis-stable/config/# 日志文件位置logfile "./sentinel-26379.log"# pid文件pidfile /var/run/sentinel_26379.pid# 是否后台运行daemonize yes# 端口port 26379# 监控主服务器master的名字:mymaster,IP:127.0.0.1,port:6379,最后的数字2表示当Sentinel集群中有2个Sentinel认为master存在故障不可用,则进行自动故障转移sentinel monitor mymaster 127.0.0.1 6379 2# master响应超时时间(毫秒),Sentinel会向master发送ping来确认master,如果在20秒内,ping不通master,则主观认为master不可用sentinel down-after-milliseconds mymaster 20000# 故障转移超时时间(毫秒),如果3分钟内没有完成故障转移操作,则视为转移失败sentinel failover-timeout mymaster 180000# 故障转移之后,进行新的主从复制,配置项指定了最多有多少个slave对新的master进行同步,那可以理解为1是串行复制,大于1是并行复制sentinel parallel-syncs mymaster 1# 指定mymaster主的密码(没有就不指定)sentinel auth-pass mymaster 123456

      (2)创建sentinel2的配置文件:sentinel-26380.conf

        touch sentinel-26380.confvi sentinel-26380.conf
          bind 0.0.0.0# 配置文件目录dir /opt/redis-stable/config/# 日志文件位置logfile "./sentinel-26380.log"# pid文件pidfile /var/run/sentinel_26380.pid# 是否后台运行daemonize yes# 端口port 26380# 监控主服务器master的名字:mymaster,IP:127.0.0.1,port:6379,最后的数字2表示当Sentinel集群中有2个Sentinel认为master存在故障不可用,则进行自动故障转移sentinel monitor mymaster 127.0.0.1 6379 2# master响应超时时间(毫秒),Sentinel会向master发送ping来确认master,如果在20秒内,ping不通master,则主观认为master不可用sentinel down-after-milliseconds mymaster 20000# 故障转移超时时间(毫秒),如果3分钟内没有完成故障转移操作,则视为转移失败sentinel failover-timeout mymaster 180000# 故障转移之后,进行新的主从复制,配置项指定了最多有多少个slave对新的master进行同步,那可以理解为1是串行复制,大于1是并行复制sentinel parallel-syncs mymaster 1# 指定mymaster主的密码(没有就不指定)sentinel auth-pass mymaster 123456

          (3)创建sentinel3的配置文件:sentinel-26381.conf

            touch sentinel-26381.confvi sentinel-26381.conf
              bind 0.0.0.0# 配置文件目录dir /opt/redis-stable/config/# 日志文件位置logfile "./sentinel-26381.log"# pid文件pidfile /var/run/sentinel_26381.pid# 是否后台运行daemonize yes# 端口port 26381# 监控主服务器master的名字:mymaster,IP:127.0.0.1,port:6379,最后的数字2表示当Sentinel集群中有2个Sentinel认为master存在故障不可用,则进行自动故障转移sentinel monitor mymaster 127.0.0.1 6379 2# master响应超时时间(毫秒),Sentinel会向master发送ping来确认master,如果在20秒内,ping不通master,则主观认为master不可用sentinel down-after-milliseconds mymaster 20000# 故障转移超时时间(毫秒),如果3分钟内没有完成故障转移操作,则视为转移失败sentinel failover-timeout mymaster 180000# 故障转移之后,进行新的主从复制,配置项指定了最多有多少个slave对新的master进行同步,那可以理解为1是串行复制,大于1是并行复制sentinel parallel-syncs mymaster 1# 指定mymaster主的密码(没有就不指定)sentinel auth-pass mymaster 123456
              3.2、启动3个sentinel

              启动sentinel有2种方式

              • 方式1:redis-server sentinel.conf --sentinel

              • 方式2:redis-sentinel sentinel.conf

              下面我们使用方式2来启动3个sentinel

                /opt/redis-stable/src/redis-sentinel /opt/redis-stable/config/sentinel-26379.conf/opt/redis-stable/src/redis-sentinel /opt/redis-stable/config/sentinel-26380.conf/opt/redis-stable/src/redis-sentinel /opt/redis-stable/config/sentinel-26381.conf

                分别对3个sentinel执行下面命令,查看每个sentinel的信息

                /opt/redis-stable/src/redis-cli -h 127.0.0.1 -p 26379
                info sentinel

                sentinel1 的信息如下,其他2个sentinel的信息这里就不列了,大家自己去看一下

                注意:连接哨兵并不等于连接redis,我们会发现输入redis的相关命令会直接异常。关于Sentinel支持的命令在文章下方有记录!

                3.3、使用Redis DeskTop连接sentinel

                使用windows当中的Redis DeskTop客户端可以通过连接哨兵来操作redis。

                本质上他就是通过哨兵来获取到的Redis的主机,然后相当于是直接连接的Redis主机!

                sentinel当中配置的Redis的master的ip一定不可以是127.0.0.1,不然可能连接不上!

                 

                3.4、验证故障自动转移是否成功

                (1)在master中执行下面命令,停止master

                [root@localhost config]# /opt/redis-stable/src/redis-cli -h 127.0.0.1 -p 6379 -a 123456
                Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
                127.0.0.1:6379> shutdown
                (0.76s)

                (2)等待2分钟,等待完成故障转移

                sentinel中我们配置 down-after-milliseconds 的值是20秒,表示判断主机下线时间是20秒,所以我们等2分钟,让系统先自动完成故障转移。

                (3)查看slave1的主从信息,如下

                /opt/redis-stable/src/redis-cli -h 127.0.0.1 -p 6380 -a 123456
                info replication

                (4)查看slave2的主从信息,如下

                slave2变成master了,且slave2变成slave1的从库了,完成了故障转移。

                /opt/redis-stable/src/redis-cli -h 127.0.0.1 -p 6381 -a 123456
                info replication

                (5)redis的配置文件以及哨兵的配置文件都发生了变化:

                 

                (6)下面验证下slave1和slave2是否同步

                在slave2中执行下面命令

                  127.0.0.1:6381>set address chinaOK

                  在slave1中执行下面命令,查询一下address的值,效果如下,说明slave2和slave1同步正常

                  127.0.0.1:6381> get address
                  "china"
                  3.4、恢复旧的master自动俯首称臣

                  当旧的master恢复之后,会自动挂在新的master下面,咱们来验证下是不是这样的。

                  (1)执行下面命令,启动旧的master

                  /opt/redis-stable/src/redis-server /opt/redis-stable/config/redis-6379.conf

                  2)执行下面命令,连接旧的master

                  /opt/redis-stable/src/redis-cli -h 127.0.0.1 -p 6379 -a 123456

                  (3)执行下面命令,查看其主从信息:info replication

                  效果如下,确实和期望的一致。

                  4、Sentinel支持的命令

                  哨兵节点作为运行在特殊模式下的redis节点,其支持的命令与普通的redis节点不同。在运维中,我们可以通过这些命令查询或修改哨兵系统;不过更重要的是,哨兵系统要实现故障发现、故障转移等各种功能,离不开哨兵节点之间的通信,而通信的很大一部分是通过哨兵节点支持的命令来实现的。下面介绍哨兵节点支持的主要命令。

                  (1)基础查询:通过这些命令,可以查询哨兵系统的拓扑结构、节点信息、配置信息等。

                  • info sentinel:获取监控的所有主节点的基本信息

                  • sentinel masters:获取监控的所有主节点的详细信息

                  • sentinel master mymaster:获取监控的主节点mymaster的详细信息

                  • sentinel slaves mymaster:获取监控的主节点mymaster的从节点的详细信息

                  • sentinel sentinels mymaster:获取监控的主节点mymaster的哨兵节点的详细信息

                  • sentinel get-master-addr-by-name mymaster:获取监控的主节点mymaster的地址信息,前文已有介绍

                  • sentinel is-master-down-by-addr:哨兵节点之间可以通过该命令询问主节点是否下线,从而对是否客观下线做出判断

                  (2)增加/移除对主节点的监控

                  • sentinel monitor mymaster2 192.168.92.128 16379 2:与部署哨兵节点时配置文件中的sentinel monitor功能完全一样,不再详述

                  • sentinel remove mymaster2:取消当前哨兵节点对主节点mymaster2的监控

                  (3)强制故障转移

                  • sentinel failover mymaster:该命令可以强制对mymaster执行故障转移,即便当前的主节点运行完好;例如,如果当前主节点所在机器即将报废,便可以提前通过failover命令进行故障转移。

                  五、Jedis连接Sentinel模式

                  dependency>
                  groupId>redis.clientsgroupId>
                  artifactId>jedisartifactId>
                  version>4.4.2version>
                  dependency>
                    import redis.clients.jedis.HostAndPort;import redis.clients.jedis.Jedis;import redis.clients.jedis.JedisPoolConfig;import redis.clients.jedis.JedisSentinelPool;
                    import java.util.HashSet;import java.util.Set;
                    public class SentinelDemo { private static JedisSentinelPool jedisSentinelPool;
                    static { try { JedisPoolConfig config = new JedisPoolConfig(); //最大空闲连接数, 默认8个 config.setMaxIdle(8); //最大连接数, 默认8个 config.setMaxTotal(8); //最小空闲连接数, 默认0 config.setMinIdle(0); //获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间, 默认-1 config.setMaxWaitMillis(3000); //在获取连接的时候检查有效性,表示取出的redis对象可用, 默认false config.setTestOnBorrow(true);

                    //redis服务器列表 SetString> sentinels = new HashSet(); sentinels.add(new HostAndPort("192.168.115.78", 26379).toString()); sentinels.add(new HostAndPort("192.168.115.78", 26380).toString()); sentinels.add(new HostAndPort("192.168.115.78", 26381).toString());
                    //初始化连接池 jedisSentinelPool = new JedisSentinelPool("mymaster", sentinels, config, "123456"); } catch (Exception e) { e.printStackTrace(); } }
                    public static void main(String[] args) { // 从池中获取一个Jedis对象 Jedis jedis = jedisSentinelPool.getResource(); String aaa = jedis.get("aaa"); System.out.println(aaa); jedis.close(); }}

                    六、SpringBoot整合Sentinel模式

                    (1)引入redis的maven配置

                    dependency>
                    groupId>org.springframework.bootgroupId>
                    artifactId>spring-boot-starter-data-redisartifactId>
                    dependency>

                    (2)application.properties中配置redis sentinel信息

                    spring:
                    redis:
                    sentinel:
                    master: mymaster
                    nodes: 192.168.115.78:26379,192.168.115.78:26380,192.168.115.78:26381
                    # 连接超时时间(毫秒)
                    timeout: 60000
                    # Redis默认情况下有16个分片,这里配置具体使用的分片,默认是0
                    database: 0
                    # redis的密码
                    password: 123456

                    (3)使用RedisTemplate工具类操作redis

                    springboot中使用RedisTemplate来操作redis,需要在我们的bean中注入这个对象,代码如下:

                      @Autowiredprivate RedisTemplate redisTemplate;
                      // 用下面5个对象来操作对应的类型this.redisTemplate.opsForValue(); //提供了操作string类型的所有方法this.redisTemplate.opsForList(); // 提供了操作list类型的所有方法this.redisTemplate.opsForSet(); //提供了操作set的所有方法this.redisTemplate.opsForHash(); //提供了操作hash表的所有方法this.redisTemplate.opsForZSet(); //提供了操作zset的所有方法

                      (4)RedisTemplate示例代码

                        @RestController@RequestMapping("/redis")public class RedisController {
                        @Autowired private RedisTemplate redisTemplate;
                        /** * string测试 * * @return */ @RequestMapping("/stringTest") public String stringTest() { this.redisTemplate.delete("name"); this.redisTemplate.opsForValue().set("name", "路人"); String name = this.redisTemplate.opsForValue().get("name"); return name; }
                        /** * list测试 * * @return */ @RequestMapping("/listTest") public List listTest() { this.redisTemplate.delete("names"); this.redisTemplate.opsForList().rightPushAll("names", "刘德华", "张学友", "郭富城", "黎明");
                        List courses = this.redisTemplate.opsForList().range("names", 0, -1); return courses; }
                        /** * set类型测试 * * @return */ @RequestMapping("setTest") public Set setTest() { this.redisTemplate.delete("courses"); this.redisTemplate.opsForSet().add("courses", "java", "spring", "springboot"); Set courses = this.redisTemplate.opsForSet().members("courses"); return courses; }
                        /** * hash表测试 * * @return */ @RequestMapping("hashTest") public Map hashTest() { this.redisTemplate.delete("userMap"); Map map = new HashMap(); map.put("name", "路人"); map.put("age", "30"); this.redisTemplate.opsForHash().putAll("userMap", map); Map userMap = this.redisTemplate.opsForHash().entries("userMap"); return userMap; }
                        /** * zset测试 * * @return */ @RequestMapping("zsetTest") public Set zsetTest() { this.redisTemplate.delete("languages"); this.redisTemplate.opsForZSet().add("languages", "java", 100d); this.redisTemplate.opsForZSet().add("languages", "c", 95d); this.redisTemplate.opsForZSet().add("languages", "php", 70); Set languages = this.redisTemplate.opsForZSet().range("languages", 0, -1); return languages; }
                        /** * 查看redis机器信息 * * @return */ @RequestMapping(value = "/info", produces = MediaType.TEXT_PLAIN_VALUE) public String info() { Object obj = this.redisTemplate.execute(new RedisCallback() { @Override public Object doInRedis(RedisConnection connection) throws DataAccessException { return connection.execute("info"); } }); return obj.toString(); }

                        5)访问的时候出现如下异常

                        解决方案:在redis.conf 当中将no-writes-on-bgsave-error设置为 no

                        stop-writes-on-bgsave-error no
                          版权声明:本文内容始发于CSDN>作者:怪 咖@,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。始发链接:https://blog.csdn.net/weixin_43888891/article/details/131039418在此特别鸣谢原作者的创作。此篇文章的所有版权归原作者所有,商业转载建议请联系原作者,非商业转载请注明出