如何正確地連接Redis Sentinel。有人會說這有什麼難的,已經知道了主節點的ip地址和端口,用對應編程語言的客戶端連接主節點不就可以了嗎?但試想一下,如果這樣使用客戶端,客戶端連接Redis Sentinel和主從複製的Redis又有什麼區別呢,如果主節點掛掉了,雖然Redis Sentinel可以完成故障轉移,但是客戶端無法獲取這個變化,那麼使用Redis Sentinel的意義就不大了,所以各個語言的客戶端需要對Redis Sentinel進行顯式的支持。
Sentinel節點集合具備了監控、通知、自動故障轉移、配置提供者若干功能,也就是說實際上最瞭解主節點信息的就是Sentinel節點集合,而各個主節點可以通過<master-name>進行標識的,所以,無論是哪種編程語言的客戶端,如果需要正確地連接Redis Sentinel,必須有Sentinel節點集合和masterName兩個參數。
Redis Sentinel客戶端基本實現原理
1)遍歷Sentinel節點集合獲取一個可用的Sentinel節點,後面會介紹Sentinel節點之間可以共享數據,所以從任意一個Sentinel節點獲取主節點信息都是可以的
2)通過sentinel get-master-addr-by-name master-name這個API來獲取對應主節點的相關信息
3)驗證當前獲取的“主節點”是真正的主節點,這樣做的目的是爲了防止故障轉移期間主節點的變化
4)保持和Sentinel節點集合的“聯繫”,時刻獲取關於主節點的相關“信息”
從上面的模型可以看出,Redis Sentinel客戶端只有在初始化和切換主節點時需要和Sentinel節點集合進行交互來獲取主節點信息,所以在設計客戶端時需要將Sentinel節點集合考慮成配置(相關節點信息和變化)發現服務。
Java操作Redis Sentinel
作爲Redis的Java客戶端,Jedis能夠很好地支持Redis Sentinel,並且使用Jedis連接Redis Sentinel也很簡單,按照Redis Sentinel的原理,需要有masterName和Sentinel節點集合兩個參數。我們介紹了Jedis的連接池JedisPool,爲了不與之相混淆,Jedis針對Redis Sentinel給出了一個JedisSentinelPool,很顯然這個連接池保存的連接還是針對主節點的。Jedis給出很多構造方法
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——客戶端名
JedisSentinelPool jedisSentinelPool = new JedisSentinelPool(masterName,
sentinelSet, poolConfig, timeout);
此時timeout既代表連接超時又代表讀寫超時,password爲空,database默認使用0,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;
}
下面代碼就是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");