点击查看官方介绍
1. 哨兵(Sentinel)机制核心作用
2. 核心运作流程
- 服务发现和健康检查流程
- 故障切换流程
3. 实操的例子
这里受限于服务器数量,所以所有的内容都弄在一台服务器上了
1. 准备
- 3个redis服务
192.168.1.7 6379
192.168.1.7 6380
192.168.1.7 6381
- 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算法实现的选举机制,流程简述如下:
- 拉票阶段:每个哨兵节点希望自己成为领导者;
- sentinel节点收到拉票命令后,如果没有收到或同意过其他sentinel节点的请求,就同意该sentinel节点的请求(每个sentinel只持有一个同意票数);
- 如果sentinel节点发现自己的票数已经超过一半的数值,那么它将成为领导者,去执行故障转移;
- 投票结束后,如果超过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);
}
}
}
如果觉得有收获,欢迎点赞和评论,更多知识,请点击关注查看我的主页信息哦~
网友评论