模式二:哨兵模式
上一篇问讲述了redis集群的主从模式,这一篇我们讲述哨兵模式。
Redis 的 Sentinel 系统用于管理多个 Redis 服务器(instance), 该系统执行以下三个任务:
- 监控(Monitoring): Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。
- 提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
- 自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。
这个就相比于主从系统更加的灵活化,主从系统一旦主节点崩溃,整个系统写的功能就丧失。
Redis Sentinel 是一个分布式系统, 你可以在一个架构中运行多个 Sentinel 进程(progress), 这些进程使用流言协议(gossip protocols)来接收关于主服务器是否下线的信息, 并使用投票协议(agreement protocols)来决定是否执行自动故障迁移, 以及选择哪个从服务器作为新的主服务器。
哨兵进程工作方式:
- 每个Sentinel(哨兵)进程以每秒钟一次的频率向整个集群中的Master主服务器,Slave从服务器以及其他Sentinel(哨兵)进程发送一个 PING 命令。
- 如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 那么这个实例会被 Sentinel 标记为主观下线。 一个有效回复可以是: +PONG 、 -LOADING 或者 -MASTERDOWN 。
- 如果一个主服务器被标记为主观下线, 那么正在监视这个主服务器的所有 Sentinel 要以每秒一次的频率确认主服务器的确进入了主观下线状态。
- 如果一个主服务器被标记为主观下线, 并且有足够数量的 Sentinel (至少要达到配置文件指定的数量)在指定的时间范围内同意这一判断, 那么这个主服务器被标记为客观下线。
- 在一般情况下, 每个 Sentinel 会以每 10 秒一次的频率向它已知的所有主服务器和从服务器发送 INFO 命令。 当一个主服务器被 Sentinel 标记为客观下线时, Sentinel 向下线主服务器的所有从服务器发送 INFO 命令的频率会从 10 秒一次改为每秒一次。
- 只要一个 Sentinel 发现某个主服务器进入了客观下线状态, 这个 Sentinel 就可能会被其他 Sentinel 推选出, 并对失效的主服务器执行自动故障迁移操作。
- 当没有足够数量的 Sentinel 同意主服务器已经下线, 主服务器的客观下线状态就会被移除。 当主服务器重新向 Sentinel 的 PING 命令返回有效回复时, 主服务器的主观下线状态就会被移除。
主观下线以及客观下线解释:
主观下线(Subjectively Down, 简称 SDOWN)指的是单个 Sentinel 实例对服务器做出的下线判断。
客观下线(Objectively Down, 简称 ODOWN)指的是多个 Sentinel 实例在对同一个服务器做出 SDOWN 判断, 并且通过 SENTINEL is-master-down-by-addr 命令互相交流之后, 得出的服务器下线判断。
从主观下线状态切换到客观下线状态并没有使用严格的法定人数算法(strong quorum algorithm), 而是使用了流言协议: 如果 Sentinel 在给定的时间范围内, 从其他 Sentinel 那里接收到了足够数量的主服务器下线报告, 那么 Sentinel 就会将主服务器的状态从主观下线改变为客观下线。 如果之后其他 Sentinel 不再报告主服务器已下线, 那么客观下线状态就会被移除。
客观下线条件只适用于主服务器: 对于任何其他类型的 Redis 实例, Sentinel 在将它们判断为下线前不需要进行协商, 所以从服务器或者其他 Sentinel 永远不会达到客观下线条件。
Sentinel是如何发现其他的Sentinel 和从服务器的?
- 每个 Sentinel 会以每两秒一次的频率, 通过发布与订阅功能, 向被它监视的所有主服务器和从服务器的 sentinel:hello 频道发送一条信息, 信息中包含了 Sentinel 的 IP 地址、端口号和运行 ID (runid)。
- 每个 Sentinel 都订阅了被它监视的所有主服务器和从服务器的 sentinel:hello 频道, 查找之前未出现过的 sentinel (looking for unknown sentinels)。 当一个 Sentinel 发现一个新的 Sentinel 时, 它会将新的 Sentinel 添加到一个列表中, 这个列表保存了 Sentinel 已知的, 监视同一个主服务器的所有其他 Sentinel 。
- Sentinel 发送的信息中还包括完整的主服务器当前配置(configuration)。 如果一个 Sentinel 包含的主服务器配置比另一个 Sentinel 发送的配置要旧, 那么这个 Sentinel 会立即升级到新配置上。
- 在将一个新 Sentinel 添加到监视主服务器的列表上面之前, Sentinel 会先检查列表中是否已经包含了和要添加的 Sentinel 拥有相同运行 ID 或者相同地址(包括 IP 地址和端口号)的 Sentinel , 如果是的话, Sentinel 会先移除列表中已有的那些拥有相同运行 ID 或者相同地址的 Sentinel , 然后再添加新 Sentinel 。
从上述我们可以看出哨兵之间的互相发现以及发现从服务器,都是通过发布订阅的功能来实现的,既我们之前所讲的redis的发布订阅。当有一个新的哨兵加入进来就会向频道中发送自己的信息,其他所有订阅的哨兵会通过消费信息添加新的哨兵信息。
自动故障迁移过程:
- 发现主服务器已经进入客观下线状态。
- 在失效主服务器属下的从服务器当中, 那些被标记为主观下线、已断线、或者最后一次回复 PING 命令的时间大于五秒钟的从服务器都会被淘汰。
- 在失效主服务器属下的从服务器当中, 那些与失效主服务器连接断开的时长超过 down-after 选项指定的时长十倍的从服务器都会被淘汰。
- 在经历了以上两轮淘汰之后剩下来的从服务器中, 我们选出复制偏移量(replication offset)最大的那个从服务器作为新的主服务器; 如果复制偏移量不可用, 或者从服务器的复制偏移量相同, 那么带有最小运行 ID 的那个从服务器成为新的主服务器。
- 向被选中的从服务器发送 SLAVEOF NO ONE 命令,让它转变为主服务器。
- 通过发布与订阅功能, 将更新后的配置传播给所有其他 Sentinel , 其他 Sentinel 对它们自己的配置进行更新。
- 向已下线主服务器的从服务器发送 SLAVEOF 命令, 让它们去复制新的主服务器。
- 当所有从服务器都已经开始复制新的主服务器时, 领头 Sentinel 终止这次故障迁移操作。
上述简单讲述了:
- Sentinel是如何发现其他的Sentinel 以及从服务器
- Sentinel是如何判断主服务器主观下线以及客观下线的
- Sentinel是如何自动故障迁移的
下面我们将具体的搭建哨兵模式
首先我们像搭建主从模式一样,搭建出主从并启动,如上一篇文章一样,这里我们主节点为:6380端口,从节点为6381端口,如图所示:
启动主从后,6380redis文件中创建哨兵模式所需要的配置文件,需要启动几个哨兵就创建几个配置文件,如图:
配置文件内容如下:
如上就是我们需要的全部配置,现在我们开始启动两个哨兵。
启动命令为 redis-service sentinel.conf --sentinel
首先启动端口号为26379的哨兵,如图:
启动端口号为26380的哨兵,如图:
此时我们查看主节点服务的模式也是哨兵模式。
并且我们可以看到两个哨兵启动成功后,两个哨兵的配置文件也有所变化:
可以看到两个配置文件都自动添加了从节点以及另一个哨兵的信息。
此时我们的redis哨兵模式就创建成功了,后面我们测试主节点断开及主节点恢复的时候和java代码结合演示。
我们要整合哨兵模式,首先要修改redis的配置文件。如下:
@Component
@Slf4j
public class JedisConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("${spring.redis.pool.max-active}")
private int maxActive;
@Value("${spring.redis.pool.max-idle}")
private int maxIdle;
@Value("${spring.redis.pool.min-idle}")
private int minIdle;
@Value("${spring.redis.pool.max-wait}")
private long maxWaitMillis;
@Bean
public JedisSentinelPool redisPoolFactory(){
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(maxIdle);
jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
jedisPoolConfig.setMaxTotal(maxActive);
jedisPoolConfig.setMinIdle(minIdle);
//此处配置的哨兵信息,切记不要配置主节点地址,这样故障迁移的时候才能切换过来。
Set<String> sentinels = new HashSet<String>(Arrays.asList("127.0.0.1:26379","127.0.0.1:26380"));
// JedisPool jedisPool = new JedisPool(jedisPoolConfig,host,port,timeout,null);
JedisSentinelPool jedisPool = new JedisSentinelPool("mymaster",sentinels,jedisPoolConfig);
log.info("JedisPool注入成功!");
log.info("redis地址:" + host + ":" + port);
return jedisPool;
}
}
并将RedisClient中连接池修改:
//@Resource
//private JedisPool jedisPool;
@Resource
private JedisSentinelPool jedisPool;
这个具体配置之前文章中有详细内容
我们先测试一下哨兵模式是否连接正常
@Test
public void setRedis() {
try {
redisClient.set("testSentinel5","aaaa");
} catch (Exception e) {
e.printStackTrace();
}
}
运行结果:
可以看到连接正常,那么我们现在将主节点关掉,测试一下故障迁移。
从上图我们可以看出在6380主节点断开后,6381从节点讲过哨兵的故障迁移变为了主节点。
并且我们可以看到原先加在6381从节点redis.windows.conf配置文件中的slaveof配置以及自动删除了,而且两个哨兵中的主节点监控也由6380变为了6381.
我们再测试一下迁移后的写入功能。
@Test
public void setRedis() {
try {
redisClient.set("testSentinel6","aaaa");
} catch (Exception e) {
e.printStackTrace();
}
}
可以看出在主节点断开后,集群的写入查看功能正常。
接下来我们将6380恢复,看是否能够重新连接入集群:
可以看到6380重新开启后,自动变为从节点连接入集群,并在6380的redis.windows.conf的配置文件中自动加入了:
slaveof 10.66.205.85 6381
今天就写到这里了,Redis的哨兵模式是以主从模式为基础的,所以说,主从模式拥有的一些缺点,在哨兵模式下也具有。哨兵模式主要是监控Master主服务器的运行情况,当然也会监控Slave从服务器的运行情况,如果Master主服务器发生了故障,该模式可以保证Slave从服务器顺利升级为Master主服务器继续提供服务,以此提高系统的高可用性。虽然哨兵模式比主从模式提高了不少系统的高可用性,但是该模式不能水平扩容,不能动态的增、删节点,这也是限制哨兵模式广泛应用的主要原因。Redis也看到了这个情况,所在在Redis的3.x以后的版本提供了一个更加强大集群模式,那就是Cluster集群模式,这个模式也是我们下一篇文章的主题。