Redis【一】Redis主從復(fù)制原理
Redis【二】Redis哨兵模式原理
Redis的高可用實(shí)現(xiàn)方案現(xiàn)在官方的有redis-sentinel redis-cluster都是直連(非代理模式)
- redis-sentinel 是主從模式晃琳,同一時(shí)間只有一個(gè)master實(shí)例可以寫醒第,其他從節(jié)點(diǎn)是只讀的羽嫡,如果master節(jié)點(diǎn)宕機(jī),redis-sentinel之間會(huì)通過(guò)raft協(xié)議選舉出一個(gè)負(fù)責(zé)故障遷移的主redis-sentinel妻枕,這個(gè)主redis-sentinel負(fù)責(zé)選擇一個(gè)slave redis節(jié)點(diǎn)作為master,設(shè)置其他slave的master為這個(gè)新的master
- redis-cluster 是集群粘驰、分片加主從模式
1. redis-sentinel拓?fù)浣Y(jié)構(gòu)圖
如上圖所示屡谐,sentinel是在正常的redis主從配置的基礎(chǔ)上,通過(guò)監(jiān)控redis節(jié)點(diǎn)的健康狀況蝌数,實(shí)現(xiàn)自動(dòng)化的主從切換愕掏。如果master節(jié)點(diǎn)掛掉了,sentinel會(huì)及時(shí)的發(fā)現(xiàn)顶伞,自動(dòng)化的設(shè)置新的master饵撑,并把其他的slave指向新的master剑梳。
Sentinel本質(zhì)上也是一個(gè)Redis節(jié)點(diǎn),但是僅支持部分命令
redis-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
其中最主要的配置是sentinel monitor mymaster 127.0.0.1 6379 2滑潘,格式為sentinel monitor {masterMame} {masterIP} {masterPort} {quorum}
- masterName 是當(dāng)sentinel同時(shí)監(jiān)控多套redis主從的時(shí)候區(qū)分不同的主從的垢乙。
- masterIP masterPort 是監(jiān)控的Redis實(shí)例中master的IP和port,sentinel通過(guò)該master可以獲取其他所有的slave實(shí)例和sentinel節(jié)點(diǎn)
- quorum 用于故障發(fā)現(xiàn)和判定语卤,如果quorum=2追逮,則代表至少2個(gè)Sentinel節(jié)點(diǎn)認(rèn)為主節(jié)點(diǎn)不可達(dá),這個(gè)不可達(dá)的判定才是客觀的粱侣。一般設(shè)置為 {sentinel實(shí)例個(gè)數(shù)}/2 + 1
redis-sentinel的3個(gè)定時(shí)任務(wù)
- 每隔10秒羊壹,向Redis master和slave節(jié)點(diǎn)發(fā)送info命令獲取最新的拓?fù)浣Y(jié)構(gòu)
通過(guò)解析info命令中的Replication信息,sentinel就可以獲取到所有的最新的master和slave節(jié)點(diǎn)齐婴,這也是為什么sentinel配而不需要顯示的配置slave節(jié)點(diǎn)油猫。
- 每隔2秒,向master節(jié)點(diǎn)的sentinel:hello 頻道上publish該sentinel節(jié)點(diǎn)對(duì)于主節(jié)點(diǎn)的判斷以及當(dāng)前sentinel節(jié)點(diǎn)的信息柠偶,同時(shí)每個(gè)sentinel節(jié)點(diǎn)也會(huì)訂閱該頻道情妖,來(lái)了解其他sentinel節(jié)點(diǎn)以及它們對(duì)主節(jié)點(diǎn)的判斷。 所以這個(gè)定時(shí)任務(wù)可以完成以下兩個(gè)工作:
- sentinel節(jié)點(diǎn)之間互相發(fā)現(xiàn)诱担,每個(gè)sentinel節(jié)點(diǎn)上只配置了redis master節(jié)點(diǎn)毡证, 通過(guò)這種方式,sentinel節(jié)點(diǎn)之間就可以實(shí)現(xiàn)互相發(fā)現(xiàn)蔫仙。來(lái)實(shí)現(xiàn)后續(xù)的sentinel節(jié)點(diǎn)之間的互相通信料睛、選舉操作
sentinel節(jié)點(diǎn)之間交換主節(jié)點(diǎn)狀態(tài),作為后面客觀下線以及領(lǐng)導(dǎo)者選舉的依據(jù)摇邦。
10.xx.xx.xx,26379,811ee2bc21cfc21e6aea0da3ed90f80deeod91kc,1530,mymaster,10.xx.xx.xx,6379,1530
sentinel節(jié)點(diǎn)publish的消息格式如下:
{sentinel節(jié)點(diǎn)IP} {sentinel節(jié)點(diǎn)端口} {sentinel節(jié)點(diǎn)runId} {sentinel節(jié)點(diǎn)配置版本} {主節(jié)點(diǎn)名字} {主節(jié)點(diǎn)IP} {主節(jié)點(diǎn)Port} {主節(jié)點(diǎn)配置版本}恤煞,示例:
每隔1秒,會(huì)向master施籍、slave節(jié)點(diǎn)居扒、其余sentinel節(jié)點(diǎn)發(fā)送ping命令做一次心跳檢測(cè),來(lái)確認(rèn)這些節(jié)點(diǎn)當(dāng)前是否可達(dá)丑慎。sentinel就是基于這個(gè)ping命令來(lái)決定redis節(jié)點(diǎn)和sentinel節(jié)點(diǎn)是否活著喜喂。
2. redis-sentinel故障遷移流程
主觀下線和客觀下線
- 主觀下線
上面介紹的sentinel的第三個(gè)定時(shí)任務(wù)每秒對(duì)其他的節(jié)點(diǎn)ping一次竿裂,如果這個(gè)節(jié)點(diǎn)超過(guò)down-after-milliseconds沒(méi)有有效的回復(fù)玉吁,該sentinel節(jié)點(diǎn)就會(huì)對(duì)這個(gè)節(jié)點(diǎn)做失敗判定,這個(gè)行為是主觀下線腻异。由此可見主觀下線只是單個(gè)sentinel的判定行為诈茧,在分布式環(huán)境中,肯定不能只靠一個(gè)節(jié)點(diǎn)的判斷捂掰。 - 客觀下線
當(dāng)sentinel主觀下線的節(jié)點(diǎn)是master時(shí)敢会,該sentinel就會(huì)向其他sentinel節(jié)點(diǎn)發(fā)送sentinel is-master-down-by-addr命令詢問(wèn)對(duì)主節(jié)點(diǎn)的判斷曾沈,當(dāng)超過(guò){quorum}個(gè)數(shù)sentinel節(jié)點(diǎn)認(rèn)為主節(jié)點(diǎn)確實(shí)有問(wèn)題,這時(shí)該sentinel會(huì)做出客觀下線的決定鸥昏。
leader選舉
sentinel做出客觀下線的決定之后并不會(huì)立即進(jìn)行故障轉(zhuǎn)移塞俱,而是要在sentinel之間選舉出一個(gè)leader,由leader來(lái)執(zhí)行具體的故障轉(zhuǎn)移工作吏垮。
其實(shí)在上面的客觀下線中障涯,sentinel發(fā)送sentinel is-master-down-by-addr命令的時(shí)候就已經(jīng)包含了leader選舉的一些信息。
sentinel is-master-down-by-addr {故障masterIP} {故障masterPort} {downState} {leader_runid}
- downState:1是下線膳汪、0是在線
- leader_runid:如果為*唯蝶,表示返回結(jié)果只是用來(lái)表示主節(jié)點(diǎn)是否可達(dá),當(dāng)leader_runid等于具體的runid時(shí)遗嗽,就代表目標(biāo)節(jié)點(diǎn)同意runid稱為leader
故障轉(zhuǎn)移
上面選擇出一個(gè)sentinel作為leader之后粘我,該leader就會(huì)開始操作故障轉(zhuǎn)移,具體步驟如下:
-
從所有slave中選出一個(gè)slave作為新的master痹换,選擇方法如下圖所示:
- sentinel leader對(duì)選擇出來(lái)的master執(zhí)行slaveof no one命令讓其成為真正的master
- sentinel leader對(duì)其他slave發(fā)送命令征字,讓它們成為新master的slave,新的slave的復(fù)制規(guī)則與parallel-syncs(同時(shí)允許多少個(gè)slave執(zhí)行sync操作)的配置有關(guān)系
- sentinel leader將原來(lái)的master更新為slave娇豫,并保持對(duì)其監(jiān)控匙姜,當(dāng)它恢復(fù)后命令它去復(fù)制新的master
3. redis-sentinel客戶端邏輯
實(shí)現(xiàn)一個(gè)Redis Sentinel客戶端的基本步驟如下:
- 遍歷sentinel節(jié)點(diǎn)集合獲取一個(gè)可用的sentinel節(jié)點(diǎn), 對(duì)sentinel節(jié)點(diǎn)調(diào)用sentinel get-master-addr-by-name master這個(gè)API來(lái)獲取對(duì)應(yīng)主節(jié)點(diǎn)的相關(guān)信息
- sentinel客戶端訂閱sentinel的swtich-master事件冯痢,如果master有變更氮昧,需要更新redis連接。
- sentinel客戶端如果是維護(hù)的連接池浦楣,在獲取連接的時(shí)候要檢查該連接是否與最新的master是一致的郭计,如果不一致就要銷毀該連接,重新獲取椒振。
redis.clients.jedis.JedisSentinelPool
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節(jié)點(diǎn),嘗試獲取master節(jié)點(diǎn)
for (String sentinel : sentinels) {
final HostAndPort hap = toHostAndPort(Arrays.asList(sentinel.split(":")));
log.fine("Connecting to Sentinel " + hap);
Jedis jedis = null;
try {
jedis = new Jedis(hap.getHost(), hap.getPort());
// 從sentinel獲取master節(jié)點(diǎn)
List<String> masterAddr = jedis.sentinelGetMasterAddrByName(masterName);
// connected to sentinel...
sentinelAvailable = true;
if (masterAddr == null || masterAddr.size() != 2) {
log.warning("Can not get master addr, master name: " + masterName + ". Sentinel: " + hap
+ ".");
continue;
}
master = toHostAndPort(masterAddr);
log.fine("Found Redis master at " + master);
break;
} catch (JedisException e) {
...
} finally {
if (jedis != null) {
jedis.close();
}
}
}
...
for (String sentinel : sentinels) {
final HostAndPort hap = toHostAndPort(Arrays.asList(sentinel.split(":")));
// MasterListener會(huì)訂閱sentinel節(jié)點(diǎn)的swatich-master渠道梧乘,如果master有變更澎迎,就會(huì)執(zhí)行initPool操作
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;
}
// 根據(jù)最新的master更新線程池中的jedis連接
private void initPool(HostAndPort master) {
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);
// clear操作只會(huì)destroy idle狀態(tài)的jedis連接,不會(huì)釋放正在使用的jedis連接选调。所以需要在getResource的時(shí)候再次做檢查
internalPool.clear();
}
log.info("Created JedisPool to master at " + master);
}
}
// 從連接池中獲取一個(gè)連接
public Jedis getResource() {
while (true) {
Jedis jedis = super.getResource();
jedis.setDataSource(this);
// get a reference because it can change concurrently
final HostAndPort master = currentHostMaster;
final HostAndPort connection = new HostAndPort(jedis.getClient().getHost(), jedis.getClient()
.getPort());
// 檢查當(dāng)前連接的master與最新的master是否相同夹供,如果相同就可以直接返回
if (master.equals(connection)) {
return jedis;
} else {
// 如果master不正確,就把該jedis連接返回給連接池仁堪,重新嘗試從連接池獲取
returnBrokenResource(jedis);
}
}
}