1. redis sentinel
故障转移的基本原理:
- 多个sentinel发现并确认master有问题
- 选举出一个sentinel作为领导
- 选出一个slave称为新的master的slave
- 通知其他的slave称为新的master的slave
- 通知客户端主从变化
- 等待老的master复活称为新的master的slave
也支持多个master-slave结构:
2. 安装与配置
- 配置开启主从节点
- 配置开启sentinel监控主节点(sentinel是特殊的redis)
- 实际应该多台机器,但是演示方便,只用一台机器来搭建
- 详细配置节点
本地安装的结构图:
对于master:redis-7000.conf配置:
port 7000
daemonize yes
pidfile /usr/local/redis/data/redis-7000.pid
logfile "7000.log"
dir "/usr/local/redis/data"
对于slave:redis-7001和redis-7002配置:
port 7001
daemonize yes
pidfile /usr/local/redis/data/redis-7001.pid
logfile "7001.log"
dir "/usr/local/redis/data"
slaveof 127.0.0.1 7000
启动redis服务:
redis-server ../config/redis-7000.conf
访问7000端口的master redis:
redis-cli -p 7000 info replication
显示他有两个从节点:
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=7002,state=online,offset=99550,lag=1
slave1:ip=127.0.0.1,port=7001,state=online,offset=99816,lag=0
master_repl_offset:99816
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:99815
对于sentinel主要配置:
master sentinel config:
port 26379
daemonize yes
dir "/usr/local/redis/data"
logfile "26379.log"
sentinel monitor mymaster 127.0.0.1 7000 2
...
启动redis sentinel:
redis-sentinel ../config/redis-sentinel-26379.conf
访问26379 redis sentinel master:
redis-cli -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:7000,slaves=2,sentinels=3
查看这六个进程是否都起来了:ps -ef | grep redis
注意,如果上面是配置在虚拟机的话,需要将127.0.0.1改为虚拟机的ip,要不然找不着。
3. 故障转移演练
3.1 java客户端程序
JedisSentinelPool只是一个配置中心,不需要具体连接某个redis,注意它不是代理。
private Logger logger = LoggerFactory.getLogger(AppTest.class);
@Test
public void test4(){
//哨兵配置,我们访问redis,就通过sentinel来访问
String masername = "mymaster";
Set<String> sentinels = new HashSet<>();
sentinels.add("10.128.24.176:26379");
sentinels.add("10.128.24.176:26380");
sentinels.add("10.128.24.176:26381");
JedisSentinelPool sentinelPool = new JedisSentinelPool(masername,sentinels);
//一个while死循环,每隔一秒往master塞入一个值,并且日志打印
while (true){
Jedis jedis = null;
try{
jedis = sentinelPool.getResource();
int index = new Random().nextInt(100000);
String key = "k-" + index;
String value = "v-" + index;
jedis.set(key,value);
logger.info("{} value is {}",key,jedis.get(key));
TimeUnit.MILLISECONDS.sleep(1000);
}catch (Exception e){
logger.error(e.getMessage(),e);
}finally {
if(jedis != null){
jedis.close();
}
}
}
}
maven依赖是:
<!--jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<!--slf4j日志接口-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.6</version>
</dependency>
<!--logback日志实现-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.1</version>
</dependency>
启动程序,发现是正常写入:
16:16:01.424 [main] INFO com.njupt.swg.AppTest - k-54795 value is v-54795
16:16:02.426 [main] INFO com.njupt.swg.AppTest - k-55630 value is v-55630
16:16:03.429 [main] INFO com.njupt.swg.AppTest - k-70642 value is v-70642
16:16:04.430 [main] INFO com.njupt.swg.AppTest - k-42978 value is v-42978
16:16:05.431 [main] INFO com.njupt.swg.AppTest - k-96297 value is v-96297
16:16:06.433 [main] INFO com.njupt.swg.AppTest - k-4220 value is v-4220
16:16:07.435 [main] INFO com.njupt.swg.AppTest - k-34103 value is v-34103
16:16:08.436 [main] INFO com.njupt.swg.AppTest - k-9177 value is v-9177
16:16:09.437 [main] INFO com.njupt.swg.AppTest - k-24389 value is v-24389
16:16:10.439 [main] INFO com.njupt.swg.AppTest - k-32325 value is v-32325
16:16:11.440 [main] INFO com.njupt.swg.AppTest - k-68538 value is v-68538
16:16:12.441 [main] INFO com.njupt.swg.AppTest - k-36233 value is v-36233
16:16:13.443 [main] INFO com.njupt.swg.AppTest - k-305 value is v-305
16:16:14.444 [main] INFO com.njupt.swg.AppTest - k-59279 value is v-59279
我们将现在的端口为7000的redis master 给kill掉
kill -9 master的pid
我们会发现:客户端报异常,但是在大概十几秒之后,就继续正常塞值了。原因是服务端的哨兵机制的选举matser需要一定的时间。
4. 三个定时任务
4.1 每10秒每个sentinel对master和slave执行Info
- 发现slave节点
- 确认主从关系
4.2 每2秒每个sentinel通过master节点的channel交换信息(pub/sub)
- 通过sentinel:hello进行频道交互
- 交互对节点的“看法”和自身信息
4.3 每1秒每个sentinel对其他sentinel和redis执行ping
- 心跳监测,失败判定依据
5. 主观下线和客观下线
对于之前的Sentinel配置文件中有两条配置:
监控master redis节点,这里是当超过两个sentinel认为master挂了,则认为master挂了。
sentinel monitor
sentinel monitor mymaster 127.0.0.1 6379 2
这里是每秒sentinel都回去Ping周围的master redis,超过30秒没有任何相应,说明其挂了。
sentinel down-after-milliseconds
sentinel down-after-milliseconds mymaster 300000
5.1 主观下线
主观下线:每个sentinel节点对Redis节点失败的“偏见”
这是一种主观下线。因为在复杂的网络环境下,这个sentinel与这个master不通,但是master与其他的sentinel都是通的呢?所以是一种“偏见”
这是依靠的第三种定时:每秒去ping一下周围的sentinel和redis。对于slave redis,可以使用这个主观下线,因为他不需要进行故障转移。
5.2 客观下线
客观下线:所有sentinel节点对master Redis节点失败“达成共识”(超过quorum个则统一)
这是依靠的第二种定时:每两秒,sentinel之间进行“商量”,传递的消息是:sentinel is-master-down-by-addr
对于master redis的下线,必须要达成共识才可以,因为涉及故障转移,仅仅依靠一个sentinel判断是不够的。
6. 领导者选举
原因:只有一个sentinel节点完成故障转移
选举:通过sentinel is-master-down-by-addr命令都希望成为领导者
- 每个做主观下线的sentinel节点向其他sentinel节点发送命令,要求将它设置为领导者
- 收到命令的sentinel节点如果没有同意通过其他semtinel节点发送的命令,那么将同意该请求,否则拒绝
- 如果该sentinel节点发现自己的票数已经超过sentinel集合半数并且超过quorum,那么它将成为领导者。
- 如果此过程中多个sentinel节点成为了领导者,那么将等待一段时间重新进行选举
7. 故障转移
- 从slave节点中选出一个“合适的”节点作为新的master节点
- 对上述的slave节点执行“slaveof no one”命令使其成为master节点
- 向剩余的slave节点发送命令,让它们成为新master节点的slave节点,复制规则和parallel-syncs参数一样
- 更新对原来的master节点配置为slave,并保持着对其“关注”,当恢复后命令他去复制新的master节点
那么,如何选择“合适”的slave节点呢?
- 选择slave-priority(slave节点优先级)最高的slave节点,如果存在则返回,不存在则继续。
- 选择复制偏移量对打的slave节点(复制得最完整),如果存在则返回,不存在则继续
- 选择run_id最小的slave节点(最早的节点)
8. 节点下线
主节点下线:sentinel failover
从节点下线要注意读写分离问题。
9. 总结与思考
redis sentinel是redis高可用实现方案:故障发现、故障自动转移、配置中心、客户端通知。
redis sentinel从redis2.8版本才正式生产可用,之前版本不可生产用。
尽可能在不同物理机上部署redis sentinel所有节点。
redis sentinel中的sentinel节点个数应该大于等于3且最好是奇数。
redis sentinel中的数据节点和普通数据节点没有区别。每个sentinel节点在本质上还是一个redis实例,只不过和redis数据节点不同的是,其主要作用是监控redis数据节点
客户端初始化时连接的是sentinel节点集合,不再是具体的redis节点,但sentinel只是配置中心不是代理。
redis sentinel通过三个定时任务实现了sentinel节点对于主节点、从节点、其余sentinel节点的监控。
redis sentinel在对节点做失败判定时分为主观下线和客观下线。
看懂redis sentinel故障转移日志对于redis sentinel以及问题排查非常有用。
redis sentinel实现读写分离高可用可以依赖sentinel节点的消息通知,获取redis数据节点的状态变化。
redis sentinel可以实现高可用的读写分离,高可用体现在故障转移,那么实现高可用的基础就是要有从节点,主从节点还实现了读写分离,减少master的压力。但是如果是从节点下线了,sentinel是不会对其进行故障转移的,并且连接从节点的客户端也无法获取到新的可用从节点,而这些问题在Cluster中都得到了有效的解决。
对于性能提高、容量扩展的时候,这种方式是比较复杂的,比较推荐的是使用集群,就是下面讨论的redis cluster!