本文来自《Redis开发与运维》–付磊 / 张益军,读后收益匪浅。
但是主从复制也带来了以下问题:
当主节点出现故障时,Redis Sentinel能自动完成故障发现和故障转移,并通知应用方,从而实现真正的高可用。
Redis Sentinel是一个分布式架构,其中包含若干个Sentinel节点和Redis数据节点,每个Sentinel节点会对数据节点和其余Sentinel节点进行监控,当它发现节点不可达时,会对节点做下线标识。如果被标识的是主节点,它还会和其他Sentinel节点进行“协商”,当大多数Sentinel节点都认为主节点不可达时,它们会选举出一个Sentinel节点来完成自动故障转移的工作,同时会将这个变化实时通知给Redis应用方。整个过程完全是自动的,不需要人工来介入,所以这套方案很有效地解决了Redis的高可用问题。
注意
这里的分布式是指:Redis数据节点、Sentinel节点集合、客户端分布在多个物理节点的架构
redis-sentinel-26379.conf
port 26379
daemonize yes
logfile "26379.log"
dir /opt/soft/redis/data
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
Sentinel节点的启动方法有两种:
redis-sentinel redis-sentinel-26379.conf
redis-server redis-sentinel-26379.conf --sentinel
Sentinel节点本质上是一个特殊的Redis节点,所以也可以通过info命令来查询它的相关信息,从下面info的Sentinel片段来看,Sentinel节点找到了主节点127.0.0.1:6379,发现了它的两个从节点,同时发现Redis Sentinel一共有3个Sentinel节点。这里只需要了解Sentinel节点能够彼此感知到对方,同时能够感知到Redis数据节点就可以了:
$ redis-cli -h 127.0.0.1 -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:6379,slaves=2,sentinels=3
有2点需要强调一下:
Redis安装目录下有一个sentinel.conf,是默认的Sentinel节点配置文件
port 26379
dir /opt/soft/redis/data
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
#sentinel auth-pass
#sentinel notification-script
#sentinel client-reconfig-script
port和dir分别代表Sentinel节点的端口和工作目录,下面重点对sentinel相关配置进行详细说明。
配置如下:
sentinel monitor
Sentinel节点会定期监控主节点,所以从配置上必然也会有所体现,本配置说明Sentinel节点要监控的是一个名字叫做
,ip地址和端口为
的主节点。
代表要判定主节点最终不可达所需要的票数。但实际上Sentinel节点会对所有节点进行监控,但是在Sentinel节点的配置中没有看到有关从节点和其余Sentinel节点的配置,那是因为Sentinel节点会从主节点中获取有关从节点以及其余Sentinel节点的相关信息
例如某个Sentinel初始节点配置如下:
port 26379
daemonize yes
logfile "26379.log"
dir /opt/soft/redis/data
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
当所有节点启动后,配置文件中的内容发生了变化,体现在三个方面:
启动后变化为:
port 26379
daemonize yes
logfile "26379.log"
dir "/opt/soft/redis/data"
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel config-epoch mymaster 0
sentinel leader-epoch mymaster 0
#发现两个slave节点
sentinel known-slave mymaster 127.0.0.1 6380
sentinel known-slave mymaster 127.0.0.1 6381
#发现两个sentinel节点
sentinel known-sentinel mymaster 127.0.0.1 26380 282a70ff56c36ed56e8f7ee6ada741
24140d6f53
sentinel known-sentinel mymaster 127.0.0.1 26381 f714470d30a61a8e39ae031192f1fe
ae7eb5b2be
sentinel current-epoch 0
同时还与Sentinel节点的领导者选举有关,至少要有max(quorum,num(sentinels)/2+1)个Sentinel节点参与选举,才能选出领导者Sentinel,从而完成故障转移。例如有5个Sentinel节点,quorum=4,那么至少要有max(quorum,num(sentinels)/2+1)=4个在线Sentinel节点才可以进行领导者选举
配置如下:
sentinel down-after-milliseconds
每个sentinel节点都要通过定期发送ping命令来判断Redis数据节点和其余sentinel节点是否可达,如果超过了down-after-millisends配置的时间且没有有效回复,则判定节点不可达,
运维提示
down-after-milliseconds虽然以为参数,但实际上对Sentinel节点、主节点、从节点的失败判定同时有效。
配置如下:
sentinel parallel-syncs
当sentinel节点集合对主节点故障判定达成一致时,sentinel领导者节点会做故障转移操作,选出新的主节点,原来的从节点会向新的主节点发起复制操作,parallel-syncs就是用来限制在一次故障转移之后,每次向新的主节点发起复制操作的从节点个数。如果这个参数配置的比较大,那么多个从节点会向新的主节点同时发起复制操作,尽管复制操作通常不会阻塞主节点,但是同时会向新的主节点发起复制,必然会对主节点所在的机器造成一定的网络和磁盘IO开销。
配置如下:
sentinel failover-timeout
failover-time通常被解释成故障转移超时时间,但实际上它作用于故障转移各个阶段:
failover-timeout的作用具体体现在四个方面:
配置如下:
sentinel auth-pass
如果Sentinel监控的主节点配置了密码,sentinel auth-pass配置通过添加主节点的密码,防止Sentinel节点对主节点无法监控。
配置如下:
sentinel notification-script
sentinel set
参数 | 使用方法 |
---|---|
quorum | sentinel set mymaster quorum 2 |
down-after-milliseconds | sentinel set mymaster down-afer-milliseconds 30000 |
failover-time | sentinel set mymaster failover-time 360000 |
parallel-syncs | sentinel set mymaster parallel-syncs 2 |
有几点需要注意一下:
1)sentinel set命令只对当前Sentinel节点有效。
2)sentinel set命令如果执行成功会立即刷新配置文件,这点和Redis普通数据节点设置配置需要执行config rewrite刷新到配置文件不同。
3)建议所有Sentinel节点的配置尽可能一致,这样在故障发现和转移时比较容易达成一致。
4)Sentinel对外不支持config命令。
1)Sentinel节点不应该部署在一台物理“机器”上。
2)部署至少三个且奇数个的Sentinel节点。
展示所有被监控的主节点状态以及相关的统计信息
展示指定的Sentinel节点集合(不包含当前Sentinel节点)
返回指定主节点的IP地址和端口
当前Sentinel节点对符合(通配符风格)主节点的配置进行重置,包含清除主节点的相关状态(例如故障转移),重新发现从节点和Sentinel节点。
对指定主节点进行强制故障转移(没有和其他Sentinel节点“协商”),当故障转移完成后,其他sentinel节点按照故障转移的结果更新自身配置,这个命令在Redis Sentinel的日常运维中非常有用
取消当前Sentinel节点对于指定主节点的监控。
这个命令和配置文件中的含义是完全一样的,只不过是通过命令的形式来完成Sentinel节点对主节点的监控。
Sentinel节点集合具备了监控、通知、自动故障转移、配置提供者若干功能,也就是说实际上最了解主节点信息的就是Sentinel节点集合,而各个主节点可以通过进行标识的,所以,无论是哪种编程语言的客户端,如果需要正确地连接Redis Sentinel,必须有Sentinel节点集合和masterName两个参数
实现一个Redis Sentinel客户端的基本步骤如下:
sentinel get-master-addr-by-name master-name
这个API来获取对应主节点的相关信息从上面的模型可以看出,Redis Sentinel客户端只有在初始化和切换主节点时需要和Sentinel节点集合进行交互来获取主节点信息
Jedis能够很好地支持Redis Sentinel,并且使用Jedis连接Redis Sentinel也很简单,按照Redis Sentinel的原理,需要有masterName和Sentinel节点集合两个参数
public JedisSentinelPool(String masterName, Set<String> sentinels,
final GenericObjectPoolConfig poolConfig, final int connectionTimeout,
final int soTimeout,
final String password, final int database,
final String clientName)
具体参数含义如下:
·masterName——主节点名。
·sentinels——Sentinel节点集合。
·poolConfig——common-pool连接池配置。
·connectTimeout——连接超时。
·soTimeout——读写超时。
·password——主节点密码。
·database——当前数据库索引。
·clientName——客户端名。
private HostAndPort initSentinels(Set<String> sentinels, final String masterName) {
// 主节点
HostAndPort master = null;
// 遍历所有sentinel节点
for (String sentinel : sentinels) {
// 连接sentinel节点
HostAndPort hap = toHostAndPort(Arrays.asList(sentinel.split(":")));
Jedis jedis = new Jedis(hap.getHost(), hap.getPort());
// 使用sentinel get-master-addr-by-name masterName获取主节点信息
List<String> masterAddr = jedis.sentinelGetMasterAddrByName(masterName);
// 命令返回列表为空或者长度不为2, 继续从下一个sentinel节点查询
if (masterAddr == null || masterAddr.size() != 2) {
continue;
}
// 解析masterAddr获取主节点信息
master = toHostAndPort(masterAddr);
// 找到后直接跳出for循环
break;
}
if (master == null) {
// 直接抛出异常,
throw new Exception();
}
// 为每个sentinel节点开启主节点switch的监控线程
for (String sentinel : sentinels) {
final HostAndPort hap = toHostAndPort(Arrays.asList(sentinel.split(":")));
MasterListener masterListener = new MasterListener(masterName, hap.getHost(),
hap.getPort());
masterListener.start();
}
// 返回结果
return master;
}
具体过程如下:
1)遍历Sentinel节点集合,找到一个可用的Sentinel节点,如果找不到就从Sentinel节点集合中去找下一个,如果都找不到直接抛出异常给客户端:
new JedisException("Can connect to sentinel, but " + masterName + " seems to be not monitored...")
2)找到一个可用的Sentinel节点,执行sentinelGetMasterAddrByName(masterName),找到对应主节点信息:
List<String> masterAddr = jedis.sentinelGetMasterAddrByName(masterName);
3)JedisSentinelPool中没有发现对主节点角色验证的代码,这是因为get-master-addr-by-name master-name这个API本身就会自动获取真正的主节点(例如故障转移期间)。
4)为每一个Sentinel节点单独启动一个线程,利用Redis的发布订阅功能,每个线程订阅Sentinel节点上切换master的相关频道 +switch-master
。
for (String sentinel : sentinels) {
final HostAndPort hap = toHostAndPort(Arrays.asList(sentinel.split(":")));
MasterListener masterListener = new MasterListener(masterName, hap.
getHost(), hap.getPort());
masterListener.start();
}
下面代码就是MasterListener的核心监听代码,代码中比较重要的部分就是订阅Sentinel节点的+switch-master频道,它就是Redis Sentinel在结束对主节点故障转移后会发布切换主节点的消息,Sentinel节点基本将故障转移的各个阶段发生的行为都通过这种发布订阅的形式对外提供,开发者只需订阅感兴趣的频道即可
Jedis sentinelJedis = new Jedis(sentinelHost, sentinelPort);
// 客户端订阅Sentinel节点上"+switch-master"(切换主节点)频道
sentinelJedis.subscribe(new JedisPubSub() {
@Override
public void onMessage(String channel, String message) {
String[] switchMasterMsg = message.split(" ");
if (switchMasterMsg.length > 3) {
// 判断是否为当前masterName
if (masterName.equals(switchMasterMsg[0])) {
// 发现当前masterName发生switch, 使用initPool重新初始化连接池
initPool(toHostAndPort(switchMasterMsg[3], switchMasterMsg[4]));
}
}
}
}, "+switch-master");
一套合理的监控机制是Sentinel节点判定节点不可达的重要保证,Redis Sentinel通过三个定时监控任务完成对各个节点发现和监控:
__sentinel__:hello
频道上发送该Sentinel节点对于主节点的判断以及当前Sentinel节点的信息,同时每个Sentinel节点也会订阅该频道,来了解其他Sentinel节点以及它们对主节点的判断,所以这个定时任务可以完成以下两个工作:
sentinel is-master-down-by-addr
命令向其他Sentinel节点询问对主节点的判断,当超过个数,Sentinel节点认为主节点确实有问题,这时该Sentinel节点会做客观下线的决定,这样客观下线的含义就比较明显了,也就是大部分Sentinel节点都会对主节点的下线做了同意判定,那么这个判定就是客观的。注意
从节点、Sentinel节点在主观下线后,没有后续的故障转移操作。
这里有必要对sentinel is-master-down-by-addr命令做一个介绍,它的使用方法如下:
sentinel is-master-down-by-addr
sentinel is-master-down-by-addr 127.0.0.1 6379 0 *
返回结果包含三个参数,如下所示:
假如Sentinel节点对于主节点已经做了客观下线,那么是不是就可以立即进行故障转移了?当然不是,实际上故障转移的工作只需要一个Sentinel节点来完成即可,所以Sentinel节点之间会做一个领导者选举的工作,选出一个Sentinel节点作为领导者进行故障转移的工作。Redis使用了Raft算法实现领导者选举,因为Raft算法相对比较抽象和复杂,以及篇幅所限,所以这里给出一个Redis Sentinel进行领导者选举的大致思路:
1)每个在线的Sentinel节点都有资格成为领导者,当它确认主节点主观下线时候,会向其他Sentinel节点发送sentinel is-master-down-by-addr
命令,要求将自己设置为领导者。
2)收到命令的Sentinel节点,如果没有同意过其他Sentinel节点的sentinel is-master-down-by-addr命令,将同意该请求,否则拒绝。
3)如果该Sentinel节点发现自己的票数已经大于等于max(quorum,num(sentinels)/2+1)
,那么它将成为领导者。
4)如果此过程没有选举出领导者,将进入下一次选举。
down-after-milliseconds * 10
秒slaveof no one
命令让其成为主节点Redis Sentinel在故障转移一些重要的事件消息对应的频道(Redis pub-sub发布订阅模式):