介绍
Redis的主从复制有两个作用,第一个是给主提供了备份,当主节点挂掉之后,备份中有完整的数据,第二个是对主进行一个分流,例如读写分离功能,将大部分的读分流到从节点中去执行,减轻主节点的压力。但是主从复制存在的问题是当主节点出现问题时,需要手动故障转移,将一个从节点晋升为主节点;并且写能力和存储能力受到限制,都限制在一个节点上,其他节点都是备份。例如之前介绍的主从复制故障处理:

上述过程也可以依赖于脚本来完成,使用脚本不断与监控master节点是否有问题,如果有问题则将master进行下线,然后选择一个新的master,让其他节点都slaveof新的master节点。Redis针对上述问题提供了Redis sentinel的高可用实现,可以完成故障发现、故障自动转移、配置中心以及客户端通知等,下面我们看下Redis sentinel是如何解决这个的。
一、Redis Sentinel架构
Redis Sentinel架构首先依然是一个主从结构,还有多个sentinel节点,你可以把它想象成一个Redis进程,但这个进程它不会存储数据,它的作用是完后对Redis的一个故障判断与故障转移的处理,它可以对master和slave进行一个监控,每套master和slave会使用master-name作为一个标识。Redis Sentinel中的Sentinel节点个数应该大于等于3且最好为奇数。

对于客户端来说,它不会直接从Redis获取信息,不会记录某个Redis的ip地址,而是直接记录Redis sentinel的地址。客户端不关心谁是master和slave,而是由sentinel来监控master和slave的状态。
二、Redis Sentinel安装与配置
本机安装部署sentinel说明:

sentinel的主要配置说明:
port ${port}daemonize yespidfile "/var/run/redis-sentinel.pid"logfile "${port}.log"dir "/home/duan/myhome/soft/redis/redis-5.0.3/data"sentinel monitor mymaster 127.0.0.1 6379 2sentinel down-after-milliseconds mymaster 30000sentinel parallel-syncs mymaster 1sentinel failover-timeout mymaster 180000
2表示至少有多少个sentinel认为master有问题了则进行故障转移
30000毫秒表示30秒sentinel ping master ping不通,则认为master有问题
下面两个是复制配置,我们选择了新的master之后,其他老的slave会对新的master进行复制,1表示每次只能复制一个,减轻master的压力
最后一个是故障转移时间
下面介绍下Redis Sentinel的配置演示。
6379Redis配置:
[root@common redis-5.0.3]# cat redis_6379.confbind 0.0.0.0port 6379daemonize yespidfile var/run/redis_6379.pidlogfile "/home/duan/myhome/soft/redis/redis-5.0.3/logs/6379.log"dir /home/duan/myhome/soft/redis/redis-5.0.3/data/
6380Redis配置:
[root@node1 redis-5.0.3]# cat redis_6380.confport 6380daemonize yespidfile var/run/redis_6380.pidlogfile "/home/duan/myhome/soft/redis/redis-5.0.3/logs/6380.log"dir home/duan/myhome/soft/redis/redis-5.0.3/data/slaveof 192.168.1.102 6379
6381Redis配置:
[root@node2 redis-5.0.3]# cat redis_6381.confport 6381daemonize yespidfile var/run/redis_6381.pidlogfile "/home/duan/myhome/soft/redis/redis-5.0.3/logs/6381.log"dir home/duan/myhome/soft/redis/redis-5.0.3/data/slaveof 192.168.1.102 6379
可以通过快速生成从节点的配置信息:
[root@common config]# sed 's/6379/6380/g' redis_6379.conf > redis_6380.conf[root@common config]# sed 's/6379/6381/g' redis_6379.conf > redis_6381.conf
启动redis:
[root@common redis-5.0.3]# redis-server redis_6379.conf[root@common redis-5.0.3]# ps -ef | grep redisroot 4453 1 0 23:24 ? 00:00:00 redis-server *:6379root 4460 2983 0 23:24 pts/0 00:00:00 grep --color=auto redis[root@common redis-5.0.3]# redis-server redis_6380.conf[root@common redis-5.0.3]# redis-server redis_6381.conf
启动客户端:
[root@common redis-5.0.3]# redis-cli127.0.0.1:6379> pingPONG[root@node1 redis-5.0.3]# redis-cli -p 6380127.0.0.1:6380> pingPONG[root@node2 redis-5.0.3]# redis-cli -p 6381127.0.0.1:6381> pingPONG
查看主节点信息:
127.0.0.1:6379> info replication# Replicationrole:masterconnected_slaves:2slave0:ip=192.168.1.104,port=6380,state=online,offset=154,lag=2slave1:ip=192.168.1.106,port=6381,state=online,offset=154,lag=1master_replid:28cd61c74cf41734081951ba93c6a89055aade20master_replid2:0000000000000000000000000000000000000000master_repl_offset:154second_repl_offset:-1repl_backlog_active:1repl_backlog_size:1048576repl_backlog_first_byte_offset:1repl_backlog_histlen:154127.0.0.1:6379>
可以看到主节点信息有两个从节点connected_slaves:2
并且从节点的信息也已生成slave0和slave1
主从配置踩坑:

主从配置服务启动发现info replication的connected_slaves一直是0,查找原因发现主要是下面两个原因:
主节点配置:bind 0.0.0.0,这样redis才会listen来源于各个ip地址的请求,要不只会处理指定地址来的请求
因为我的Redis配置后台服务,所以查看日志看到了是和我后台自启动的redis冲突了,将后台自启动的Redis服务kill掉再查看,主从配置正常了
Redis的主从配置已经配置完了,接下来我们配置Redis sentinel。
6379sentinel配置:去掉注释和空格查看
[root@common redis-5.0.3]# cat sentinel.conf | grep -v "#" | grep -v "^$" > redis_sentinel_26379.conf[root@common redis-5.0.3]# cat redis_sentinel_26379.confport 26379daemonize yespidfile "/var/run/redis-sentinel.pid"logfile "/home/duan/myhome/soft/redis/redis-5.0.3/logs/26379.log"dir "/home/duan/myhome/soft/redis/redis-5.0.3/data"sentinel monitor mymaster 127.0.0.1 6379 2sentinel down-after-milliseconds mymaster 30000sentinel parallel-syncs mymaster 1sentinel failover-timeout mymaster 180000
使用Redis sentinel启动:
[root@common redis-5.0.3]# redis-sentinel redis_sentinel_26379.conf[root@common redis-5.0.3]# ps -ef | grep redis-sentinelroot 5280 1 0 00:24 ? 00:00:00 redis-sentinel *:26379 [sentinel]root 5295 2983 0 00:24 pts/0 00:00:00 grep --color=auto redis-sentinel[root@common redis-5.0.3]#
连接客户端:
[root@common redis-5.0.3]# redis-cli -p 26379127.0.0.1:26379> set hello world(error) ERR unknown command `set`, with args beginning with: `hello`, `world`,127.0.0.1:26379> pingPONG127.0.0.1:26379> info replication# Sentinelsentinel_masters:1sentinel_tilt:0sentinel_running_scripts:0sentinel_scripts_queue_length:0sentinel_simulate_failure_flags:0master0:name=mymaster,status=ok,address=127.0.0.1:6379,slaves=2,sentinels=1127.0.0.1:26379>
可以到master相关的信息,ip端口号,从节点的个数以及sentinels的个数。
启动sentinel之后查看配置变化:
[root@common redis-5.0.3]# cat redis_sentinel_26379.confport 26379daemonize yespidfile "/var/run/redis-sentinel.pid"logfile "/home/duan/myhome/soft/redis/redis-5.0.3/logs/26379.log"dir "/home/duan/myhome/soft/redis/redis-5.0.3/data"sentinel myid 46da8b1dd13d41c1f9dd00a17e19c1237f0150afsentinel deny-scripts-reconfig yessentinel monitor mymaster 127.0.0.1 6379 2sentinel config-epoch mymaster 0# Generated by CONFIG REWRITEprotected-mode nosentinel leader-epoch mymaster 0sentinel known-replica mymaster 192.168.1.106 6381sentinel known-replica mymaster 192.168.1.104 6380sentinel current-epoch 0
可以从#Generated by CONFIG REWRITE(配置重写产生的配置)看出:
发现master的两个从节点,6380,6381
去掉了我们默认的30000和180000(故障转移时间)以及复制的并行度等的配置
增加一些配置选举的一些东西
相同的步骤我们配置26380和26381,分别启动之后查看info sentinel信息:
[root@node1 redis-5.0.3]# redis-cli -p 26380127.0.0.1:26380> info sentinel# Sentinelsentinel_masters:1sentinel_tilt:0sentinel_running_scripts:0sentinel_scripts_queue_length:0sentinel_simulate_failure_flags:0master0:name=mymaster,status=ok,address=192.168.1.102:6379,slaves=2,sentinels=3127.0.0.1:26380>[root@node2 redis-5.0.3]# redis-cli -p 26381127.0.0.1:26381> info sentinel# Sentinelsentinel_masters:1sentinel_tilt:0sentinel_running_scripts:0sentinel_scripts_queue_length:0sentinel_simulate_failure_flags:0master0:name=mymaster,status=ok,address=192.168.1.102:6379,slaves=2,sentinels=3127.0.0.1:26381>
三、Java客户端来连接Redis Sentinel
客户端如果采用直连的方式,当master挂掉之后客户端是无法收到通知的,所以做不到客户端高可用。下面看一下客户端使用Redis Sentinel实现高可用的流程:

遍历Sentinel节点集合获取一个可用Sentinel节点
Sentinel节点会监控Redis节点,返回redis的master节点
通过master节点获取到节点的角色信息
通知客户端Redis数据节点的变化
Redis为了保证连接的高效,这里的实现并没有使用代理,基本原理如下图:

使用Java客户端连接代码示例:
JedisSentinelPool sentinelPoll = new JedisSentinelPool(masterName, sentinelSet, poolConfig, timeout);Jedis jedis = null;try {jedis = redisSentinelPoll.getResource();jedis common;} catch (Exception e) {log.error(e.getMessage(), e);} finally {if (jedis != null) {jedis.close();}}
四、故障转移
前面介绍了主从复制时的手动故障转移,下面我们介绍Redis Sentinel是怎么实现自动故障转移的。
Redis Sentinel的故障转移过程

多个sentinel发现并确认master有问题
选举出一个sentinel作为领导
选出一个slave作为master
通知其余slave成为新的master的slave
通知客户端主从变化
等待老的master复活称为新的master的slave
实际上就是手动故障转移变成sentinel进行故障发现以及故障自动转移以及通知客户端。
故障转移机制
redis sentinel可以对redis节点做失败判定和故障转移,通过三个定时任务实现了Sentinel节点对于主节点、从节点、其余Sentinel节点的监控。
1. 每10秒每个sentinel对master和slave执行info

发现slave节点
确认主从关系
2. 每2秒每个sentinel通过master节点的channel交换信息(pub/sub)

通过sentinel:hello频道交互
交互对节点的看法和自身信息
3. 每1秒每个sentinel对其他sentinel和redis执行ping

是一个心跳检测的过程
主观下线与客观下线
Redis Sentinel在对节点做失败判定时分为主观下线和客观下线。
主观下线:
sentinel down-after-millisenconds <masterName> <timeout>sentinel down-after-millisenconds myMaster 30000
每个sentinel节点对Redis节点失败的偏见
客观下线:
sentinel monitor <masterName> <ip> <port> <quorum>sentinel monitor myMaster 127.0.0.1 6379 2
所有sentinel节点对Redis节点失败达成共识(超过quorum个统一)。sentinel is-master-down-by-addr
领导者选举
当节点下线之后是Sentinel节点的领导者选举过程,原因是只有一个sentinel节点完成故障转移。选举时所有Sentinel节点通过sentinel is-master-down-by-addr命令都希望成为领导者。
领导者选举基本过程说明(raft算法):

每个做主观下线的sentinel节点向其他sentinel节点发送命令,要求将它设置为领导者
收到命令的sentinel节点如果没有同意通过其他sentinel节点发送的命令,那么将同意该请求,否则拒绝
如果该sentinel节点发现自己的票数已经超过sentinel集合半数且超过quorum,那么它将成为领导者
如果此过程有多个sentinel节点成为领导者,那么将等待一段时间重新进行选举
Raft算法是对一致性算法Paxos算法的升级精简版,更易于用于构建实际的系统中。 在分布式系统中,一致性指的是集群中的多个节点在状态上达成一致。但是在现实场景中,由于程序崩溃、网络故障、硬件故障、断电等原因,节点间的一致性很难保证,这样就需要Paxos、Raft等一致性协议。Paxos协议是Leslie Lamport在1990年提出的一种基于消息传递的、具有高度容错特性的一致性算法。但是Paxos有两个明显的缺点:第一个缺点就是Paxos算法难以理解。第二个缺点就是并没有提供构建现实系统的良好基础。
关于Paxos、Raft一致性协议算法目前也已经写完了,大家后续可以关注。
故障转移详情
从slave节点中选出一个合适的节点作为新的master节点
对上述的slave节点执行slaveof no one命令让其成为master节点
向剩余的slave节点发送命令,让他们成为新的master节点的slave节点,复制规则和parallel-syncs参数有关:
1. 假如现在是一主三从,主挂掉了,选择一个新slave节点成为master,剩余两个slave复制master。2. 如果parallel-syncs=2,则两个slave同时执行这个复制操作,redis还是只生成一份RDB文件,但是会同时去进行RDB文件的发送和buff数据的发送给两个slave,所以对于master节点会占用很多的网络资源开销去传输这个RDB文件。3. 如果parallel-syncs=1,则两个slave复制是一个顺序的过程,首先是第一个slave复制,然后第二个slave节点完成复制,减少master节点的资源占用。更新对原来master节点配置为slave,并保持着对其关注,当其恢复后命令它去复制新的master节点
选择合适的slave节点成为master:
选择slave-priority(slave节点优先级)最高的slave节点(比如机器配置最好的slave节点),如果存在则返回,不存在则继续
选择复制偏移量最大的slave节点(复制的最完整),如果存在则返回,不存在则继续
选择runId最小的slave节点(启动最早的节点)
故障转移实现过程我们可以开3个Sentinel节点,使用kill -9 master sentinel pid,然后30s后观察日志的方式来熟悉,主要涉及到redis的主从节点日志和sentinel的主从节点日志。
五、高可用读写分离
从节点有个很重的作用是扩展读能力,Redis sentinel这个场景下读写分离有一个问题是,只会对master节点做故障转移,从节点下线的时候,对salve节点只会做一个主观下线的一个判断,从节点无法完成到客户端感知到一个新的从节点的过程,当一个slave节点挂掉后我们如何做客户端读转移呢?实际上和客户端需要知道的三个消息有关:参考JedisSentinelPool的源码(initSentinels)实现:
+switch-master:切换主节点(从节点晋升主节点)
+convert-to-slave:切换从节点(原主节点降为从节点)
+sdown:主观下线
类似于下图处理逻辑:

但是需要自己实现类似JedisSentinelPool客户端源码的一个过程。监听处理以上三个消息的处理。
推荐阅读

看完本文有收获?请转发分享给更多人
关注「并发编程之美」,一起交流Java学习心得




