Sentinel——主从复制高可用方案
在使用主从复制时,我们面临以下问题:
-
手动故障转移
master宕机
在发生上图中的master宕机之后,我们必须手动替换主节点,就像下图:
master宕机处理 -
写能力和存储能力受限(采用读写分离时)
因此,Redis为我们提供了Sentinel架构:
Sentinel.png
Sentinel是一个管理redis实例的工具,它可以实现对redis的监控、通知、自动故障转移。sentinel不断地检测redis实例是否可以正常工作,通过API向其他程序报告redis的状态,如果redis master不能工作,则会自动启动故障转移进程,将其中的一个slave提升为master,其他的slave重新设置新的master服务器。
- Sentinel作用:
- Master状态检测
- 如果Master异常,则会进行Master-Slave切换,将其中一个Slave作为Master,将之前的Master作为Slave
- Master-Slave切换后,master_redis.conf、slave_redis.conf和sentinel.conf的内容都会发生改变,即master_redis.conf中会多一行slaveof的配置,sentinel.conf的监控目标会随之调换
- sentinel实现原理
-
sentinel创建两个连接master的 异步网络连接
- 命令连接:向master发送命令,并接受回复(默认十秒一次通过INFO来获取master信息)
- 订阅连接:订阅master的sentinel:hello频道(依赖于Redis的订阅发布,因为我们以前说过Redis是不能保存过期发布消息的,所以,slave节点可能错过master的消息,所以需要sentinel来接受这些信息提供给slave,同时其他sentinel也可以获取最新信息)
-
sentinel创建两个连接slave的 异步网络连接(当发现新的slave出现时)
- 命令连接:向slave发送命令,并接受回复(默认十秒一次通过INFO来获取slave信息)
- 订阅连接:订阅slave的频道
-
创建连向其他sentinel的命令连接(用于信息交换:主观/客观下线检测)
- sentinels字典:为master创建,用于保存自身以及其他监视该master的sentinel节点信息
不需要订阅连接的原因:sentinel订阅了master和slave的频道,会接受到新sentinel的信息
- sentinels字典:为master创建,用于保存自身以及其他监视该master的sentinel节点信息
-
下线检测:
- sentinel默认每秒一次向建立了命令连接的所有节点(包括sentinel)发送PING命令,用于检测是否在线
- 主观下线:当master超过down-after-milliseconds设置的时间仍然返回无效回复时,这时候sentinel将master标记为主观下线
- 客观下线:在标记为主观下线后,改sentinel会想其他监视该master的节点通过命令连接进行询问,当赞同的数量达到我们设置的数量时,认为该master客观下线。
上面的总结一下可以认为是sentinel的三个定时任务:
- 每10秒每个sentinel对master和slave执行INFO(故障转移时改为1秒)
- 发现slave节点
- 确认主从关系
- 每2秒每个sentinel通过节点的channel向所有主从节点发送命令
- 交换对master节点的看法和自身信息(更新sentinels字典以及master节点实例结构)
- 每1秒每个sentinel对其他的sentinel和节点执行ping(心跳检测)
- 下线检测(主观下线判断)
-
- 故障转移
在判断master客观下线后,选举出的sentinel将进行故障转移- 多个sentinel发现并确认master有问题
- 选举出一个sentinel作为领导
- 选举出一个slave作为master
- 通知其余slave成为新的master的slave
- 通知客户端主从变化
- 等待老的master复活成为新master的slave
- 领导者选举
- 每个做主观下线的sentinel节点向其他sentinel节点发送sentinel is-master-down-by-addr 命令,要求成为领导者
- 收到命令的sentinel节点如果没有同意过其他sentinel节点的请求,将同意该请求,否则拒绝
- 当票数超过sentinel节点半数且超过quorum,将成为领导者
- 若此过程有多个sentinel节点成为领导者,将等待一会重新选举
- sentinel备份策略
可以参考以前的文章《Redis——持久化》 - 部署sentinel主从配置
我们这里是采用了三台物理机,一共五个节点,一主四从,三个sentinel节点。- 主节点主要配置
#开放外网访问
bind 0.0.0.0
#下面我们设置了密码,所以开启安全模式
protected-mode yes
#开放端口
port 6379
#设置为守护线程
daemonize yes
#pid文件
pidfile "/var/run/redis_6379.pid"
#日志文件
logfile "6379.log"
#rdb文件
dbfilename "dump-6379.rdb"
#文件路径
dir "/var/redis/6379"
#主节点访问密码(主节点也要设置是因为故障切换后主节点有可能变为从节点)
masterauth "password"
requirepass "password"
- 从节点配置
#开放外网访问
bind 0.0.0.0
#下面我们设置了密码,所以开启安全模式
protected-mode yes
#开放端口
port 6380
#设置为守护线程
daemonize yes
#pid文件
pidfile "/var/run/redis_6380.pid"
#日志文件
logfile "6379.log"
#rdb文件
dbfilename "dump-6380.rdb"
#文件路径
dir "/var/redis/6380"
#主节点访问密码
masterauth "password"
#该节点密码
requirepass "password"
#设置主节点 我用的公网ip
slaveof 120.1.1.1 6379
#设置从节点只读
slave-read-only yes
其他从节点类似
- 哨兵节点配置
bind 0.0.0.0
port 26379
#要监控的主节点名称、ip、port ,2表示将这个主服务器判断为失效至少需要 2 个 Sentinel 同意
sentinel monitor mymaster 120.1.11.11 6379 2
# Sentinel 认为服务器已经断线所需的毫秒数。
sentinel down-after-milliseconds mymaster 60000
# failover过期时间,当failover(选出新的master)开始后,在此时间内仍然没有触发任何failover操作,当前sentinel 将会认为此次failoer失败
sentinel failover-timeout mymaster 180000
#指定了在执行故障转移时, 最多可以有多少个从服务器同时对新的主服务器进行同步, 这个数字越小, 完成故障转移所需的时间就越长。
sentinel parallel-syncs mymaster 1
其他哨兵节点类似,只是端口不同。
- 哨兵模式测试
编写一个工具类,用来获取jedis实例
public class RedisUtil {
/*redis-sentinel节点地址*/
private static final String SENTINEL_1 = "ip:26379";
private static final String SENTINEL_2 = "ip1.31:26380";
private static final String SENTINEL_3 = "ip:26381";
//sentinel节点地址集合
private static final Set<String> SENTINELS = new HashSet<String>() {
{
add(SENTINEL_1);
add(SENTINEL_2);
add(SENTINEL_3);
}
};
//masterName
private static final String MASTER_NAME = "mymaster";
// 访问密码
private static String AUTH = "password";
private static String HOST = "ip";
/**
* 可用连接实例的最大数目,默认值为8; 如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。
*/
private static int MAX_ACTIVE = 1024;
// 控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8。
private static int MAX_IDLE = 200;
// 等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException;
private static int MAX_WAIT = 10000;
private static int TIMEOUT = 10000;
// 在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的;
private static boolean TEST_ON_BORROW = true;
/*sentinelPool*/
private static JedisSentinelPool SENTINEL_POLL = null;
/**
* 初始化Redis-Sentinel连接池.
*/
static {
try {
// maxActive ==> maxTotal
// maxWait ==> maxWaitMillis
JedisPoolConfig CONFIG = new JedisPoolConfig();
CONFIG.setMaxTotal(MAX_ACTIVE);
CONFIG.setMaxIdle(MAX_IDLE);
CONFIG.setMaxWaitMillis(MAX_WAIT);
CONFIG.setTestOnBorrow(TEST_ON_BORROW);
SENTINEL_POLL = new JedisSentinelPool(MASTER_NAME, SENTINELS, AUTH);
System.out.println(SENTINEL_POLL.getCurrentHostMaster());
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取Jedis实例.
*/
public static synchronized Jedis getJedis() {
Jedis jedis = null;
int count = 0;
do {
try {
jedis = SENTINEL_POLL.getResource();
} catch (Exception e) {
e.printStackTrace();
}
count++;
} while (jedis == null && count < 10);
return jedis;
}
/**
* 释放jedis资源
*/
public static void returnResource(final Jedis jedis) {
if (jedis != null) {
jedis.close();
}
}
}
测试客户端
public class RedisClient {
public static void main(String[] args) {
Jedis jedis = RedisUtil.getJedis();
jedis.set("4", "1");
jedis.set("5", "2");
jedis.set("6", "3");
System.out.println(jedis.get("4"));
System.out.println(jedis.get("5"));
System.out.println(jedis.get("6"));
RedisUtil.returnResource(jedis);
}
}
测试结果
可以看到,可以成功使用了,我们再来测试一下故障转移是否可用
我们手动关闭主节点,可以看到主节点已经关闭
图片.png
再来运行一下程序, 故障转移
发现主节点已经自动切换为其他的节点,最后我们再启动刚才关闭的主节点,发现他已经成为新主节点的从节点
图片.png
网友评论