Sentinel——主從復(fù)制高可用方案
在使用主從復(fù)制時(shí)弄唧,我們面臨以下問(wèn)題:
-
手動(dòng)故障轉(zhuǎn)移
在發(fā)生上圖中的master宕機(jī)之后围苫,我們必須手動(dòng)替換主節(jié)點(diǎn)俄精,就像下圖:
-
寫能力和存儲(chǔ)能力受限(采用讀寫分離時(shí))
因此刁笙,Redis為我們提供了Sentinel架構(gòu):
Sentinel是一個(gè)管理redis實(shí)例的工具,它可以實(shí)現(xiàn)對(duì)redis的監(jiān)控滋尉、通知玉控、自動(dòng)故障轉(zhuǎn)移。sentinel不斷地檢測(cè)redis實(shí)例是否可以正常工作狮惜,通過(guò)API向其他程序報(bào)告redis的狀態(tài)奸远,如果redis master不能工作既棺,則會(huì)自動(dòng)啟動(dòng)故障轉(zhuǎn)移進(jìn)程讽挟,將其中的一個(gè)slave提升為master懒叛,其他的slave重新設(shè)置新的master服務(wù)器。
- Sentinel作用:
- Master狀態(tài)檢測(cè)
- 如果Master異常耽梅,則會(huì)進(jìn)行Master-Slave切換薛窥,將其中一個(gè)Slave作為Master,將之前的Master作為Slave
- Master-Slave切換后眼姐,master_redis.conf诅迷、slave_redis.conf和sentinel.conf的內(nèi)容都會(huì)發(fā)生改變,即master_redis.conf中會(huì)多一行slaveof的配置众旗,sentinel.conf的監(jiān)控目標(biāo)會(huì)隨之調(diào)換
- sentinel實(shí)現(xiàn)原理
-
sentinel創(chuàng)建兩個(gè)連接master的 異步網(wǎng)絡(luò)連接
- 命令連接:向master發(fā)送命令罢杉,并接受回復(fù)(默認(rèn)十秒一次通過(guò)INFO來(lái)獲取master信息)
- 訂閱連接:訂閱master的sentinel:hello頻道(依賴于Redis的訂閱發(fā)布,因?yàn)槲覀円郧罢f(shuō)過(guò)Redis是不能保存過(guò)期發(fā)布消息的贡歧,所以滩租,slave節(jié)點(diǎn)可能錯(cuò)過(guò)master的消息,所以需要sentinel來(lái)接受這些信息提供給slave利朵,同時(shí)其他sentinel也可以獲取最新信息)
-
sentinel創(chuàng)建兩個(gè)連接slave的 異步網(wǎng)絡(luò)連接(當(dāng)發(fā)現(xiàn)新的slave出現(xiàn)時(shí))
- 命令連接:向slave發(fā)送命令律想,并接受回復(fù)(默認(rèn)十秒一次通過(guò)INFO來(lái)獲取slave信息)
- 訂閱連接:訂閱slave的頻道
-
創(chuàng)建連向其他sentinel的命令連接(用于信息交換:主觀/客觀下線檢測(cè))
- sentinels字典:為master創(chuàng)建,用于保存自身以及其他監(jiān)視該master的sentinel節(jié)點(diǎn)信息
不需要訂閱連接的原因:sentinel訂閱了master和slave的頻道绍弟,會(huì)接受到新sentinel的信息
- sentinels字典:為master創(chuàng)建,用于保存自身以及其他監(jiān)視該master的sentinel節(jié)點(diǎn)信息
-
下線檢測(cè):
- sentinel默認(rèn)每秒一次向建立了命令連接的所有節(jié)點(diǎn)(包括sentinel)發(fā)送PING命令技即,用于檢測(cè)是否在線
- 主觀下線:當(dāng)master超過(guò)down-after-milliseconds設(shè)置的時(shí)間仍然返回?zé)o效回復(fù)時(shí),這時(shí)候sentinel將master標(biāo)記為主觀下線
- 客觀下線:在標(biāo)記為主觀下線后樟遣,改sentinel會(huì)想其他監(jiān)視該master的節(jié)點(diǎn)通過(guò)命令連接進(jìn)行詢問(wèn)而叼,當(dāng)贊同的數(shù)量達(dá)到我們?cè)O(shè)置的數(shù)量時(shí),認(rèn)為該master客觀下線豹悬。
上面的總結(jié)一下可以認(rèn)為是sentinel的三個(gè)定時(shí)任務(wù):
- 每10秒每個(gè)sentinel對(duì)master和slave執(zhí)行INFO(故障轉(zhuǎn)移時(shí)改為1秒)
- 發(fā)現(xiàn)slave節(jié)點(diǎn)
- 確認(rèn)主從關(guān)系
- 每2秒每個(gè)sentinel通過(guò)節(jié)點(diǎn)的channel向所有主從節(jié)點(diǎn)發(fā)送命令
- 交換對(duì)master節(jié)點(diǎn)的看法和自身信息(更新sentinels字典以及master節(jié)點(diǎn)實(shí)例結(jié)構(gòu))
- 每1秒每個(gè)sentinel對(duì)其他的sentinel和節(jié)點(diǎn)執(zhí)行ping(心跳檢測(cè))
- 下線檢測(cè)(主觀下線判斷)
-
- 故障轉(zhuǎn)移
在判斷master客觀下線后葵陵,選舉出的sentinel將進(jìn)行故障轉(zhuǎn)移- 多個(gè)sentinel發(fā)現(xiàn)并確認(rèn)master有問(wèn)題
- 選舉出一個(gè)sentinel作為領(lǐng)導(dǎo)
- 選舉出一個(gè)slave作為master
- 通知其余slave成為新的master的slave
- 通知客戶端主從變化
- 等待老的master復(fù)活成為新master的slave
- 領(lǐng)導(dǎo)者選舉
- 每個(gè)做主觀下線的sentinel節(jié)點(diǎn)向其他sentinel節(jié)點(diǎn)發(fā)送sentinel is-master-down-by-addr 命令,要求成為領(lǐng)導(dǎo)者
- 收到命令的sentinel節(jié)點(diǎn)如果沒(méi)有同意過(guò)其他sentinel節(jié)點(diǎn)的請(qǐng)求屿衅,將同意該請(qǐng)求埃难,否則拒絕
- 當(dāng)票數(shù)超過(guò)sentinel節(jié)點(diǎn)半數(shù)且超過(guò)quorum,將成為領(lǐng)導(dǎo)者
- 若此過(guò)程有多個(gè)sentinel節(jié)點(diǎn)成為領(lǐng)導(dǎo)者涤久,將等待一會(huì)重新選舉
- sentinel備份策略
可以參考以前的文章《Redis——持久化》 - 部署sentinel主從配置
我們這里是采用了三臺(tái)物理機(jī)涡尘,一共五個(gè)節(jié)點(diǎn),一主四從响迂,三個(gè)sentinel節(jié)點(diǎn)考抄。- 主節(jié)點(diǎn)主要配置
#開(kāi)放外網(wǎng)訪問(wèn)
bind 0.0.0.0
#下面我們?cè)O(shè)置了密碼,所以開(kāi)啟安全模式
protected-mode yes
#開(kāi)放端口
port 6379
#設(shè)置為守護(hù)線程
daemonize yes
#pid文件
pidfile "/var/run/redis_6379.pid"
#日志文件
logfile "6379.log"
#rdb文件
dbfilename "dump-6379.rdb"
#文件路徑
dir "/var/redis/6379"
#主節(jié)點(diǎn)訪問(wèn)密碼(主節(jié)點(diǎn)也要設(shè)置是因?yàn)楣收锨袚Q后主節(jié)點(diǎn)有可能變?yōu)閺墓?jié)點(diǎn))
masterauth "password"
requirepass "password"
- 從節(jié)點(diǎn)配置
#開(kāi)放外網(wǎng)訪問(wèn)
bind 0.0.0.0
#下面我們?cè)O(shè)置了密碼蔗彤,所以開(kāi)啟安全模式
protected-mode yes
#開(kāi)放端口
port 6380
#設(shè)置為守護(hù)線程
daemonize yes
#pid文件
pidfile "/var/run/redis_6380.pid"
#日志文件
logfile "6379.log"
#rdb文件
dbfilename "dump-6380.rdb"
#文件路徑
dir "/var/redis/6380"
#主節(jié)點(diǎn)訪問(wèn)密碼
masterauth "password"
#該節(jié)點(diǎn)密碼
requirepass "password"
#設(shè)置主節(jié)點(diǎn) 我用的公網(wǎng)ip
slaveof 120.1.1.1 6379
#設(shè)置從節(jié)點(diǎn)只讀
slave-read-only yes
其他從節(jié)點(diǎn)類似
- 哨兵節(jié)點(diǎn)配置
bind 0.0.0.0
port 26379
#要監(jiān)控的主節(jié)點(diǎn)名稱川梅、ip疯兼、port ,2表示將這個(gè)主服務(wù)器判斷為失效至少需要 2 個(gè) Sentinel 同意
sentinel monitor mymaster 120.1.11.11 6379 2
# Sentinel 認(rèn)為服務(wù)器已經(jīng)斷線所需的毫秒數(shù)贫途。
sentinel down-after-milliseconds mymaster 60000
# failover過(guò)期時(shí)間吧彪,當(dāng)failover(選出新的master)開(kāi)始后,在此時(shí)間內(nèi)仍然沒(méi)有觸發(fā)任何failover操作丢早,當(dāng)前sentinel 將會(huì)認(rèn)為此次failoer失敗
sentinel failover-timeout mymaster 180000
#指定了在執(zhí)行故障轉(zhuǎn)移時(shí)姨裸, 最多可以有多少個(gè)從服務(wù)器同時(shí)對(duì)新的主服務(wù)器進(jìn)行同步, 這個(gè)數(shù)字越小怨酝, 完成故障轉(zhuǎn)移所需的時(shí)間就越長(zhǎng)傀缩。
sentinel parallel-syncs mymaster 1
其他哨兵節(jié)點(diǎn)類似,只是端口不同农猬。
- 哨兵模式測(cè)試
編寫一個(gè)工具類赡艰,用來(lái)獲取jedis實(shí)例
public class RedisUtil {
/*redis-sentinel節(jié)點(diǎn)地址*/
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節(jié)點(diǎn)地址集合
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";
// 訪問(wèn)密碼
private static String AUTH = "password";
private static String HOST = "ip";
/**
* 可用連接實(shí)例的最大數(shù)目,默認(rèn)值為8斤葱; 如果賦值為-1慷垮,則表示不限制;如果pool已經(jīng)分配了maxActive個(gè)jedis實(shí)例苦掘,則此時(shí)pool的狀態(tài)為exhausted(耗盡)换帜。
*/
private static int MAX_ACTIVE = 1024;
// 控制一個(gè)pool最多有多少個(gè)狀態(tài)為idle(空閑的)的jedis實(shí)例,默認(rèn)值也是8鹤啡。
private static int MAX_IDLE = 200;
// 等待可用連接的最大時(shí)間惯驼,單位毫秒,默認(rèn)值為-1递瑰,表示永不超時(shí)祟牲。如果超過(guò)等待時(shí)間,則直接拋出JedisConnectionException抖部;
private static int MAX_WAIT = 10000;
private static int TIMEOUT = 10000;
// 在borrow一個(gè)jedis實(shí)例時(shí)说贝,是否提前進(jìn)行validate操作;如果為true慎颗,則得到的jedis實(shí)例均是可用的乡恕;
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實(shí)例.
*/
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();
}
}
}
測(cè)試客戶端
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);
}
}
可以看到,可以成功使用了俯萎,我們?cè)賮?lái)測(cè)試一下故障轉(zhuǎn)移是否可用
我們手動(dòng)關(guān)閉主節(jié)點(diǎn)傲宜,可以看到主節(jié)點(diǎn)已經(jīng)關(guān)閉
再來(lái)運(yùn)行一下程序,
發(fā)現(xiàn)主節(jié)點(diǎn)已經(jīng)自動(dòng)切換為其他的節(jié)點(diǎn)夫啊,最后我們?cè)賳?dòng)剛才關(guān)閉的主節(jié)點(diǎn)函卒,發(fā)現(xiàn)他已經(jīng)成為新主節(jié)點(diǎn)的從節(jié)點(diǎn)