Redis主从模式下,主节点由于故障不能提供服务,需要人工将从节点晋升为主节点,同时还要通知应用方更新主节点地址,这样非常不利于应用。Redis的Redis Sentinel(哨兵)架构来解决该问题
基本概念
- 主节点:Redis主服务/数据库 一个独立的Redis进程
- 从节点:Redis从服务/数据库 一个独立的Resid进程
- Redis数据节点:主节点和从节点 主节点和从节点的进程
- Sentinel节点:监控Redis数据节点 一个独立的Sentinel进程
- Sentinel节点集合:若干Sentinel节点的抽象组合 若干Sentinel节点进程
- Redis Sentinel:Redis高可用实现方案 Sentinel节点集合和Redis数据节点进程
- 应用方:泛指一个或多个客户端 一个或者多个客户端进程或者线程
Redis Sentinel是Redis的高可用实现方案,在实际的生产环境中,对提高整个系统的高可用性是非常有帮助的
主从复制的问题
主从复制的好处作用
主从复制可以将主节点数据同步到从节点
- 作为主节点的一个备份,主节点出现故障从节点可以作为后备,保证数据尽量不丢失
- 从节点可以扩展主节点的读能力,读写分离
主从复制的问题
- 主节点出现故障需要手动将一个从节点晋升为主节点,整个过程需要人工干预 (高可用问题)
- 主节点的写能力收到单机的限制 (分布式问题)
- 主节点的储存能力受到单机的限制 (分布式问题)
高可用
Redis主从复制模式下,一个主节点出现了故障不可达需要人工干预进行故障转移,如果无法及时感知到主节点的变化,必然会造成写数据丢失和
一定的读数据错误,应用方服务器不可用。
-
Redis主从故障转移步骤
1.主节点发生故障,客户端连接失败,从节点与主节点连接失败,复制中断
2.如果主节点无法重启,则需选择一个从节点执行slaveof no one晋升为新的主节点
3.更新应用方的主节点信息,重新启动应用方
4.将另一个从节点更新现在的主节点信息,复制新的主节点
6.待原来的主节点恢复后,让他去复制新的主节点 -
问题
1.判断主节点故障的机制标准
2.多个从节点如何保证只有一个被晋升为主节点
3.通知客户端新的主节点机制是否足够健壮
Redis Sentinel的高可用性
Redis Sentinel与Redis主从复制模式只是多个若干Sentinel节点,并没有针对Redis节点做特殊处理
当主节点出现故障时,Redis Sentinel能自动完成故障发现和故障转移,并通知应用方,从而实现真正的高可用
- 哨兵模式:
1.Redis Sentinel是一个分布式架构(指的是Redis数据节点、Sentinel节点集合、客户端分布在多个物理节点),包含若干个Sentinel节点和Redis数据节点
2.每个Sentinel节点回对数据节点和其余Sentinel节点进行监控,当其发现节点不可达时,回对节点做下线标识
3.如果下线的为主节点,Sentinel会和其他Sentinel节点“协商”,选举出一个Sentinel节点来完成故障自动转移的工作
4.将变化实时通知Redis应用方
Redis Sentinel具有的功能
- 具有的功能:
1.监控:定期检测Redis数据节点、其余Sentinel节点是否可达
2.通知:将故障转移的结果通知给应用发
3.主节点故障转移:实现从节点晋升为主节点并维护后续正确的主从关系
4.配置提供者:在Redis Sentinel结构中,客户端在初始化的时候连接的是Sentinel节点集合,从中获取主节点信息
多个Sentinel节点好处
- Redis Sentinel包含若干个Sentinel节点好处
1.故障判断是由多个Sentinel节点共同完成,可以有效地防止误判
2.个别Sentinel节点不可用,整个Sentinel节点集合依然是健壮的
安装和部署Redis Sentinel
部署拓扑结构
部署Redis数据节点
在Redis Sentinel中Redis数据节点没有做任何特殊配置
启动主节点
启用另外的配置
启动两个从节点
配置如下
确认主从关系
部署Sentinel节点
Sentinel配置信息
配置信息如下:
上图少写了个s,导致报错了。。。
可以看出redis提示也非常人性化,改过之后如下
- 配置分别为:
1.Sentinel节点端口,默认为26379
2.monitor mymaster 需要监控的主节点,2为失败后必须要2个Sentinel节点同意
启动Sentinel
redis-sentinel [配置文件] [--sentinel 参数]
确认
- 至此Redis Sentinel已经搭建好了,建议:
1.Redis Sentinel的所有节点部署在不同的物理机上
2.Redis Sentinel中的数据节点和普通的Redis数据节点在配置上没有任何区别,只是添加了Sentinel节点对其进行监控
测试
- 如上图所示:
1.keys *所有都为空
2.对主节点set Redis Sentinel
3.两个从节点都有设置的信息
4.强制杀死主节点,一个从节点晋升为主节点
5.查看剩余节点信息get Redis都有数据
6.重启被杀死的进程,查看角色为从节点
7.且get Redis的信息也都存在
配置优化
默认配置文件为/etc/redis-sentinel.conf
启动后的config文件会被改变重写,如下
port 26380
logfile "26380.log"
dir "/usr/etc/Redis/data"
sentinel myid 1a72ed12b****************
sentinel monitor mymaster 127.0.0.1 6381 2
sentinel config-epoch mymaster 1
sentinel leader-epoch mymaster 1
# Generated by CONFIG REWRITE
sentinel known-slave mymaster 127.0.0.1 6379
sentinel known-slave mymaster 127.0.0.1 6380
sentinel known-sentinel mymaster 127.0.0.1 26381 17e01eda629c****************
sentinel known-sentinel mymaster 127.0.0.1 26379 cc4da90f**********************
sentinel current-epoch 1
- 配置说明:
1.port与dir:端口和工作目录
2.sentinel monitor mymaster 127.0.0.1 6381 2:Sentinel节点定期监控的主节点ip和端口,和判定主节点不可用需要达到的票数虽然未体现从节点及其他Sentinel节点配置信息,但会通过主节点获得有关节点和Sentinel节点信息后进行监控
3.所有节点启动后,获得到的节点会在config中重写,config文件会改变
4.去除默认配置
5.添加配置纪元相关参数
监控多个主节点
Redis Sentinel可以同时监控多个主节点,只需要制定多个masterName来区分不同的主节点即可
调整配置
Sentinel节点和普通的数据节点一样支持动态设置参数,且并不支持所有参数
部署技巧
Sentinel节点不应该部署在同一台物理“机器”上
如果出现硬件故障,所有的虚拟机都会受到影响,为了实现Sentinel节点集合真正的高可用,不应该部署在同一台物理机器上
部署至少3个且奇数个的Sentinel节点
领导者选举需要至少一半+1个节点,奇数点可以在满足的条件上节省一个节点
只有一套Sentinel还是每个主节点配置一套Sentinel
-
一套Sentinel:
优点:降低维护成本,秩序维护固定个数的Sentinel节点,集中对多个Redis数据节点进行管理就可以
缺点:Sentinel节点集合出现异常时,会对多个Redis数据节点造成影响,如果监控的Redis数据较多会造成Sentinel节点产生过多的网络连接 -
多套Sentinel:
优缺点与上述相反
会造成资源浪费。但每套Redis Sentinel彼此隔离,数据保护较安全
使用建议:如果监控的是同一个业务的多个主节点集合,可以使用一套Sentinel。否则应该使用多套Sentinel
API
Sentinel节点是一个特殊的Redis节点,有自己专门的API
sentinel masters
展示所有被检控的主节点状态以及相关的的信息统计
sentinel master [master_name]
展示指定[master name]的主节点状态及相关的统计信息
sentinel slaves [master name]
展示指定mastr name的从节点状态以及相关的统计信息
sentinel sentinels [master name]
展示指定master name的Sentinel节点集合(不包含当前Sentinel节点)
...
客户端连接
如果主节点挂掉了,虽然Redis Sentinel可以完成故障转移,但是客户端无法获取这个变化,那么使用Redis Sentinel的意义就不大了,所以各个语言的客户端都需要对Redis Sentinel进行显示的支持
Redis Sentinel的客户端
最了解主节点信息的就是Sentinel节点集合,通过masterName标识各个主节点。所以无论哪种编程语言的客户端,如果需要正确地连接Redis Sentinel,必须有Sentinel节点集合和masterName两个参数
Redis Sentinel客户端基本实现原理
- 基本步骤:
1.遍历Sentinel节点集合获取一个可用的Sentinel节点
2.通过Sentinel get-master-addr-by-name这个API获取对应主节点相关信息
3.info replication验证当前获取的“主节点”是否为真正的主节点(防止故障转移期间主节点的变化)
4.保持和Sentinel节点集合的“联系”,时刻获取关于主节点相关“信息”
Java操作Redis Sentinel
Jedis的连接池JedisPool,为了不与之相混淆,Jdeis针对Redis Sentinel给出了JedisSentinelPool,这个连接池保存的连接还是针对主节点的。
Jedis的源码
Jedis的JedisSentinelPool构造方法如下
public class JedisSentinelPool extends JedisPoolAbstract {
protected GenericObjectPoolConfig poolConfig;
protected int connectionTimeout;
protected int soTimeout;
protected String password;
protected int database;
protected String clientName;
protected int sentinelConnectionTimeout;
protected int sentinelSoTimeout;
protected String sentinelPassword;
protected String sentinelClientName;
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,
final int sentinelConnectionTimeout, final int sentinelSoTimeout, final String sentinelPassword,
final String sentinelClientName) {
this.poolConfig = poolConfig;
this.connectionTimeout = connectionTimeout;
this.soTimeout = soTimeout;
this.password = password;
this.database = database;
this.clientName = clientName;
this.sentinelConnectionTimeout = sentinelConnectionTimeout;
this.sentinelSoTimeout = sentinelSoTimeout;
this.sentinelPassword = sentinelPassword;
this.sentinelClientName = sentinelClientName;
//初始化
HostAndPort master = initSentinels(sentinels, masterName);
//初始化连接池
initPool(master);
}
- 上述参数含义如下:
masterName:主节点名字
sentinels:Sentinel节点集合
poolConfig:common-pool连接池配置
connectTimeout:连接超时
soTimeout:读写超时
password:主节点密码
database:当前数据库索引
clientName:客户端名
其中最主要的是初始化Sentinels和Pool
private HostAndPort initSentinels(Set<String> sentinels, final String masterName) {
//主节点
HostAndPort master = null;
boolean sentinelAvailable = false;
log.info("Trying to find master from available Sentinels...");
//开始遍历所有Sentinel节点
for (String sentinel : sentinels) {
//连接Sentinel 节点
final HostAndPort hap = HostAndPort.parseString(sentinel);
log.debug("Connecting to Sentinel {}", hap);
Jedis jedis = null;
try {
jedis = new Jedis(hap.getHost(), hap.getPort(), sentinelConnectionTimeout, sentinelSoTimeout);
if (sentinelPassword != null) {
jedis.auth(sentinelPassword);
}
if (sentinelClientName != null) {
jedis.clientSetname(sentinelClientName);
}
//获取主节点信息
List<String> masterAddr = jedis.sentinelGetMasterAddrByName(masterName);
// connected to sentinel...
sentinelAvailable = true;
if (masterAddr == null || masterAddr.size() != 2) {
log.warn("Can not get master addr, master name: {}. Sentinel: {}", masterName, hap);
continue;
}
//解析masterAddr获取主节点信息
master = toHostAndPort(masterAddr);
log.debug("Found Redis master at {}", master);
//找到后跳出循环
break;
} catch (JedisException e) {
// resolves #1036, it should handle JedisException there's another chance
// of raising JedisDataException
log.warn(
"Cannot get master address from sentinel running @ {}. Reason: {}. Trying next one.", hap,
e.toString());
} finally {
if (jedis != null) {
jedis.close();
}
}
}
if (master == null) {
if (sentinelAvailable) {
// can connect to sentinel, but master name seems to not
// monitored
throw new JedisException("Can connect to sentinel, but " + masterName
+ " seems to be not monitored...");
} else {
throw new JedisConnectionException("All sentinels down, cannot determine where is "
+ masterName + " master is running...");
}
}
log.info("Redis master running at " + master + ", starting Sentinel listeners...");
//为每个Senrinel节点开启主节点switch的监控线程
for (String sentinel : sentinels) {
final HostAndPort hap = HostAndPort.parseString(sentinel);
MasterListener masterListener = new MasterListener(masterName, hap.getHost(), hap.getPort());
// whether MasterListener threads are alive or not, process can be stopped
masterListener.setDaemon(true);
masterListeners.add(masterListener);
masterListener.start();
}
return master;
}
private void initPool(HostAndPort master) {
synchronized(initPoolLock){
if (!master.equals(currentHostMaster)) {
currentHostMaster = master;
if (factory == null) {
factory = new JedisFactory(master.getHost(), master.getPort(), connectionTimeout,
soTimeout, password, database, clientName);
initPool(poolConfig, factory);
} else {
factory.setHostAndPort(currentHostMaster);
// although we clear the pool, we still have to check the
// returned object
// in getResource, this call only clears idle instances, not
// borrowed instances
internalPool.clear();
}
log.info("Created JedisPool to master at " + master);
}
}
}
- 具体过程如下:
1.遍历Sentinel节点集合,找到一个可用的Sentinel节点,如果遍历后都找不到就抛出异常
2.找到一个可用的Sentinel节点,执行 ListmasterAddr = jedis.sentinelGetMasterAddrByName(masterName);找到主节点信息
3.JedisSentinelPool没有对主节点角色进行验证的代码,是因为getmasteraddbyname的API本身就会自动获取真正的主节点
4.为每个Sentinel节点单独启动一个线程,利用Redis的发布订阅功能,每个线程订阅Sentinel节点上切换master的相关频道
Redis Sentinel的实现原理
三个定时监控任务
Redis Sentinel通过三个定时监控任务完成对各个节点发现和监控
每隔10S info命令
每隔10S,每个Sentinel节点回想主节点和从节点发送info命令获取最新的结构
- 通过向主节点执行info命令,获取节点信息,故不需要显式配置监控从节点
- 当有新的从节点加入时都可以立刻感知
- 节点不可达或者故障转移后,可以通过info命令实时更新节点信息
每隔2S hello
每隔2S,每个Sentinel节点会向Redis数据节点的hello频道上发送该Sentinel节点对于主节点的判断以及当前Sentinel节点的信息
- 发现新的Sentinel节点
- Sentinel节点之间交换主节点的状态,作为后面客观下线以及领导者选举的依据
每隔1S 心跳任务ping
每隔Sentinel节点会向主节点、从节点、其余Sentinel发送一条ping命令做一次心跳检测,来确认这些节点当前是否可达
主观下线和客观下线
主观下线
每个Sentinel节点心跳ping发送后超过预定时间没有收到有效回复,则Sentinel节点会对该节点做失败判定,存在误判的可能
客观下线
当SSentinel主观下线的节点是主节点时,该Sentinel节点会向其他Sentinel节点询问对主节点的判断,当超过
领导者Sentinel节点选举
故障转移的工作只需要一个Sentinel节点来完成即可,所以Sentinel节点之间会做一个领导者选举工作,选出一个Sentinel节点作为领导者进行故障转移的工作(Redis使用Raft算法实现)
- 选举的过程:
1.每个在线的Sentinel节点都有资格称为领导者,当其确认客观下线主节点是,回想其他Sentinel节点发送命令,请求自己设置为领导者
2.收到命令的Sentinel节点如果没有同意过其他节点,将会同意该次请求,即Sentinel节点同意第一次收到请求设置为leader的请求,拒绝后面的
3.如果该Sentinel节点发现自己的票数大于等于max(quorum,num(sentinels)/2+1),那么其将成为领导者
4.如果此过程没有选举出领导者,将进入下一次选举
其实选举的过程非常快,基本上谁先完成客观下线,谁就是领导者
故障转移
选举出的领导者leader负责故障转移,步骤如下
在从节点列表中选出一个节点作为新的主节点
- 过滤:“不健康”(主观下线、断线)、5s内没有回复过Sentinel节点ping响应
- 选择:选择slave-priority(从节点优先级)最高的从节点列表,如果存在则返回,不存在继续
- 选择复制偏移量最大(复制的最完整)的从节点,如果存在则返回,不存在继续
- 选择runid最小的从节点
从节点称为主节点
Sentinel领导者节点会对第一步选出来的从节点执行slave on one,从节点称为主节点
其他从节点转移新主节点
Sentinel领导者节点回想剩余的从节点发送命令,让他们称为新主节点的从节点
Sentinel节点更新
Sentinel节点集合会将原来的主节点更新为从节点,保持对其关注,当其恢复后命令其复制新的主节点
Comments | 0 条评论