目录
本博文主要总结关于哨兵的一些理论知识,主要关注点有一下几个方面:
一、哨兵解决了什么问题?
二、哨兵是如何解决“问题一”的?
三、如何使用哨兵?
四、Redis Sentinel客户端实现的原理是什么?Java如何操作Redis Sentinel?
四、Redis Sentinel客户端实现的原理是什么?Java如何操作Redis Sentinel?
首先,看JedisSentinelPool的构造函数。
Jedis Version:2.9.0
sentinel构造函数:
/**
*Set<String> sentinels:哨兵集合,格式为IP:PORT
*/
public JedisSentinelPool(String masterName, Set<String> sentinels, GenericObjectPoolConfig poolConfig, int timeout, String password) {
this(masterName, sentinels, poolConfig, timeout, password, 0);
}
public JedisSentinelPool(String masterName, Set<String> sentinels, GenericObjectPoolConfig poolConfig, int connectionTimeout, int soTimeout, String password, int database, String clientName) {
this.connectionTimeout = 2000;
this.soTimeout = 2000;
this.database = 0;
this.masterListeners = new HashSet();
this.log = Logger.getLogger(this.getClass().getName());
this.poolConfig = poolConfig;
this.connectionTimeout = connectionTimeout;
this.soTimeout = soTimeout;
this.password = password;
this.database = database;
this.clientName = clientName;
HostAndPort master = this.initSentinels(sentinels, masterName);
this.initPool(master);
}
再看,如何初始化哨兵的,initSentinels(sentinels, masterName)。
private HostAndPort initSentinels(Set<String> sentinels, String masterName) {
HostAndPort master = null;
boolean sentinelAvailable = false;
this.log.info("Trying to find master from available Sentinels...");
Iterator var5 = sentinels.iterator();
String sentinel;
HostAndPort hap;
while(var5.hasNext()) {
sentinel = (String)var5.next();
hap = HostAndPort.parseString(sentinel);
this.log.fine("Connecting to Sentinel " + hap);
Jedis jedis = null;
try {
jedis = new Jedis(hap.getHost(), hap.getPort());
List<String> masterAddr = jedis.sentinelGetMasterAddrByName(masterName);
sentinelAvailable = true;
if (masterAddr != null && masterAddr.size() == 2) {
master = this.toHostAndPort(masterAddr);
this.log.fine("Found Redis master at " + master);
break;
}
this.log.warning("Can not get master addr, master name: " + masterName + ". Sentinel: " + hap + ".");
} catch (JedisException var13) {
this.log.warning("Cannot get master address from sentinel running @ " + hap + ". Reason: " + var13 + ". Trying next one.");
} finally {
if (jedis != null) {
jedis.close();
}
}
}
if (master == null) {
if (sentinelAvailable) {
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...");
}
} else {
this.log.info("Redis master running at " + master + ", starting Sentinel listeners...");
var5 = sentinels.iterator();
while(var5.hasNext()) {
sentinel = (String)var5.next();
// 获取哨兵的IP和PORT
hap = HostAndPort.parseString(sentinel);
// 创建一个线程创建监听器,对每个哨兵进行监听
JedisSentinelPool.MasterListener masterListener = new JedisSentinelPool.MasterListener(masterName, hap.getHost(), hap.getPort());
// 设置为守护线程
masterListener.setDaemon(true);
this.masterListeners.add(masterListener);
masterListener.start();
}
return master;
}
}
private HostAndPort initSentinels(Set<String> sentinels, String masterName) {
HostAndPort master = null;
boolean sentinelAvailable = false;
this.log.info("Trying to find master from available Sentinels...");
Iterator var5 = sentinels.iterator();
String sentinel;
HostAndPort hap;
while(var5.hasNext()) {
sentinel = (String)var5.next();
hap = HostAndPort.parseString(sentinel);
this.log.fine("Connecting to Sentinel " + hap);
Jedis jedis = null;
try {
jedis = new Jedis(hap.getHost(), hap.getPort());
List<String> masterAddr = jedis.sentinelGetMasterAddrByName(masterName);
sentinelAvailable = true;
if (masterAddr != null && masterAddr.size() == 2) {
master = this.toHostAndPort(masterAddr);
this.log.fine("Found Redis master at " + master);
break;
}
this.log.warning("Can not get master addr, master name: " + masterName + ". Sentinel: " + hap + ".");
} catch (JedisException var13) {
this.log.warning("Cannot get master address from sentinel running @ " + hap + ". Reason: " + var13 + ". Trying next one.");
} finally {
if (jedis != null) {
jedis.close();
}
}
}
if (master == null) {
if (sentinelAvailable) {
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...");
}
} else {
this.log.info("Redis master running at " + master + ", starting Sentinel listeners...");
var5 = sentinels.iterator();
while(var5.hasNext()) {
sentinel = (String)var5.next();
// 获取哨兵的IP和PORT
hap = HostAndPort.parseString(sentinel);
// 创建一个线程创建监听器,对每个哨兵进行监听
JedisSentinelPool.MasterListener masterListener = new JedisSentinelPool.MasterListener(masterName, hap.getHost(), hap.getPort());
// 设置为守护线程
masterListener.setDaemon(true);
this.masterListeners.add(masterListener);
masterListener.start();
}
return master;
}
}
对master监听的监听器的源码,创建一个线程监听master。
protected class MasterListener extends Thread {
protected String masterName;
protected String host;
protected int port;
protected long subscribeRetryWaitTimeMillis;
protected volatile Jedis j;
protected AtomicBoolean running;
protected MasterListener() {
this.subscribeRetryWaitTimeMillis = 5000L;
this.running = new AtomicBoolean(false);
}
public MasterListener(String masterName, String host, int port) {
super(String.format("MasterListener-%s-[%s:%d]", masterName, host, port));
this.subscribeRetryWaitTimeMillis = 5000L;
this.running = new AtomicBoolean(false);
this.masterName = masterName;
this.host = host;
this.port = port;
}
public MasterListener(String masterName, String host, int port, long subscribeRetryWaitTimeMillis) {
this(masterName, host, port);
this.subscribeRetryWaitTimeMillis = subscribeRetryWaitTimeMillis;
}
public void run() {
this.running.set(true);
while(this.running.get()) {
this.j = new Jedis(this.host, this.port);
try {
if (!this.running.get()) {
break;
}
// 订阅+switch-master频道。
// 如果哨兵完成了主从切换,会在+switch-master频道发布这行信息:pmaster 172.16.0.149 6379 172.16.0.34 6379
// 然后对消息“pmaster 172.16.0.149 6379 172.16.0.34 6379”以“ ”分割得到一个String类型的数组,数组的第4个元素就是新的master的IP,第5个元素就是新的master的port,第1个元素就是新的master的名称。
this.j.subscribe(new JedisPubSub() {
public void onMessage(String channel, String message) {
JedisSentinelPool.this.log.fine("Sentinel " + MasterListener.this.host + ":" + MasterListener.this.port + " published: " + message + ".");
String[] switchMasterMsg = message.split(" ");
if (switchMasterMsg.length > 3) {
if (MasterListener.this.masterName.equals(switchMasterMsg[0])) {
JedisSentinelPool.this.initPool(JedisSentinelPool.this.toHostAndPort(Arrays.asList(switchMasterMsg[3], switchMasterMsg[4])));
} else {
JedisSentinelPool.this.log.fine("Ignoring message on +switch-master for master name " + switchMasterMsg[0] + ", our master name is " + MasterListener.this.masterName);
}
} else {
JedisSentinelPool.this.log.severe("Invalid message received on Sentinel " + MasterListener.this.host + ":" + MasterListener.this.port + " on channel +switch-master: " + message);
}
}
}, new String[]{"+switch-master"});
} catch (JedisConnectionException var8) {
if (this.running.get()) {
JedisSentinelPool.this.log.log(Level.SEVERE, "Lost connection to Sentinel at " + this.host + ":" + this.port + ". Sleeping 5000ms and retrying.", var8);
try {
Thread.sleep(this.subscribeRetryWaitTimeMillis);
} catch (InterruptedException var7) {
JedisSentinelPool.this.log.log(Level.SEVERE, "Sleep interrupted: ", var7);
}
} else {
JedisSentinelPool.this.log.fine("Unsubscribing from Sentinel at " + this.host + ":" + this.port);
}
} finally {
this.j.close();
}
}
}
}
Redis消息订阅与发布:
客户端可以将 Sentinel 看作是一个只提供了订阅功能的 Redis 服务器:
你不可以使用 PUBLISH 命令向这个服务器发送信息, 但你可以用 SUBSCRIBE 命令或者 PSUBSCRIBE 命令, 通过订阅给定的频道来获取相应的事件提醒。
一个频道能够接收和这个频道的名字相同的事件。
比如说, 名为 +sdown 的频道就可以接收所有实例进入主观下线(SDOWN)状态的事件。通过执行 PSUBSCRIBE * 命令可以接收所有事件信息。
+switch-master <master name> <oldip> <oldport> <newip> <newport> :配置变更,主服务器的 IP 和地址已经改变。
这是绝大多数外部用户都关心的信息。
可以看出,我们使用Sentinel命令和发布订阅两种机制就能很好的实现和客户端的集成整合:使用get-master-addr-by-name和slaves指令可以获取当前的Master和Slaves的地址和信息;而当发生故障转移时,即Master发生切换,可以通过订阅的+switch-master事件获得最新的Master信息。
——————————————————Redis哨兵原理总结完结——————————————————
订阅与发布的基本原理:
https://redisbook.readthedocs.io/en/latest/feature/pubsub.html