Redis哨兵模式(sentinel)學習總結及部署記錄(主從複製、讀寫分離、主從切換)

散盡浮華發表於2018-05-07

 

Redis的叢集方案大致有三種:1)redis cluster叢集方案;2)master/slave主從方案;3)哨兵模式來進行主從替換以及故障恢復。

一、sentinel哨兵模式介紹
Sentinel(哨兵)是用於監控redis叢集中Master狀態的工具,是Redis 的高可用性解決方案,sentinel哨兵模式已經被整合在redis2.4之後的版本中。sentinel是redis高可用的解決方案,sentinel系統可以監視一個或者多個redis master服務,以及這些master服務的所有從服務;當某個master服務下線時,自動將該master下的某個從服務升級為master服務替代已下線的master服務繼續處理請求。

sentinel可以讓redis實現主從複製,當一個叢集中的master失效之後,sentinel可以選舉出一個新的master用於自動接替master的工作,叢集中的其他redis伺服器自動指向新的master同步資料。一般建議sentinel採取奇數臺,防止某一臺sentinel無法連線到master導致誤切換。其結構如下:

Redis-Sentinel是Redis官方推薦的高可用性(HA)解決方案,當用Redis做Master-slave的高可用方案時,假如master當機了,Redis本身(包括它的很多客戶端)都沒有實現自動進行主備切換,而Redis-sentinel本身也是一個獨立執行的程式,它能監控多個master-slave叢集,發現master當機後能進行自動切換。Sentinel由一個或多個Sentinel 例項 組成的Sentinel 系統可以監視任意多個主伺服器,以及這些主伺服器屬下的所有從伺服器,並在被監視的主伺服器進入下線狀態時,自動將下線主伺服器屬下的某個從伺服器升級為新的主伺服器。

例如下圖所示:

在Server1 掉線後:

升級Server2 為新的主伺服器:

Sentinel版本
Sentinel當前最新的穩定版本稱為Sentinel 2(與之前的Sentinel 1區分開來)。隨著redis2.8的安裝包一起發行。安裝完Redis2.8後,可以在redis2.8/src/裡面找到Redis-sentinel的啟動程式。
強烈建議:如果你使用的是redis2.6(sentinel版本為sentinel 1),你最好應該使用redis2.8版本的sentinel 2,因為sentinel 1有很多的Bug,已經被官方棄用,所以強烈建議使用redis2.8以及sentinel 2。

Sentinel狀態持久化
snetinel的狀態會被持久化地寫入sentinel的配置檔案中。每次當收到一個新的配置時,或者新建立一個配置時,配置會被持久化到硬碟中,並帶上配置的版本戳。這意味著,可以安全的停止和重啟sentinel程式。

Sentinel作用: 
1)Master狀態檢測 
2)如果Master異常,則會進行Master-Slave切換,將其中一個Slave作為Master,將之前的Master作為Slave。 
3)Master-Slave切換後,master_redis.conf、slave_redis.conf和sentinel.conf的內容都會發生改變,即master_redis.conf中會多一行slaveof的配置,sentinel.conf的監控目標會隨之調換。

Sentinel工作方式(每個Sentinel例項都執行的定時任務)
1)每個Sentinel以每秒鐘一次的頻率向它所知的Master,Slave以及其他 Sentinel 例項傳送一個PING命令。
2)如果一個例項(instance)距離最後一次有效回覆PING命令的時間超過 own-after-milliseconds 選項所指定的值,則這個例項會被Sentinel標記為主觀下線。 
3)如果一個Master被標記為主觀下線,則正在監視這個Master的所有 Sentinel 要以每秒一次的頻率確認Master的確進入了主觀下線狀態。 
4)當有足夠數量的Sentinel(大於等於配置檔案指定的值)在指定的時間範圍內確認Master的確進入了主觀下線狀態,則Master會被標記為客觀下線。
5)在一般情況下,每個Sentinel 會以每10秒一次的頻率向它已知的所有Master,Slave傳送 INFO 命令。
6)當Master被Sentinel標記為客觀下線時,Sentinel 向下線的 Master 的所有Slave傳送 INFO命令的頻率會從10秒一次改為每秒一次。 
7)若沒有足夠數量的Sentinel同意Master已經下線,Master的客觀下線狀態就會被移除。 若 Master重新向Sentinel 的PING命令返回有效回覆,Master的主觀下線狀態就會被移除。

三個定時任務
sentinel在內部有3個定時任務
1)每10秒每個sentinel會對master和slave執行info命令,這個任務達到兩個目的:
a)發現slave節點
b)確認主從關係
2)每2秒每個sentinel通過master節點的channel交換資訊(pub/sub)。master節點上有一個釋出訂閱的頻道(__sentinel__:hello)。sentinel節點通過__sentinel__:hello頻道進行資訊交換(對節點的"看法"和自身的資訊),達成共識。
3)每1秒每個sentinel對其他sentinel和redis節點執行ping操作(相互監控),這個其實是一個心跳檢測,是失敗判定的依據。

主觀下線
所謂主觀下線(Subjectively Down, 簡稱 SDOWN)指的是單個Sentinel例項對伺服器做出的下線判斷,即單個sentinel認為某個服務下線(有可能是接收不到訂閱,之間的網路不通等等原因)。
主觀下線就是說如果伺服器在down-after-milliseconds給定的毫秒數之內, 沒有返回 Sentinel 傳送的 PING 命令的回覆, 或者返回一個錯誤, 那麼 Sentinel 將這個伺服器標記為主觀下線(SDOWN )。
sentinel會以每秒一次的頻率向所有與其建立了命令連線的例項(master,從服務,其他sentinel)發ping命令,通過判斷ping回覆是有效回覆,還是無效回覆來判斷例項時候線上(對該sentinel來說是“主觀線上”)。
sentinel配置檔案中的down-after-milliseconds設定了判斷主觀下線的時間長度,如果例項在down-after-milliseconds毫秒內,返回的都是無效回覆,那麼sentinel回認為該例項已(主觀)下線,修改其flags狀態為SRI_S_DOWN。如果多個sentinel監視一個服務,有可能存在多個sentinel的down-after-milliseconds配置不同,這個在實際生產中要注意。

客觀下線
客觀下線(Objectively Down, 簡稱 ODOWN)指的是多個 Sentinel 例項在對同一個伺服器做出 SDOWN 判斷, 並且通過 SENTINEL is-master-down-by-addr 命令互相交流之後, 得出的伺服器下線判斷,然後開啟failover。
客觀下線就是說只有在足夠數量的 Sentinel 都將一個伺服器標記為主觀下線之後, 伺服器才會被標記為客觀下線(ODOWN)。
只有當master被認定為客觀下線時,才會發生故障遷移。
當sentinel監視的某個服務主觀下線後,sentinel會詢問其它監視該服務的sentinel,看它們是否也認為該服務主觀下線,接收到足夠數量(這個值可以配置)的sentinel判斷為主觀下線,既任務該服務客觀下線,並對其做故障轉移操作。
sentinel通過傳送 SENTINEL is-master-down-by-addr ip port current_epoch runid,(ip:主觀下線的服務id,port:主觀下線的服務埠,current_epoch:sentinel的紀元,runid:*表示檢測服務下線狀態,如果是sentinel 執行id,表示用來選舉領頭sentinel)來詢問其它sentinel是否同意服務下線。
一個sentinel接收另一個sentinel發來的is-master-down-by-addr後,提取引數,根據ip和埠,檢測該服務時候在該sentinel主觀下線,並且回覆is-master-down-by-addr,回覆包含三個引數:down_state(1表示已下線,0表示未下線),leader_runid(領頭sentinal id),leader_epoch(領頭sentinel紀元)。
sentinel接收到回覆後,根據配置設定的下線最小數量,達到這個值,既認為該服務客觀下線。
客觀下線條件只適用於主伺服器: 對於任何其他型別的 Redis 例項, Sentinel 在將它們判斷為下線前不需要進行協商, 所以從伺服器或者其他 Sentinel 永遠不會達到客觀下線條件。只要一個 Sentinel 發現某個主伺服器進入了客觀下線狀態, 這個 Sentinel 就可能會被其他 Sentinel 推選出, 並對失效的主伺服器執行自動故障遷移操作。

在redis-sentinel的conf檔案裡有這麼兩個配置:
1)sentinel monitor <masterName> <ip> <port> <quorum>

四個引數含義:
masterName這個是對某個master+slave組合的一個區分標識(一套sentinel是可以監聽多套master+slave這樣的組合的)。
ip 和 port 就是master節點的 ip 和 埠號。
quorum這個引數是進行客觀下線的一個依據,意思是至少有 quorum 個sentinel主觀的認為這個master有故障,才會對這個master進行下線以及故障轉移。因為有的時候,某個sentinel節點可能因為自身網路原因,導致無法連線master,而此時master並沒有出現故障,所以這就需要多個sentinel都一致認為該master有問題,才可以進行下一步操作,這就保證了公平性和高可用。

2)sentinel down-after-milliseconds <masterName> <timeout>
這個配置其實就是進行主觀下線的一個依據,masterName這個引數不用說了,timeout是一個毫秒值,表示:如果這臺sentinel超過timeout這個時間都無法連通master包括slave(slave不需要客觀下線,因為不需要故障轉移)的話,就會主觀認為該master已經下線(實際下線需要客觀下線的判斷通過才會下線)

那麼,多個sentinel之間是如何達到共識的呢?
這就是依賴於前面說的第二個定時任務,某個sentinel先將master節點進行一個主觀下線,然後會將這個判定通過sentinel is-master-down-by-addr這個命令問對應的節點是否也同樣認為該addr的master節點要做客觀下線。最後當達成這一共識的sentinel個數達到前面說的quorum設定的這個值時,就會對該master節點下線進行故障轉移。quorum的值一般設定為sentinel個數的二分之一加1,例如3個sentinel就設定2。

主觀下線(SDOWN)和客觀下線(ODOWN)的更多細節
sentinel對於不可用有兩種不同的看法,一個叫主觀不可用(SDOWN),另外一個叫客觀不可用(ODOWN)。SDOWN是sentinel自己主觀上檢測到的關於master的狀態,ODOWN需要一定數量的sentinel達成一致意見才能認為一個master客觀上已經宕掉,各個sentinel之間通過命令SENTINEL is_master_down_by_addr來獲得其它sentinel對master的檢測結果。
從sentinel的角度來看,如果傳送了PING心跳後,在一定時間內沒有收到合法的回覆,就達到了SDOWN的條件。這個時間在配置中通過is-master-down-after-milliseconds引數配置。
當sentinel傳送PING後,以下回復之一都被認為是合法的:
PING replied with +PONG.
PING replied with -LOADING error.
PING replied with -MASTERDOWN error.
其它任何回覆(或者根本沒有回覆)都是不合法的。

從SDOWN切換到ODOWN不需要任何一致性演算法,只需要一個gossip協議:如果一個sentinel收到了足夠多的sentinel發來訊息告訴它某個master已經down掉了,SDOWN狀態就會變成ODOWN狀態。如果之後master可用了,這個狀態就會相應地被清理掉。
正如之前已經解釋過了,真正進行failover需要一個授權的過程,但是所有的failover都開始於一個ODOWN狀態。
ODOWN狀態只適用於master,對於不是master的redis節點sentinel之間不需要任何協商,slaves和sentinel不會有ODOWN狀態。

配置版本號
為什麼要先獲得大多數sentinel的認可時才能真正去執行failover呢?
當一個sentinel被授權後,它將會獲得宕掉的master的一份最新配置版本號,當failover執行結束以後,這個版本號將會被用於最新的配置。因為大多數sentinel都已經知道該版本號已經被要執行failover的sentinel拿走了,所以其他的sentinel都不能再去使用這個版本號。這意味著,每次failover都會附帶有一個獨一無二的版本號。我們將會看到這樣做的重要性。而且,sentinel叢集都遵守一個規則:如果sentinel A推薦sentinel B去執行failover,B會等待一段時間後,自行再次去對同一個master執行failover,這個等待的時間是通過failover-timeout配置項去配置的。從這個規則可以看出,sentinel叢集中的sentinel不會再同一時刻併發去failover同一個master,第一個進行failover的sentinel如果失敗了,另外一個將會在一定時間內進行重新進行failover,以此類推。
redis sentinel保證了活躍性:如果大多數sentinel能夠互相通訊,最終將會有一個被授權去進行failover.
redis sentinel也保證了安全性:每個試圖去failover同一個master的sentinel都會得到一個獨一無二的版本號。

配置傳播
一旦一個sentinel成功地對一個master進行了failover,它將會把關於master的最新配置通過廣播形式通知其它sentinel,其它的sentinel則更新對應master的配置。
一個faiover要想被成功實行,sentinel必須能夠向選為master的slave傳送SLAVEOF NO ONE命令,然後能夠通過INFO命令看到新master的配置資訊。
當將一個slave選舉為master併傳送SLAVEOF NO ONE後,即使其它的slave還沒針對新master重新配置自己,failover也被認為是成功了的,然後所有sentinels將會發布新的配置資訊。
新配在叢集中相互傳播的方式,就是為什麼我們需要當一個sentinel進行failover時必須被授權一個版本號的原因。
每個sentinel使用##釋出/訂閱##的方式持續地傳播master的配置版本資訊,配置傳播的##釋出/訂閱##管道是:__sentinel__:hello。
因為每一個配置都有一個版本號,所以以版本號最大的那個為標準。

舉個例子:
假設有一個名為mymaster的地址為192.168.10.202:6379。一開始,叢集中所有的sentinel都知道這個地址,於是為mymaster的配置打上版本號1。一段時候後mymaster死了,有一個sentinel被授權用版本號2對其進行failover。如果failover成功了,假設地址改為了192.168.10.202:9000,此時配置的版本號為2,進行failover的sentinel會將新配置廣播給其他的sentinel,由於其他sentinel維護的版本號為1,發現新配置的版本號為2時,版本號變大了,說明配置更新了,於是就會採用最新的版本號為2的配置。
這意味著sentinel叢集保證了第二種活躍性:一個能夠互相通訊的sentinel叢集最終會採用版本號最高且相同的配置。

sentinel的"仲裁會"
前面我們談到,當一個master被sentinel叢集監控時,需要為它指定一個引數,這個引數指定了當需要判決master為不可用,並且進行failover時,所需要的sentinel數量,可以稱這個引數為票數

不過,當failover主備切換真正被觸發後,failover並不會馬上進行,還需要sentinel中的大多數sentinel授權後才可以進行failover。
當ODOWN時,failover被觸發。failover一旦被觸發,嘗試去進行failover的sentinel會去獲得“大多數”sentinel的授權(如果票數比大多數還要大的時候,則詢問更多的sentinel)
這個區別看起來很微妙,但是很容易理解和使用。例如,叢集中有5個sentinel,票數被設定為2,當2個sentinel認為一個master已經不可用了以後,將會觸發failover,但是,進行failover的那個sentinel必須先獲得至少3個sentinel的授權才可以實行failover。
如果票數被設定為5,要達到ODOWN狀態,必須所有5個sentinel都主觀認為master為不可用,要進行failover,那麼得獲得所有5個sentinel的授權。

選舉領頭sentinel(即領導者選舉)
一個redis服務被判斷為客觀下線時,多個監視該服務的sentinel協商,選舉一個領頭sentinel,對該redis服務進行故障轉移操作。選舉領頭sentinel遵循以下規則:
1)所有的sentinel都有公平被選舉成領頭的資格。
2)所有的sentinel都有且只有一次將某個sentinel選舉成領頭的機會(在一輪選舉中),一旦選舉某個sentinel為領頭,不能更改。
3)sentinel設定領頭sentinel是先到先得,一旦當前sentinel設定了領頭sentinel,以後要求設定sentinel為領頭請求都會被拒絕。
4)每個發現服務客觀下線的sentinel,都會要求其他sentinel將自己設定成領頭。
5)當一個sentinel(源sentinel)向另一個sentinel(目sentinel)傳送is-master-down-by-addr ip port current_epoch runid命令的時候,runid引數不是*,而是sentinel執行id,就表示源sentinel要求目標sentinel選舉其為領頭。
6)源sentinel會檢查目標sentinel對其要求設定成領頭的回覆,如果回覆的leader_runid和leader_epoch為源sentinel,表示目標sentinel同意將源sentinel設定成領頭。
7)如果某個sentinel被半數以上的sentinel設定成領頭,那麼該sentinel既為領頭。
8)如果在限定時間內,沒有選舉出領頭sentinel,暫定一段時間,再選舉。

為什麼要選領導者?
簡單來說,就是因為只能有一個sentinel節點去完成故障轉移。
sentinel is-master-down-by-addr這個命令有兩個作用,一是確認下線判定,二是進行領導者選舉。
選舉過程:
1)每個做主觀下線的sentinel節點向其他sentinel節點傳送上面那條命令,要求將它設定為領導者。
2)收到命令的sentinel節點如果還沒有同意過其他的sentinel傳送的命令(還未投過票),那麼就會同意,否則拒絕。
3)如果該sentinel節點發現自己的票數已經過半且達到了quorum的值,就會成為領導者
4)如果這個過程出現多個sentinel成為領導者,則會等待一段時間重新選舉。

Redis Sentinel的主從切換方案
Redis 2.8版開始正式提供名為Sentinel的主從切換方案,通俗的來講,Sentinel可以用來管理多個Redis伺服器例項,可以實現一個功能上實現HA的叢集,Sentinel主要負責三個方面的任務
1)監控(Monitoring): Sentinel 會不斷地檢查你的主伺服器和從伺服器是否運作正常。
2)提醒(Notification): 當被監控的某個 Redis 伺服器出現問題時, Sentinel 可以通過 API 向管理員或者其他應用程式傳送通知。
3)自動故障遷移(Automatic failover): 當一個主伺服器不能正常工作時, Sentinel 會開始一次自動故障遷移操作, 它會將失效主伺服器的其中一個從伺服器升級為新的主伺服器, 並讓失效主伺服器的其他從伺服器改為複製新的主伺服器; 當客戶端試圖連線失效的主伺服器時, 叢集也會向客戶端返回新主伺服器的地址, 使得叢集可以使用新主伺服器代替失效伺服器。

Redis Sentinel 是一個分散式系統, 可以在一個架構中執行多個 Sentinel 程式(progress), 這些程式使用流言協議(gossip protocols)來接收關於主伺服器是否下線的資訊, 並使用投票協議(agreement protocols)來決定是否執行自動故障遷移, 以及選擇哪個從伺服器作為新的主伺服器。
一個簡單的主從結構加sentinel叢集的架構圖如下:

上圖是一主一從節點,加上兩個部署了sentinel的叢集,sentinel叢集之間會互相通訊,溝通交流redis節點的狀態,做出相應的判斷並進行處理,這裡的主觀下線狀態和客觀下線狀態是比較重要的狀態,它們決定了是否進行故障轉移
可以 通過訂閱指定的頻道資訊,當伺服器出現故障得時候通知管理員
客戶端可以將 Sentinel 看作是一個只提供了訂閱功能的 Redis 伺服器,你不可以使用 PUBLISH 命令向這個伺服器傳送資訊,但你可以用 SUBSCRIBE 命令或者 PSUBSCRIBE 命令, 通過訂閱給定的頻道來獲取相應的事件提醒。 一個頻道能夠接收和這個頻道的名字相同的事件。 比如說, 名為 +sdown 的頻道就可以接收所有例項進入主觀下線(SDOWN)狀態的事件。

個人認為,Sentinel實現的最主要的一個功能就是能做到自動故障遷移,即當某一個master掛了的時候,可以自動的將某一個slave提升為新的master,且原master的所有slave也都自動的將自己的master改為新提升的master,這樣我們的程式的可用性大大提高了。只要redis安裝完成,Sentinel就安裝完成了,Sentinel整合在redis裡了。

Sentinel支援叢集(可以部署在多臺機器上,也可以在一臺物理機上通過多埠實現偽叢集部署)
很顯然,只使用單個sentinel程式來監控redis叢集是不可靠的,當sentinel程式宕掉後(sentinel本身也有單點問題,single-point-of-failure)整個叢集系統將無法按照預期的方式執行。所以有必要將sentinel叢集,這樣有幾個好處:
1)即使有一些sentinel程式宕掉了,依然可以進行redis叢集的主備切換;
2)如果只有一個sentinel程式,如果這個程式執行出錯,或者是網路堵塞,那麼將無法實現redis叢集的主備切換(單點問題);
3)如果有多個sentinel,redis的客戶端可以隨意地連線任意一個sentinel來獲得關於redis叢集中的資訊。

sentinel叢集注意事項
1)只有Sentinel 叢集中大多數伺服器認定master主觀下線時master才會被認定為客觀下線,才可以進行故障遷移,也就是說,即使不管我們在sentinel monitor中設定的數是多少,就算是滿足了該值,只要達不到大多數,就不會發生故障遷移。
2)官方建議sentinel至少部署三臺,且分佈在不同機器。這裡主要考慮到sentinel的可用性,假如我們只部署了兩臺sentinel,且quorum設定為1,也可以實現自動故障遷移,但假如其中一臺sentinel掛了,就永遠不會觸發自動故障遷移,因為永遠達不到大多數sentinel認定master主觀下線了。
3)sentinel monitor配置中的master IP儘量不要寫127.0.0.1或localhost,因為客戶端,如jedis獲取master是根據這個獲取的,若這樣配置,jedis獲取的ip則是127.0.0.1,這樣就可能導致程式連線不上master
4)當sentinel 啟動後會自動的修改sentinel.conf檔案,如已發現的master的slave資訊,和叢集中其它sentinel 的資訊等,這樣即使重啟sentinel也能保持原來的狀態。注意,當叢集伺服器調整時,如更換sentinel的機器,或者新配置一個sentinel,請不要直接複製原來執行過得sentinel配置檔案,因為其裡面自動生成了以上說的那些資訊,我們應該複製一個新的配置檔案或者把自動生成的資訊給刪掉。
5)當發生故障遷移的時候,master的變更記錄與slave更換master的修改會自動同步到redis的配置檔案,這樣即使重啟redis也能保持變更後的狀態。

每個 Sentinel 都需要定期執行的任務
每個 Sentinel 以每秒鐘一次的頻率向它所知的主伺服器、從伺服器以及其他 Sentinel 例項傳送一個 PING 命令。
如果一個例項(instance)距離最後一次有效回覆 PING 命令的時間超過 down-after-milliseconds 選項所指定的值, 那麼這個例項會被 Sentinel 標記為主觀下線。 一個有效回覆可以是: +PONG 、 -LOADING 或者 -MASTERDOWN 。
如果一個主伺服器被標記為主觀下線, 那麼正在監視這個主伺服器的所有 Sentinel 要以每秒一次的頻率確認主伺服器的確進入了主觀下線狀態。
如果一個主伺服器被標記為主觀下線, 並且有足夠數量的 Sentinel (至少要達到配置檔案指定的數量)在指定的時間範圍內同意這一判斷, 那麼這個主伺服器被標記為客觀下線。
在一般情況下, 每個 Sentinel 會以每 10 秒一次的頻率向它已知的所有主伺服器和從伺服器傳送 INFO 命令。 當一個主伺服器被 Sentinel 標記為客觀下線時, Sentinel 向下線主伺服器的所有從伺服器傳送 INFO 命令的頻率會從 10 秒一次改為每秒一次。
當沒有足夠數量的 Sentinel 同意主伺服器已經下線, 主伺服器的客觀下線狀態就會被移除。 當主伺服器重新向 Sentinel 的PING 命令返回有效回覆時, 主伺服器的主管下線狀態就會被移除。

Sentinel之間和Slaves之間的自動發現機制
雖然sentinel叢集中各個sentinel都互相連線彼此來檢查對方的可用性以及互相傳送訊息。但是你不用在任何一個sentinel配置任何其它的sentinel的節點。因為sentinel利用了master的釋出/訂閱機制去自動發現其它也監控了統一master的sentinel節點。
通過向名為__sentinel__:hello的管道中傳送訊息來實現。
同樣,你也不需要在sentinel中配置某個master的所有slave的地址,sentinel會通過詢問master來得到這些slave的地址的。
每個sentinel通過向每個master和slave的釋出/訂閱頻道__sentinel__:hello每秒傳送一次訊息,來宣佈它的存在。
每個sentinel也訂閱了每個master和slave的頻道__sentinel__:hello的內容,來發現未知的sentinel,當檢測到了新的sentinel,則將其加入到自身維護的master監控列表中。
每個sentinel傳送的訊息中也包含了其當前維護的最新的master配置。如果某個sentinel發現
自己的配置版本低於接收到的配置版本,則會用新的配置更新自己的master配置。
在為一個master新增一個新的sentinel前,sentinel總是檢查是否已經有sentinel與新的sentinel的程式號或者是地址是一樣的。如果是那樣,這個sentinel將會被刪除,而把新的sentinel新增上去。

sentinel和redis身份驗證
當一個master配置為需要密碼才能連線時,客戶端和slave在連線時都需要提供密碼。
master通過requirepass設定自身的密碼,不提供密碼無法連線到這個master。
slave通過masterauth來設定訪問master時的密碼。
但是當使用了sentinel時,由於一個master可能會變成一個slave,一個slave也可能會變成master,所以需要同時設定上述兩個配置項。

Sentinel API
在預設情況下, Sentinel 使用 TCP 埠 26379 (普通 Redis 伺服器使用的是 6379 )。Sentinel 接受 Redis 協議格式的命令請求, 所以你可以使用 redis-cli 或者任何其他 Redis 客戶端來與 Sentinel 進行通訊。有兩種方式可以和 Sentinel 進行通訊:
1)是通過直接傳送命令來查詢被監視 Redis 伺服器的當前狀態, 以及 Sentinel 所知道的關於其他 Sentinel 的資訊, 諸如此類。
2)是使用釋出與訂閱功能, 通過接收 Sentinel 傳送的通知: 當執行故障轉移操作, 或者某個被監視的伺服器被判斷為主觀下線或者客觀下線時, Sentinel 就會傳送相應的資訊。

Sentinel命令(即登入到sentinel節點後執行的命令,比如執行"redis-cli -h 192.168.10.203 -p 26379"命令後,才可以執行下面命令)
PING :返回 PONG 。
SENTINEL masters :列出所有被監視的主伺服器,以及這些主伺服器的當前狀態;
SENTINEL slaves <master name> :列出給定主伺服器的所有從伺服器,以及這些從伺服器的當前狀態;
SENTINEL get-master-addr-by-name <master name> : 返回給定名字的主伺服器的 IP 地址和埠號。 如果這個主伺服器正在執行故障轉移操作, 或者針對這個主伺服器的故障轉移操作已經完成, 那麼這個命令返回新的主伺服器的 IP 地址和埠號;
SENTINEL reset <pattern> : 重置所有名字和給定模式 pattern 相匹配的主伺服器。 pattern 引數是一個 Glob 風格的模式。 重置操作清楚主伺服器目前的所有狀態, 包括正在執行中的故障轉移, 並移除目前已經發現和關聯的, 主伺服器的所有從伺服器和 Sentinel ;
SENTINEL failover <master name> : 當主伺服器失效時, 在不詢問其他 Sentinel 意見的情況下, 強制開始一次自動故障遷移。 (不過發起故障轉移的 Sentinel 會向其他 Sentinel 傳送一個新的配置,其他 Sentinel 會根據這個配置進行相應的更新)

SENTINEL MONITOR <name> <ip> <port> <quorum> 這個命令告訴sentinel去監聽一個新的master
SENTINEL REMOVE <name> 命令sentinel放棄對某個master的監聽
SENTINEL SET <name> <option> <value> 這個命令很像Redis的CONFIG SET命令,用來改變指定master的配置。支援多個<option><value>。例如以下例項:SENTINEL SET objects-cache-master down-after-milliseconds 1000
只要是配置檔案中存在的配置項,都可以用SENTINEL SET命令來設定。這個還可以用來設定master的屬性,比如說quorum(票數),而不需要先刪除master,再重新新增master。例如:SENTINEL SET objects-cache-master quorum 5

客戶端可以通過SENTINEL get-master-addr-by-name <master name>獲取當前的主伺服器IP地址和埠號,以及SENTINEL slaves <master name>獲取所有的Slaves資訊。

[root@redis-master ~]# redis-cli -h 192.168.10.202 -p 6379 INFO|grep role
role:slave
[root@redis-master ~]# redis-cli -h 192.168.10.203 -p 6379 INFO|grep role
role:slave
[root@redis-master ~]# redis-cli -h 192.168.10.205 -p 6379 INFO|grep role
role:master

登入任意一個節點的sentinel,進行相關命令的操作(下面命令例子中的redisMaster是sentinel監控redis主從狀態時定義的master名稱)

1)sentinel masters   羅列所有sentinel 監視相關的master
[root@redis-master ~]# redis-cli -h 192.168.10.205 -p 26379 
192.168.10.205:26379> sentinel masters 

2)sentinel master masterName   列出一個master相關的的資訊
[root@redis-master ~]# redis-cli -h 192.168.10.205 -p 26379 
192.168.10.205:26379> sentinel master redisMaster

3)sentinel slaves masterName   列出一個master相應的slave組相關的資料
[root@redis-master ~]# redis-cli -h 192.168.10.205 -p 26379 
192.168.10.205:26379> sentinel slaves redisMaster

4)sentinel sentinels masterName   列出master相關的sentinels組其他相關的資訊
[root@redis-master ~]# redis-cli -h 192.168.10.205 -p 26379 
192.168.10.205:26379> sentinel sentinels redisMaster

5)sentinel get-master-addr-by-name masterName   獲取master-name相關的 ip addr 的資訊
[root@redis-master ~]# redis-cli -h 192.168.10.205 -p 26379 
192.168.10.205:26379> sentinel get-master-addr-by-name redisMaster
1) "192.168.10.205"
2) "6379"

增加或刪除Sentinel
由於有sentinel自動發現機制,所以新增一個sentinel到你的叢集中非常容易,你所需要做的只是監控到某個Master上,然後新新增的sentinel就能獲得其他sentinel的資訊以及master所有的slaves。
如果你需要新增多個sentinel,建議你一個接著一個新增,這樣可以預防網路隔離帶來的問題。你可以每個30秒新增一個sentinel。最後你可以用SENTINEL MASTER mastername來檢查一下是否所有的sentinel都已經監控到了master。
刪除一個sentinel顯得有點複雜:因為sentinel永遠不會刪除一個已經存在過的sentinel,即使它已經與組織失去聯絡很久了。
要想刪除一個sentinel,應該遵循如下步驟:
1)停止所要刪除的sentinel
2)傳送一個SENTINEL RESET * 命令給所有其它的sentinel例項,如果你想要重置指定master上面的sentinel,只需要把*號改為特定的名字,注意,需要一個接一個發,每次傳送的間隔不低於30秒。
3)檢查一下所有的sentinels是否都有一致的當前sentinel數。使用SENTINEL MASTER mastername 來查詢。

刪除舊master或者不可達slave
sentinel永遠會記錄好一個Master的slaves,即使slave已經與組織失聯好久了。這是很有用的,因為sentinel叢集必須有能力把一個恢復可用的slave進行重新配置。
並且,failover後,失效的master將會被標記為新master的一個slave,這樣的話,當它變得可用時,就會從新master上覆制資料。
然後,有時候你想要永久地刪除掉一個slave(有可能它曾經是個master),你只需要傳送一個SENTINEL RESET master命令給所有的sentinels,它們將會更新列表裡能夠正確地複製master資料的slave。

釋出與訂閱資訊(sentinel的日誌檔案裡可以看到這些資訊)
客戶端可以將 Sentinel 看作是一個只提供了訂閱功能的 Redis 伺服器: 你不可以使用 PUBLISH 命令向這個伺服器傳送資訊, 但你可以用 SUBSCRIBE 命令或者 PSUBSCRIBE 命令, 通過訂閱給定的頻道來獲取相應的事件提醒。

一個頻道能夠接收和這個頻道的名字相同的事件。 比如說, 名為 +sdown 的頻道就可以接收所有例項進入主觀下線(SDOWN)狀態的事件。

通過執行 "PSUBSCRIBE * "命令可以接收所有事件資訊(即訂閱所有訊息)。

以下列出的是客戶端可以通過訂閱來獲得的頻道和資訊的格式: 第一個英文單詞是頻道/事件的名字, 其餘的是資料的格式。

注意, 當格式中包含 instance details 字樣時, 表示頻道所返回的資訊中包含了以下用於識別目標例項的內容.

以下是所有可以收到的訊息的訊息格式,如果你訂閱了所有訊息的話。第一個單詞是頻道的名字,其它是資料的格式。
注意:以下的instance details的格式是:
<instance-type> <name> <ip> <port> @ <master-name> <master-ip> <master-port>
如果這個redis例項是一個master,那麼@之後的訊息就不會顯示。

+reset-master <instance details> -- 當master被重置時.
    +slave <instance details> -- 當檢測到一個slave並新增進slave列表時.
    +failover-state-reconf-slaves <instance details> -- Failover狀態變為reconf-slaves狀態時
    +failover-detected <instance details> -- 當failover發生時
    +slave-reconf-sent <instance details> -- sentinel傳送SLAVEOF命令把它重新配置時
    +slave-reconf-inprog <instance details> -- slave被重新配置為另外一個master的slave,但資料複製還未發生時。
    +slave-reconf-done <instance details> -- slave被重新配置為另外一個master的slave並且資料複製已經與master同步時。
    -dup-sentinel <instance details> -- 刪除指定master上的冗餘sentinel時 (當一個sentinel重新啟動時,可能會發生這個事件).
    +sentinel <instance details> -- 當master增加了一個sentinel時。
    +sdown <instance details> -- 進入SDOWN狀態時;
    -sdown <instance details> -- 離開SDOWN狀態時。
    +odown <instance details> -- 進入ODOWN狀態時。
    -odown <instance details> -- 離開ODOWN狀態時。
    +new-epoch <instance details> -- 當前配置版本被更新時。
    +try-failover <instance details> -- 達到failover條件,正等待其他sentinel的選舉。
    +elected-leader <instance details> -- 被選舉為去執行failover的時候。
    +failover-state-select-slave <instance details> -- 開始要選擇一個slave當選新master時。
    no-good-slave <instance details> -- 沒有合適的slave來擔當新master
    selected-slave <instance details> -- 找到了一個適合的slave來擔當新master
    failover-state-send-slaveof-noone <instance details> -- 當把選擇為新master的slave的身份進行切換的時候。
    failover-end-for-timeout <instance details> -- failover由於超時而失敗時。
    failover-end <instance details> -- failover成功完成時。
    switch-master <master name> <oldip> <oldport> <newip> <newport> -- 當master的地址發生變化時。通常這是客戶端最感興趣的訊息了。
    +tilt -- 進入Tilt模式。
    -tilt -- 退出Tilt模式。

可以看出,使用Sentinel命令和釋出訂閱兩種機制就能很好的實現和客戶端的整合整合:
使用get-master-addr-by-name和slaves指令可以獲取當前的Master和Slaves的地址和資訊;而當發生故障轉移時,即Master發生切換,可以通過訂閱的+switch-master事件獲得最新的Master資訊。

sentinel.conf中的notification-script
在sentinel.conf中可以配置多個sentinel notification-script <master name> <shell script-path>, 如sentinel notification-script mymaster ./check.sh
這個是在群集failover時會觸發執行指定的指令碼。指令碼的執行結果若為1,即稍後重試(最大重試次數為10);若為2,則執行結束。並且指令碼最大執行時間為60秒,超時會被終止執行。

目前會存在該指令碼被執行多次的問題,網上查詢資料獲得的解釋是:指令碼分為兩個級別, SENTINEL_LEADER 和 SENTINEL_OBSERVER ,前者僅由領頭 Sentinel 執行(一個 Sentinel),而後者由監視同一個 master 的所有 Sentinel 執行(多個 Sentinel)。

無failover時的配置糾正
即使當前沒有failover正在進行,sentinel依然會使用當前配置去設定監控的master。特別是:
1)根據最新配置確認為slaves的節點卻聲稱自己是master(上文例子中被網路隔離後的的redis3),這時它們會被重新配置為當前master的slave。
2)如果slaves連線了一個錯誤的master,將會被改正過來,連線到正確的master。

Slave選舉與優先順序
當一個sentinel準備好了要進行failover,並且收到了其他sentinel的授權,那麼就需要選舉出一個合適的slave來做為新的master。

slave的選舉主要會評估slave的以下幾個方面:
1)與master斷開連線的次數
2)Slave的優先順序
3)資料複製的下標(用來評估slave當前擁有多少master的資料)
4)程式ID

如果一個slave與master失去聯絡超過10次,並且每次都超過了配置的最大失聯時間(down-after-milliseconds),如果sentinel在進行failover時發現slave失聯,那麼這個slave就會被sentinel認為不適合用來做新master的。
更嚴格的定義是,如果一個slave持續斷開連線的時間超過
(down-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state
就會被認為失去選舉資格。

符合上述條件的slave才會被列入master候選人列表,並根據以下順序來進行排序:
1)sentinel首先會根據slaves的優先順序來進行排序,優先順序越小排名越靠前。
2)如果優先順序相同,則檢視複製的下標,哪個從master接收的複製資料多,哪個就靠前。
3)如果優先順序和下標都相同,就選擇程式ID較小的那個。

一個redis無論是master還是slave,都必須在配置中指定一個slave優先順序。要注意到master也是有可能通過failover變成slave的。
如果一個redis的slave優先順序配置為0,那麼它將永遠不會被選為master。但是它依然會從master哪裡複製資料。

故障轉移
所謂故障轉移就是當master當機,選一個合適的slave來晉升為master的操作,redis-sentinel會自動完成這個,不需要我們手動來實現。

一次故障轉移操作大致分為以下流程:
發現主伺服器已經進入客觀下線狀態。
對我們的當前叢集進行自增, 並嘗試在這個叢集中當選。
如果當選失敗, 那麼在設定的故障遷移超時時間的兩倍之後, 重新嘗試當選。 如果當選成功, 那麼執行以下步驟:
選出一個從伺服器,並將它升級為主伺服器。
向被選中的從伺服器傳送 SLAVEOF NO ONE 命令,讓它轉變為主伺服器。
通過釋出與訂閱功能, 將更新後的配置傳播給所有其他 Sentinel , 其他 Sentinel 對它們自己的配置進行更新。
向已下線主伺服器的從伺服器傳送 SLAVEOF 命令, 讓它們去複製新的主伺服器。
當所有從伺服器都已經開始複製新的主伺服器時, 領頭 Sentinel 終止這次故障遷移操作。
每當一個 Redis 例項被重新配置(reconfigured) —— 無論是被設定成主伺服器、從伺服器、又或者被設定成其他主伺服器的從伺服器 —— Sentinel 都會向被重新配置的例項傳送一個 CONFIG REWRITE 命令, 從而確保這些配置會持久化在硬碟裡。

Sentinel 使用以下規則來選擇新的主伺服器:
在失效主伺服器屬下的從伺服器當中, 那些被標記為主觀下線、已斷線、或者最後一次回覆 PING 命令的時間大於五秒鐘的從伺服器都會被淘汰。
在失效主伺服器屬下的從伺服器當中, 那些與失效主伺服器連線斷開的時長超過 down-after 選項指定的時長十倍的從伺服器都會被淘汰。
在經歷了以上兩輪淘汰之後剩下來的從伺服器中, 我們選出複製偏移量(replication offset)最大的那個從伺服器作為新的主伺服器; 如果複製偏移量不可用, 或者從伺服器的複製偏移量相同, 那麼帶有最小執行 ID 的那個從伺服器成為新的主伺服器。

Sentinel 自動故障遷移的一致性特質
Sentinel 自動故障遷移使用 Raft 演算法來選舉領頭(leader) Sentinel , 從而確保在一個給定的紀元(epoch)裡, 只有一個領頭產生。

這表示在同一個紀元中, 不會有兩個 Sentinel 同時被選中為領頭, 並且各個 Sentinel 在同一個紀元中只會對一個領頭進行投票。

更高的配置紀元總是優於較低的紀元, 因此每個 Sentinel 都會主動使用更新的紀元來代替自己的配置。

簡單來說, 可以將 Sentinel 配置看作是一個帶有版本號的狀態。 一個狀態會以最後寫入者勝出(last-write-wins)的方式(也即是,最新的配置總是勝出)傳播至所有其他 Sentinel 。

舉個例子, 當出現網路分割(network partitions)時, 一個 Sentinel 可能會包含了較舊的配置, 而當這個 Sentinel 接到其他 Sentinel 發來的版本更新的配置時, Sentinel 就會對自己的配置進行更新。

如果要在網路分割出現的情況下仍然保持一致性, 那麼應該使用 min-slaves-to-write 選項, 讓主伺服器在連線的從例項少於給定數量時停止執行寫操作, 與此同時, 應該在每個執行 Redis 主伺服器或從伺服器的機器上執行 Redis Sentinel 程式。

Sentinel 狀態的持久化
Sentinel 的狀態會被持久化在 Sentinel 配置檔案裡面。每當 Sentinel 接收到一個新的配置, 或者當領頭 Sentinel 為主伺服器建立一個新的配置時, 這個配置會與配置紀元一起被儲存到磁碟裡面。這意味著停止和重啟 Sentinel 程式都是安全的。

Sentinel 在非故障遷移的情況下對例項進行重新配置
即使沒有自動故障遷移操作在進行, Sentinel 總會嘗試將當前的配置設定到被監視的例項上面。 特別是:

根據當前的配置, 如果一個從伺服器被宣告為主伺服器, 那麼它會代替原有的主伺服器, 成為新的主伺服器, 並且成為原有主伺服器的所有從伺服器的複製物件。
那些連線了錯誤主伺服器的從伺服器會被重新配置, 使得這些從伺服器會去複製正確的主伺服器。
不過, 在以上這些條件滿足之後, Sentinel 在對例項進行重新配置之前仍然會等待一段足夠長的時間, 確保可以接收到其他 Sentinel 發來的配置更新, 從而避免自身因為儲存了過期的配置而對例項進行了不必要的重新配置。

總結來說,故障轉移分為三個步驟:

1)從下線的主服務的所有從服務裡面挑選一個從服務,將其轉成主服務
sentinel狀態資料結構中儲存了主服務的所有從服務資訊,領頭sentinel按照如下的規則從從服務列表中挑選出新的主服務;
刪除列表中處於下線狀態的從服務;
刪除最近5秒沒有回覆過領頭sentinel info資訊的從服務;
刪除與已下線的主服務斷開連線時間超過 down-after-milliseconds*10毫秒的從服務,這樣就能保留從的資料比較新(沒有過早的與主斷開連線);
領頭sentinel從剩下的從列表中選擇優先順序高的,如果優先順序一樣,選擇偏移量最大的(偏移量大說明覆制的資料比較新),如果偏移量一樣,選擇執行id最小的從服務。

2)已下線主服務的所有從服務改為複製新的主服務
挑選出新的主服務之後,領頭sentinel 向原主服務的從服務傳送 slaveof 新主服務 的命令,複製新master。

3)將已下線的主服務設定成新的主服務的從服務,當其回覆正常時,複製新的主服務,變成新的主服務的從服務
同理,當已下線的服務重新上線時,sentinel會向其傳送slaveof命令,讓其成為新主的從。

溫馨提示:還可以向任意sentinel發生sentinel failover <masterName> 進行手動故障轉移,這樣就不需要經過上述主客觀和選舉的過程。

sentinel.conf檔案配置引數解釋

1)sentinel monitor mymaster 192.168.10.202 6379 2
Sentine監聽的maste地址,第一個引數是給master起的名字,第二個引數為master IP,第三個為master埠,第四個為當該master掛了的時候,若想將該master判為失效,
在Sentine叢集中必須至少2個Sentine同意才行,只要該數量不達標,則就不會發生故障遷移。也就是說只要有2個sentinel認為master下線,就認為該master客觀下線,
啟動failover並選舉產生新的master。通常最後一個引數不能多於啟動的sentinel例項數。

這個配置是sentinel需要監控的master/slaver資訊,格式為sentinel monitor <mastername> <masterIP> <masterPort> <quorum>  
其中<quorum>應該小於叢集中slave的個數,當失效的節點數超過了<quorum>,則認為整個體系結構失效 

不過要注意, 無論你設定要多少個 Sentinel 同意才能判斷一個伺服器失效, 一個 Sentinel 都需要獲得系統中多數(majority) Sentinel 的支援, 才能發起一次自動故障遷移,
並預留一個給定的配置紀元 (configuration Epoch ,一個配置紀元就是一個新主伺服器配置的版本號)。
 
換句話說, 在只有少數(minority) Sentinel 程式正常運作的情況下, Sentinel 是不能執行自動故障遷移的。 
-----------------------------------------------------------------------------------------------
2)sentinel down-after-milliseconds mymaster 30000
表示master被當前sentinel例項認定為失效的間隔時間。
master在多長時間內一直沒有給Sentine返回有效資訊,則認定該master主觀下線。也就是說如果多久沒聯絡上redis-servevr,認為這個redis-server進入到失效(SDOWN)狀態。
 
如果伺服器在給定的毫秒數之內, 沒有返回 Sentinel 傳送的 PING 命令的回覆, 或者返回一個錯誤, 那麼 Sentinel 將這個伺服器標記為主觀下線(subjectively down,簡稱 SDOWN )。
不過只有一個 Sentinel 將伺服器標記為主觀下線並不一定會引起伺服器的自動故障遷移: 只有在足夠數量的 Sentinel 都將一個伺服器標記為主觀下線之後, 伺服器才會被標記為客觀下線
(objectively down, 簡稱 ODOWN ), 這時自動故障遷移才會執行。
將伺服器標記為客觀下線所需的 Sentinel 數量由對主伺服器的配置決定。
-----------------------------------------------------------------------------------------------
3)sentinel parallel-syncs mymaster 2
當在執行故障轉移時,設定幾個slave同時進行切換master,該值越大,則可能就有越多的slave在切換master時不可用,可以將該值設定為1,即一個一個來,這樣在某個
slave進行切換master同步資料時,其餘的slave還能正常工作,以此保證每次只有一個從伺服器處於不能處理命令請求的狀態。
 
parallel-syncs 選項指定了在執行故障轉移時, 最多可以有多少個從伺服器同時對新的主伺服器進行同步, 這個數字越小, 完成故障轉移所需的時間就越長。
 
如果從伺服器被設定為允許使用過期資料集(參見對 redis.conf 檔案中對 slave-serve-stale-data 選項的說明), 那麼你可能不希望所有從伺服器都在同一時間向新的主伺服器傳送同步請求,
因為儘管複製過程的絕大部分步驟都不會阻塞從伺服器, 但從伺服器在載入主伺服器發來的 RDB 檔案時, 仍然會造成從伺服器在一段時間內不能處理命令請求: 如果全部從伺服器一起對新的主
伺服器進行同步, 那麼就可能會造成所有從伺服器在短時間內全部不可用的情況出現。

當新master產生時,同時進行"slaveof"到新master並進行"SYNC"的slave個數。  
預設為1,建議保持預設值  
在salve執行salveof與同步時,將會終止客戶端請求。  
此值較大,意味著"叢集"終止客戶端請求的時間總和和較大。  
此值較小,意味著"叢集"在故障轉移期間,多個salve向客戶端提供服務時仍然使用舊資料。  
-----------------------------------------------------------------------------------------------
4)sentinel can-failover mymaster yes
在sentinel檢測到O_DOWN後,是否對這臺redis啟動failover機制
-----------------------------------------------------------------------------------------------
5)sentinel auth-pass mymaster 20180408
設定sentinel連線的master和slave的密碼,這個需要和redis.conf檔案中設定的密碼一樣
-----------------------------------------------------------------------------------------------
6)sentinel failover-timeout mymaster 180000
failover過期時間,當failover開始後,在此時間內仍然沒有觸發任何failover操作,當前sentinel將會認為此次failoer失敗。  
執行故障遷移超時時間,即在指定時間內沒有大多數的sentinel 反饋master下線,該故障遷移計劃則失效
-----------------------------------------------------------------------------------------------
7)sentinel config-epoch mymaster 0
選項指定了在執行故障轉移時, 最多可以有多少個從伺服器同時對新的主伺服器進行同步。這個數字越小, 完成故障轉移所需的時間就越長。
-----------------------------------------------------------------------------------------------
8)sentinel notification-script mymaster /var/redis/notify.sh
當failover時,可以指定一個"通知"指令碼用來告知當前叢集的情況。
指令碼被允許執行的最大時間為60秒,如果超時,指令碼將會被終止(KILL)
-----------------------------------------------------------------------------------------------
9)sentinel leader-epoch mymaster 0
同時一時間最多0個slave可同時更新配置,建議數字不要太大,以免影響正常對外提供服務。

基於以上細節知識梳理,總結出sentinel的工作原理

首先要能理解SDOWN和ODOWN這兩個詞的含義,上面已經詳細介紹了它們倆。在此再提一下:
SDOWN:subjectively down,直接翻譯的為"主觀"失效,即當前sentinel例項認為某個redis服務為"不可用"狀態.
ODOWN:objectively down,直接翻譯為"客觀"失效,即多個sentinel例項都認為master處於"SDOWN"狀態,那麼此時master將處於ODOWN,ODOWN可以簡單理解為master已經被叢集確定
為"不可用",將會開啟failover.

SDOWN適合於master和slave,但是ODOWN只會使用於master;當slave失效超過"down-after-milliseconds"後,那麼所有sentinel例項都會將其標記為"SDOWN"。

1) SDOWN與ODOWN轉換過程:
每個sentinel例項在啟動後,都會和已知的slaves/master以及其他sentinels建立TCP連線,並週期性傳送PING(預設為1秒)
在互動中,如果redis-server無法在"down-after-milliseconds"時間內響應或者響應錯誤資訊,都會被認為此redis-server處於SDOWN狀態。
如果SDOWN的server為master,那麼此時sentinel例項將會向其他sentinel間歇性(一秒)傳送"is-master-down-by-addr <ip> <port>"指令並獲取響應資訊,如果足夠多的
sentinel例項檢測到master處於SDOWN,那麼此時當前sentinel例項標記master為ODOWN...其他sentinel例項做同樣的互動操作。
配置項"sentinel monitor <mastername> <masterip> <masterport> <quorum>",如果檢測到master處於SDOWN狀態的slave個數達到<quorum>,那麼此時此sentinel例項將會認為
master處於ODOWN。每個sentinel例項將會間歇性(10秒)向master和slaves傳送"INFO"指令,如果master失效且沒有新master選出時,每1秒傳送一次"INFO";"INFO"的主要目的就是
獲取並確認當前叢集環境中slaves和master的存活情況。

經過上述過程後,所有的sentinel對master失效達成一致後,開始failover.

2) Sentinel與slaves"自動發現"機制:
在sentinel的配置檔案中(local-sentinel.conf),都指定了port,此port就是sentinel例項偵聽其他sentinel例項建立連結的埠.在叢集穩定後,最終會每個sentinel例項之間都
會建立一個tcp連結,此連結中傳送"PING"以及類似於"is-master-down-by-addr"指令集,可用用來檢測其他sentinel例項的有效性以及"ODOWN"和"failover"過程中資訊的互動.

在sentinel之間建立連線之前,sentinel將會盡力和配置檔案中指定的master建立連線.sentinel與master的連線中的通訊主要是基於pub/sub來發布和接收資訊,釋出的資訊內容包
括當前sentinel例項的偵聽埠:
+sentinel sentinel 127.0.0.1:26579 127.0.0.1 26579 ....  

釋出的主題名稱為"__sentinel__:hello";同時sentinel例項也是"訂閱"此主題,以獲得其他sentinel例項的資訊.由此可見,環境首次構建時,在預設master存活的情況下,所有的
sentinel例項可以通過pub/sub即可獲得所有的sentinel資訊,此後每個sentinel例項即可以根據+sentinel資訊中的"ip+port"和其他sentinel逐個建立tcp連線即可.不過需要提醒
的是,每個sentinel例項均會間歇性(5秒)向"__sentinel__:hello"主題中釋出自己的ip+port,目的就是讓後續加入叢集的sentinel例項也能或得到自己的資訊。
根據上文,我們知道在master有效的情況下,即可通過"INFO"指令獲得當前master中已有的slave列表;此後任何slave加入叢集,master都會向"主題中"釋出"+slave 127.0.0.1:6579 ..",
那麼所有的sentinel也將立即獲得slave資訊,並和slave建立連結並通過PING檢測其存活性.

補充一下,每個sentinel例項都會儲存其他sentinel例項的列表以及現存的master/slaves列表,各自的列表中不會有重複的資訊(不可能出現多個tcp連線),對於sentinel將使用ip+port
做唯一性標記,
對於master/slaver將使用runid做唯一性標記,其中redis-server的runid在每次啟動時都不同.

3) Leader選舉:
其實在sentinels故障轉移中,仍然需要一個"Leader"來排程整個過程:master的選舉以及slave的重配置和同步。當叢集中有多個sentinel例項時,如何選舉其中一個sentinel為leader呢?

在配置檔案中"can-failover""quorum"引數,以及"is-master-down-by-addr"指令配合來完成整個過程。
A) "can-failover"用來表明當前sentinel是否可以參與"failover"過程,如果為"YES"則表明它將有能力參與"Leader"的選舉,否則它將作為"Observer",observer參與leader選舉投票但
不能被選舉;
B) "quorum"不僅用來控制master ODOWN狀態確認,同時還用來選舉leader時最小"贊同票"數;
C) "is-master-down-by-addr",它可以用來檢測"ip + port"的master是否已經處於SDOWN狀態,不過此指令不僅能夠獲得master是否處於SDOWN,同時它還額外的返回當前sentinel
本地"投票選舉"的Leader資訊(runid);

每個sentinel例項都持有其他的sentinels資訊,在Leader選舉過程中(當為leader的sentinel例項失效時,有可能master server並沒失效,注意分開理解),sentinel例項將從所有的
sentinels集合中去除"can-failover = no"和狀態為SDOWN的sentinels,在剩餘的sentinels列表中按照runid按照"字典"順序排序後,取出runid最小的sentinel例項,並將它"投票選舉"
為Leader,並在其他sentinel傳送的"is-master-down-by-addr"指令時將推選的runid追加到響應中。每個sentinel例項都會檢測"is-master-down-by-addr"的響應結果,如果"投票選舉"的
leader為自己,且狀態正常的sentinels例項中,"贊同者"的自己的sentinel個數不小於(>=) 50% + 1,且不小與<quorum>,那麼此sentinel就會認為選舉成功且leader為自己。
在sentinel.conf檔案中,我們期望有足夠多的sentinel例項配置"can-failover yes",這樣能夠確保當leader失效時,能夠選舉某個sentinel為leader,以便進行failover。如果leader無法產生,
比如較少的sentinels例項有效,那麼failover過程將無法繼續.

4) failover過程:
在Leader觸發failover之前,首先wait數秒(隨即0~5),以便讓其他sentinel例項準備和調整(有可能多個leader??),如果一切正常,那麼leader就需要開始將一個salve提升為master,此slave
必須為狀態良好(不能處於SDOWN/ODOWN狀態)且權重值最低(redis.conf中)的,當master身份被確認後,開始failover
A)"+failover-triggered": Leader開始進行failover,此後緊跟著"+failover-state-wait-start",wait數秒。
B)"+failover-state-select-slave": Leader開始查詢合適的slave
C)"+selected-slave": 已經找到合適的slave
D) "+failover-state-sen-slaveof-noone": Leader向slave傳送"slaveof no one"指令,此時slave已經完成角色轉換,此slave即為master
E) "+failover-state-wait-promotition": 等待其他sentinel確認slave
F)"+promoted-slave":確認成功
G)"+failover-state-reconf-slaves": 開始對slaves進行reconfig操作。
H)"+slave-reconf-sent":向指定的slave傳送"slaveof"指令,告知此slave跟隨新的master
I)"+slave-reconf-inprog": 此slave正在執行slaveof + SYNC過程,如過slave收到"+slave-reconf-sent"之後將會執行slaveof操作。
J)"+slave-reconf-done": 此slave同步完成,此後leader可以繼續下一個slave的reconfig操作。迴圈G)
K)"+failover-end": 故障轉移結束
L)"+switch-master":故障轉移成功後,各個sentinel例項開始監控新的master。

二、redis sentinel 主從切換(failover)的容災環境部署記錄

redis主從複製簡單來說:
A)Redis的複製功能是支援多個資料庫之間的資料同步。一類是主資料庫(master)一類是從資料庫(slave),主資料庫可以進行讀寫操作,當發生寫操作的時候自動將資料同步到從資料庫,而從資料庫一般是隻讀的,並接收主資料庫同步過來的資料,一個主資料庫可以有多個從資料庫,而一個從資料庫只能有一個主資料庫。
B)通過redis的複製功能可以很好的實現資料庫的讀寫分離,提高伺服器的負載能力。主資料庫主要進行寫操作,而從資料庫負責讀操作。

Redis主從複製流程簡圖

redis主從複製的大致過程:
1)當一個從資料庫啟動時,會向主資料庫傳送sync命令,
2)主資料庫接收到sync命令後會開始在後臺儲存快照(執行rdb操作),並將儲存期間接收到的命令快取起來
3)當快照完成後,redis會將快照檔案和所有快取的命令傳送給從資料庫。
4)從資料庫收到後,會載入快照檔案並執行收到的快取的命令。

注意:redis2.8之前的版本:當主從資料庫同步的時候從資料庫因為網路原因斷開重連後會重新執行上述操作,不支援斷點續傳。redis2.8之後支援斷點續傳。

0)Redis主從結構支援一主多從+n個sentinel模式,資訊如下:

192.168.10.202   redis-master    redis(6379)、sentinel(26379)
192.168.10.203   redis-slave01   redis(6379)、sentinel(26379)
192.168.10.205   redis-slave02   redis(6379)、sentinel(26379)  
 
關閉三個節點機器的iptables和selinux(所有節點機器上都要操作)
[root@redis-master ~]# /etc/init.d/iptables stop
[root@redis-master ~]# vim /etc/sysconfig/selinux
......
SELINUX=disabled
[root@redis-master ~]# setenforce 0
[root@redis-master ~]# getenforce
Permissive
 
注意:本案例採用1主2從+3 sentinel的叢集模式,所有從節點的配置都一樣。

a)redis伺服器上各自存在一個Sentinel,監控本機redis的執行情況,並通知給閉路環上其它的redis節點;
b)當master發生異常(例如:當機和斷電等)導致不可執行時,Sentinel將通知給其它節點,而剩餘節點上的Sentinel將重新選舉出新的master,而原來的master重新恢復正常後,則一直扮演slave角色;
c)規定整個架構體系中,master提供讀寫服務,而slave只提供讀取服務。

1)redis一鍵安裝(三個節點上都要操作)

[root@redis-master ~]# cd /usr/local/src/
[root@redis-master src]# vim install_redis.sh 
#!/usr/bin/env bash
# It's Used to be install redis.
# Created on 2018/04/08 11:18.
# @author: wangshibo.
# Version: 1.0
  
function install_redis () {
#################################################################################################
        cd /usr/local/src
        if [ ! -f " redis-4.0.1.tar.gz" ]; then
           wget http://download.redis.io/releases/redis-4.0.1.tar.gz
        fi
        cd /usr/local/src
        tar -zxvf /usr/local/src/redis-4.0.1.tar.gz
        cd redis-4.0.1
        make PREFIX=/usr/local/redis install
        mkdir -p /usr/local/redis/{etc,var}
        rsync -avz redis.conf  /usr/local/redis/etc/
        sed -i 's@pidfile.*@pidfile /var/run/redis-server.pid@' /usr/local/redis/etc/redis.conf
        sed -i "s@logfile.*@logfile /usr/local/redis/var/redis.log@" /usr/local/redis/etc/redis.conf
        sed -i "s@^dir.*@dir /usr/local/redis/var@" /usr/local/redis/etc/redis.conf
        sed -i 's/daemonize no/daemonize yes/g' /usr/local/redis/etc/redis.conf
        sed -i 's/^# bind 127.0.0.1/bind 0.0.0.0/g' /usr/local/redis/etc/redis.conf
 #################################################################################################
}
  
install_redis
[root@redis-master src]# chmod 755 install_redis.sh
[root@redis-master src]# sh -x install_redis.sh

2)redis啟停指令碼(三個節點上都要操作)

[root@redis-master src]# vim /etc/init.d/redis-server
#!/bin/bash
#
# redis - this script starts and stops the redis-server daemon
#
# chkconfig:   - 85 15
# description:  Redis is a persistent key-value database
# processname: redis-server
# config:      /usr/local/redis/etc/redis.conf
# config:      /etc/sysconfig/redis
# pidfile:     /usr/local/redis/var/redis-server.pid
 
# Source function library.
. /etc/rc.d/init.d/functions
 
# Source networking configuration.
. /etc/sysconfig/network
 
# Check that networking is up.
[ "$NETWORKING" = "no" ] && exit 0
 
redis="/usr/local/redis/bin/redis-server"
prog=$(basename $redis)
 
REDIS_CONF_FILE="/usr/local/redis/etc/redis.conf"
 
[ -f /etc/sysconfig/redis ] && . /etc/sysconfig/redis
 
lockfile=/var/lock/subsys/redis-server
 
start() {
    [ -x $redis ] || exit 5
    [ -f $REDIS_CONF_FILE ] || exit 6
    echo -n $"Starting $prog: "
    daemon $redis $REDIS_CONF_FILE
    retval=$?
    echo
    [ $retval -eq 0 ] && touch $lockfile
    return $retval
}
 
stop() {
    echo -n $"Stopping $prog: "
    killproc $prog
    retval=$?
    echo
    [ $retval -eq 0 ] && rm -f $lockfile
    return $retval
}
 
restart() {
    stop
    start
}
 
reload() {
    echo -n $"Reloading $prog: "
    killproc $redis -HUP
    RETVAL=$?
    echo
}
 
force_reload() {
    restart
}
 
rh_status() {
    status $prog
}
 
rh_status_q() {
    rh_status >/dev/null 2>&1
}
 
case "$1" in
    start)
        rh_status_q && exit 0
        $1
        ;;
    stop)
        rh_status_q || exit 0
        $1
        ;;
    restart)
        $1
        ;;
    reload)
        rh_status_q || exit 7
        $1
        ;;
    force-reload)
        force_reload
        ;;
    status)
        rh_status
        ;;
    condrestart|try-restart)
        rh_status_q || exit 0
            ;;
    *)
        echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}"
        exit 2
esac

執行許可權
[root@redis-master src]# chmod 755 /etc/init.d/redis-server

3)redis-sentinel啟停指令碼示例(三個節點上都要操作)

[root@redis-master src]# vim /etc/init.d/redis-sentinel
#!/bin/bash
#
# redis-sentinel - this script starts and stops the redis-server sentinel daemon
#
# chkconfig:   - 85 15
# description:  Redis sentinel
# processname: redis-server
# config:      /usr/local/redis/etc/sentinel.conf
# config:      /etc/sysconfig/redis
# pidfile:     /usr/local/redis/var/redis-sentinel.pid
 
# Source function library.
. /etc/rc.d/init.d/functions
 
# Source networking configuration.
. /etc/sysconfig/network
 
# Check that networking is up.
[ "$NETWORKING" = "no" ] && exit 0
 
redis="/usr/local/redis/bin/redis-sentinel"
prog=$(basename $redis)
 
REDIS_CONF_FILE="/usr/local/redis/etc/sentinel.conf"
 
[ -f /etc/sysconfig/redis ] && . /etc/sysconfig/redis
 
lockfile=/var/lock/subsys/redis-sentinel
 
start() {
    [ -x $redis ] || exit 5
    [ -f $REDIS_CONF_FILE ] || exit 6
    echo -n $"Starting $prog: "
    daemon $redis $REDIS_CONF_FILE --sentinel
    retval=$?
    echo
    [ $retval -eq 0 ] && touch $lockfile
    return $retval
}
 
stop() {
    echo -n $"Stopping $prog: "
    killproc $prog
    retval=$?
    echo
    [ $retval -eq 0 ] && rm -f $lockfile
    return $retval
}
 
restart() {
    stop
    start
}
 
reload() {
    echo -n $"Reloading $prog: "
    killproc $redis -HUP
    RETVAL=$?
    echo
}
 
force_reload() {
    restart
}
 
rh_status() {
    status $prog
}
 
rh_status_q() {
    rh_status >/dev/null 2>&1
}
 
case "$1" in
    start)
        rh_status_q && exit 0
        $1
        ;;
    stop)
        rh_status_q || exit 0
        $1
        ;;
    restart)
        $1
        ;;
    reload)
        rh_status_q || exit 7
        $1
        ;;
    force-reload)
        force_reload
        ;;
    status)
        rh_status
        ;;
    condrestart|try-restart)
        rh_status_q || exit 0
            ;;
    *)
        echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}"
        exit 2
esac

執行許可權:
[root@redis-master src]# chmod 755 /etc/init.d/redis-sentinel

4)配置redis.conf

a)編輯redis-master主節點的redis.conf檔案
[root@redis-master src]# mkdir -p /usr/local/redis/data/redis
[root@redis-master src]# cp /usr/local/redis/etc/redis.conf /usr/local/redis/etc/redis.conf.bak
[root@redis-master src]# vim /usr/local/redis/etc/redis.conf
bind 0.0.0.0
daemonize yes
pidfile "/usr/local/redis/var/redis-server.pid"
port 6379
tcp-backlog 128
timeout 0
tcp-keepalive 0
loglevel notice
logfile "/usr/local/redis/var/redis-server.log"
databases 16
save 900 1   
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir "/usr/local/redis/data/redis"
#masterauth "20180408"                        #master設定密碼保護,即slave連線master時的密碼
#requirepass "20180408"                       #設定Redis連線密碼,如果配置了連線密碼,客戶端在連線Redis時需要通過AUTH <password>命令提供密碼,預設關閉
slave-serve-stale-data yes
slave-read-only yes
repl-diskless-sync no
repl-diskless-sync-delay 5
repl-disable-tcp-nodelay no
slave-priority 100
appendonly yes                                #開啟aof持久化
appendfilename "appendonly.aof"
appendfsync everysec                          # 每秒一次aof寫
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
lua-time-limit 5000
slowlog-log-slower-than 10000
slowlog-max-len 128
latency-monitor-threshold 0
notify-keyspace-events ""
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-entries 512
list-max-ziplist-value 64
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
aof-rewrite-incremental-fsync yes

注意:
上面配置中masterauth和requirepass表示設定密碼保護,如果設定了密碼,則連線redis後需要執行"auth 20180408"密碼後才能操作其他命令。這裡我不設定密碼。

b)編輯redis-slave01和redis-slave02兩個從節點的redis.conf檔案
[root@redis-slave01 src]# mkdir -p /usr/local/redis/data/redis
[root@redis-slave01 src]# cp /usr/local/redis/etc/redis.conf /usr/local/redis/etc/redis.conf.bak
[root@redis-slave01 src]# vim /usr/local/redis/etc/redis.conf
bind 0.0.0.0
daemonize yes
pidfile "/usr/local/redis/var/redis-server.pid"
port 6379
tcp-backlog 128
timeout 0
tcp-keepalive 0
loglevel notice
logfile "/usr/local/redis/var/redis-server.log"
databases 16
save 900 1   
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir "/usr/local/redis/data/redis"
#masterauth "20180408"                
#requirepass "20180408"       
slaveof 192.168.10.202 6379                  #相對主redis配置,多新增了此行        
slave-serve-stale-data yes
slave-read-only yes                          #從節點只讀,不能寫入
repl-diskless-sync no
repl-diskless-sync-delay 5
repl-disable-tcp-nodelay no
slave-priority 100
appendonly yes                            
appendfilename "appendonly.aof"
appendfsync everysec                         
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
lua-time-limit 5000
slowlog-log-slower-than 10000
slowlog-max-len 128
latency-monitor-threshold 0
notify-keyspace-events ""
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-entries 512
list-max-ziplist-value 64
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
aof-rewrite-incremental-fsync yes

5)配置sentinel.conf(這個預設沒有,需要自建)。三個節點的配置一樣。

[root@redis-master src]# mkdir -p /usr/local/redis/data/sentinel
[root@redis-master src]# vim /usr/local/redis/etc/sentinel.conf
port 26379
pidfile "/usr/local/redis/var/redis-sentinel.pid"
dir "/usr/local/redis/data/sentinel"
daemonize yes
protected-mode no
logfile "/usr/local/redis/var/redis-sentinel.log"
sentinel monitor redisMaster 192.168.10.202 6379 2  
sentinel down-after-milliseconds redisMaster 10000  
sentinel parallel-syncs redisMaster 1 
sentinel failover-timeout redisMaster 60000  

6)啟動redis和sentinel(三個節點都要操作)

設定系統變數
[root@redis-slave02 src]# vim /etc/profile
.......
export PATH=$PATH:/usr/local/redis/bin
[root@redis-slave02 src]# source /etc/profile

啟動redis和sentinel
[root@redis-master src]# /etc/init.d/redis-server start
Starting redis-server:                                     [  OK  ]
[root@redis-master src]# /etc/init.d/redis-sentinel start
Starting redis-sentinel:                                   [  OK  ]
[root@redis-master src]# lsof -i:6379
COMMAND    PID USER   FD   TYPE  DEVICE SIZE/OFF NODE NAME
redis-ser 2297 root    6u  IPv4 7819726      0t0  TCP *:6379 (LISTEN)
redis-ser 2297 root    8u  IPv4 7819778      0t0  TCP 192.168.10.202:6379->192.168.10.202:56226 (ESTABLISHED)
redis-ser 2297 root    9u  IPv4 7819780      0t0  TCP 192.168.10.202:6379->192.168.10.202:56228 (ESTABLISHED)
redis-sen 2315 root    8u  IPv4 7819777      0t0  TCP 192.168.10.202:56226->192.168.10.202:6379 (ESTABLISHED)
redis-sen 2315 root    9u  IPv4 7819779      0t0  TCP 192.168.10.202:56228->192.168.10.202:6379 (ESTABLISHED)
[root@redis-master src]# lsof -i:26379
COMMAND    PID USER   FD   TYPE  DEVICE SIZE/OFF NODE NAME
redis-sen 2315 root    6u  IPv6 7819772      0t0  TCP *:26379 (LISTEN)
redis-sen 2315 root    7u  IPv4 7819773      0t0  TCP *:26379 (LISTEN)

7)檢視redis和sentinel資訊

1)檢視三個節點的redis的主從關係
[root@redis-master src]# redis-cli -h 192.168.10.202 -p 6379 INFO|grep role
role:master
[root@redis-master src]# redis-cli -h 192.168.10.203 -p 6379 INFO|grep role
role:slave
[root@redis-master src]# redis-cli -h 192.168.10.205 -p 6379 INFO|grep role
role:slave
 
從上面資訊可以看出,192.168.10.202是master,192.168.10.203和192.168.10.205是slave
 
2)檢視Master節點資訊:
[root@redis-master src]# redis-cli -h 192.168.10.202 -p 6379 info Replication
# Replication
role:master
connected_slaves:2
slave0:ip=192.168.10.203,port=6379,state=online,offset=61480,lag=0
slave1:ip=192.168.10.205,port=6379,state=online,offset=61480,lag=0
master_replid:96a1fd63d0ad9e7903851a82382e32d690667bcc
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:61626
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:61626
 
從上面資訊看出,此時192.168.10.202的角色為master,有兩個slave(203和205)被連線成功.
 
此時開啟master的sentinel.conf,在末尾可看到如下自動寫入的內容:
[root@redis-master src]# cat /usr/local/redis/etc/sentinel.conf
port 26379
pidfile "/usr/local/redis/var/redis-sentinel.pid"
dir "/usr/local/redis/data/sentinel"
daemonize yes
protected-mode no
logfile "/usr/local/redis/var/redis-sentinel.log"
sentinel myid c165761901b5ea3cd2d622bbf13f4c99eb73c1bc
sentinel monitor redisMaster 192.168.10.202 6379 2
sentinel down-after-milliseconds redisMaster 10000
sentinel failover-timeout redisMaster 60000
# Generated by CONFIG REWRITE
sentinel config-epoch redisMaster 0
sentinel leader-epoch redisMaster 0
sentinel known-slave redisMaster 192.168.10.203 6379
sentinel known-slave redisMaster 192.168.10.205 6379
sentinel known-sentinel redisMaster 192.168.10.205 26379 cc25d5f0e37803e888732d63deae3761c9f91e1d
sentinel known-sentinel redisMaster 192.168.10.203 26379 e1505ffc65f787871febfde2f27b762f70cddd71
sentinel current-epoch 0
 
[root@redis-master ~]# redis-cli -h 192.168.10.205 -p 26379 info Sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=redisMaster,status=ok,address=192.168.10.202:6379,slaves=2,sentinels=3

3)檢視Slave節點資訊:
 
先檢視salve01節點資訊
[root@redis-slave01 src]# redis-cli -h 192.168.10.203 -p 6379 info Replication
# Replication
role:slave
master_host:192.168.10.202
master_port:6379
master_link_status:up
master_last_io_seconds_ago:0
master_sync_in_progress:0
slave_repl_offset:96744
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:96a1fd63d0ad9e7903851a82382e32d690667bcc
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:96744
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:96744
 
此時192.168.10.203的角色為slave,它們所屬的master為220。
此時開啟slave的sentinel.conf,在末尾可看到如下自動寫入的內容:
[root@redis-slave01 src]# cat /usr/local/redis/etc/sentinel.conf
port 26379
pidfile "/usr/local/redis/var/redis-sentinel.pid"
dir "/usr/local/redis/data/sentinel"
daemonize yes
protected-mode no
logfile "/usr/local/redis/var/redis-sentinel.log"
sentinel myid e1505ffc65f787871febfde2f27b762f70cddd71
sentinel monitor redisMaster 192.168.10.202 6379 2
sentinel down-after-milliseconds redisMaster 10000
sentinel failover-timeout redisMaster 60000
# Generated by CONFIG REWRITE
sentinel config-epoch redisMaster 0
sentinel leader-epoch redisMaster 0
sentinel known-slave redisMaster 192.168.10.203 6379
sentinel known-slave redisMaster 192.168.10.205 6379
sentinel known-sentinel redisMaster 192.168.10.205 26379 cc25d5f0e37803e888732d63deae3761c9f91e1d
sentinel known-sentinel redisMaster 192.168.10.202 26379 c165761901b5ea3cd2d622bbf13f4c99eb73c1bc
sentinel current-epoch 0

[root@redis-slave01 ~]# redis-cli -h 192.168.10.203 -p 26379 info Sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=redisMaster,status=ok,address=192.168.10.202:6379,slaves=2,sentinels=3

同樣檢視slave02節點的資訊
[root@redis-slave02 src]# redis-cli -h 192.168.10.205 -p 6379 info Replication
# Replication
role:slave
master_host:192.168.10.202
master_port:6379
master_link_status:up
master_last_io_seconds_ago:0
master_sync_in_progress:0
slave_repl_offset:99678
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:96a1fd63d0ad9e7903851a82382e32d690667bcc
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:99678
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:170
repl_backlog_histlen:99509
 
[root@redis-slave02 src]# cat /usr/local/redis/etc/sentinel.conf
port 26379
pidfile "/usr/local/redis/var/redis-sentinel.pid"
dir "/usr/local/redis/data/sentinel"
daemonize yes
protected-mode no
logfile "/usr/local/redis/var/redis-sentinel.log"
sentinel myid cc25d5f0e37803e888732d63deae3761c9f91e1d
sentinel monitor redisMaster 192.168.10.202 6379 2
sentinel down-after-milliseconds redisMaster 10000
sentinel failover-timeout redisMaster 60000
# Generated by CONFIG REWRITE
sentinel config-epoch redisMaster 0
sentinel leader-epoch redisMaster 0
sentinel known-slave redisMaster 192.168.10.205 6379
sentinel known-slave redisMaster 192.168.10.203 6379
sentinel known-sentinel redisMaster 192.168.10.203 26379 e1505ffc65f787871febfde2f27b762f70cddd71
sentinel known-sentinel redisMaster 192.168.10.202 26379 c165761901b5ea3cd2d622bbf13f4c99eb73c1bc
sentinel current-epoch 0

[root@redis-slave02 ~]# redis-cli -h 192.168.10.205 -p 26379 info Sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=redisMaster,status=ok,address=192.168.10.202:6379,slaves=2,sentinels=3

8)客戶端寫入測試資料

客戶端連線master節點,寫入一條資料
[root@redis-master src]# redis-cli  -h 192.168.10.202 -p 6379
192.168.10.202:6379> set name kevin;
OK
192.168.10.202:6379> get name;
"kevin";

然後客戶端再連線任意slave節點,通過get獲取上面的那條資料
[root@redis-master src]# redis-cli  -h 192.168.10.203 -p 6379
192.168.10.203:6379> get name
"kevin;"
192.168.10.203:6379> set name grace;
(error) READONLY You can't write against a read only slave.
192.168.10.203:6379> 

[root@redis-master src]# redis-cli  -h 192.168.10.205 -p 6379
192.168.10.205:6379> get name
"kevin;"
192.168.10.205:6379> set name grace;
(error) READONLY You can't write against a read only slave.
192.168.10.205:6379> 

由上面測試資訊可知,master節點可以寫入,可以讀取;而slave節點預設只能讀取,不能寫入!這就實現了主從複製,讀寫分離了!

9)模擬故障(通過sentinel實現主從切換,sentinel也要部署多臺,即叢集模式,防止單臺sentinel掛掉情況)

1)關掉任意一個slave節點(比如關閉掉slave01節點),所有節點的sentinel都可以檢測到,出現如下示例資訊:
[root@redis-master src]# redis-cli  -h 192.168.10.203 -p 6379
192.168.10.203:6379> get name
"kevin;"
192.168.10.203:6379> set name grace;
(error) READONLY You can't write against a read only slave.
192.168.10.203:6379> shutdown                              
not connected> 

說明:shutdown命令表示關閉redis

從上可看出203被sentinel檢測到已處於關閉狀態,此時再來檢視剩餘節點的主從資訊,它們的角色不會發生變化,只是master上的connected_slaves變為了1。
[root@redis-master src]# redis-cli  -h 192.168.10.202 -p 6379 info replication
# Replication
role:master
connected_slaves:1
slave0:ip=192.168.10.205,port=6379,state=online,offset=219376,lag=1
master_replid:96a1fd63d0ad9e7903851a82382e32d690667bcc
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:219376
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:219376

檢視sentinel日誌(任意節點上檢視),發現203節點已經進入"+sdown"狀態
[root@redis-master src]# tail -f /usr/local/redis/var/redis-sentinel.log 
2315:X 08 May 18:49:51.429 * Increased maximum number of open files to 10032 (it was originally set to 1024).
2315:X 08 May 18:49:51.431 * Running mode=sentinel, port=26379.
2315:X 08 May 18:49:51.431 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
2315:X 08 May 18:49:51.463 # Sentinel ID is c165761901b5ea3cd2d622bbf13f4c99eb73c1bc
2315:X 08 May 18:49:51.463 # +monitor master redisMaster 192.168.10.202 6379 quorum 2
2315:X 08 May 18:50:11.544 * +slave slave 192.168.10.203:6379 192.168.10.203 6379 @ redisMaster 192.168.10.202 6379
2315:X 08 May 18:50:11.574 * +slave slave 192.168.10.205:6379 192.168.10.205 6379 @ redisMaster 192.168.10.202 6379
2315:X 08 May 18:50:15.088 * +sentinel sentinel e1505ffc65f787871febfde2f27b762f70cddd71 192.168.10.203 26379 @ redisMaster 192.168.10.202 6379
2315:X 08 May 18:50:16.075 * +sentinel sentinel cc25d5f0e37803e888732d63deae3761c9f91e1d 192.168.10.205 26379 @ redisMaster 192.168.10.202 6379
2315:X 08 May 19:06:07.669 # +sdown slave 192.168.10.203:6379 192.168.10.203 6379 @ redisMaster 192.168.10.202 6379

然後重啟上面被關閉的slave節點(即192.168.10.203),所有節點的sentinel都可以檢測到,可看出221又被sentinel檢測到已處於可用狀態,此時再來檢視節點的主從資訊,
它們的角色仍然不會發生變化,master上的connected_slaves又變為了2
[root@redis-slave01 src]# /etc/init.d/redis-server restart
Stopping redis-server:                                     [FAILED]
Starting redis-server:                                     [  OK  ]
[root@redis-slave01 src]# /etc/init.d/redis-server restart
Stopping redis-server:                                     [  OK  ]
Starting redis-server:                                     [  OK  ]

[root@redis-master src]# redis-cli  -h 192.168.10.202 -p 6379 info replication
# Replication
role:master
connected_slaves:2
slave0:ip=192.168.10.205,port=6379,state=online,offset=268216,lag=0
slave1:ip=192.168.10.203,port=6379,state=online,offset=268070,lag=1
master_replid:96a1fd63d0ad9e7903851a82382e32d690667bcc
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:268216
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:268216

檢視sentinel日誌(任意節點上檢視),發現203節點已經進入"-sdown"狀態
[root@redis-master src]# tail -f /usr/local/redis/var/redis-sentinel.log 
2315:X 08 May 18:49:51.429 * Increased maximum number of open files to 10032 (it was originally set to 1024).
2315:X 08 May 18:49:51.431 * Running mode=sentinel, port=26379.
2315:X 08 May 18:49:51.431 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
2315:X 08 May 18:49:51.463 # Sentinel ID is c165761901b5ea3cd2d622bbf13f4c99eb73c1bc
2315:X 08 May 18:49:51.463 # +monitor master redisMaster 192.168.10.202 6379 quorum 2
2315:X 08 May 18:50:11.544 * +slave slave 192.168.10.203:6379 192.168.10.203 6379 @ redisMaster 192.168.10.202 6379
2315:X 08 May 18:50:11.574 * +slave slave 192.168.10.205:6379 192.168.10.205 6379 @ redisMaster 192.168.10.202 6379
2315:X 08 May 18:50:15.088 * +sentinel sentinel e1505ffc65f787871febfde2f27b762f70cddd71 192.168.10.203 26379 @ redisMaster 192.168.10.202 6379
2315:X 08 May 18:50:16.075 * +sentinel sentinel cc25d5f0e37803e888732d63deae3761c9f91e1d 192.168.10.205 26379 @ redisMaster 192.168.10.202 6379
2315:X 08 May 19:06:07.669 # +sdown slave 192.168.10.203:6379 192.168.10.203 6379 @ redisMaster 192.168.10.202 6379
2315:X 08 May 19:10:14.965 * +reboot slave 192.168.10.203:6379 192.168.10.203 6379 @ redisMaster 192.168.10.202 6379
2315:X 08 May 19:10:15.020 # -sdown slave 192.168.10.203:6379 192.168.10.203 6379 @ redisMaster 192.168.10.202 6379

=======================================================================================================
2)關掉master節點(即192.168.10.202),待所有節點的sentinel都檢測到後(稍等一會,2-3秒鐘時間),再來檢視兩個Slave節點的主從資訊,發現其中一個節點的角色通過選舉後會成為
master節點了!
[root@redis-master src]# redis-cli  -h 192.168.10.202 -p 6379
192.168.10.202:6379> shutdown
not connected> 

檢視sentinel日誌(任意節點上檢視),發現202節點已經進入"+sdown"狀態
[root@redis-master src]# tail -f /usr/local/redis/var/redis-sentinel.log
2315:X 08 May 19:17:03.722 # +failover-state-reconf-slaves master redisMaster 192.168.10.202 6379
2315:X 08 May 19:17:03.760 * +slave-reconf-sent slave 192.168.10.203:6379 192.168.10.203 6379 @ redisMaster 192.168.10.202 6379
2315:X 08 May 19:17:04.015 * +slave-reconf-inprog slave 192.168.10.203:6379 192.168.10.203 6379 @ redisMaster 192.168.10.202 6379
2315:X 08 May 19:17:04.459 # -odown master redisMaster 192.168.10.202 6379
2315:X 08 May 19:17:05.047 * +slave-reconf-done slave 192.168.10.203:6379 192.168.10.203 6379 @ redisMaster 192.168.10.202 6379
2315:X 08 May 19:17:05.131 # +failover-end master redisMaster 192.168.10.202 6379
2315:X 08 May 19:17:05.131 # +switch-master redisMaster 192.168.10.202 6379 192.168.10.205 6379
2315:X 08 May 19:17:05.131 * +slave slave 192.168.10.203:6379 192.168.10.203 6379 @ redisMaster 192.168.10.205 6379
2315:X 08 May 19:17:05.131 * +slave slave 192.168.10.202:6379 192.168.10.202 6379 @ redisMaster 192.168.10.205 6379
2315:X 08 May 19:17:15.170 # +sdown slave 192.168.10.202:6379 192.168.10.202 6379 @ redisMaster 192.168.10.205 6379

[root@redis-slave01 src]# redis-cli -h 192.168.10.203 -p 6379 INFO|grep role
role:slave
[root@redis-slave01 src]# redis-cli -h 192.168.10.205 -p 6379 INFO|grep role      
role:master

[root@redis-slave01 src]# redis-cli  -h 192.168.10.205 -p 6379 info replication
# Replication
role:master
connected_slaves:1
slave0:ip=192.168.10.203,port=6379,state=online,offset=348860,lag=1
master_replid:a51f87958cea0b1e8fe2c83542ca6cebace53bf7
master_replid2:96a1fd63d0ad9e7903851a82382e32d690667bcc
master_repl_offset:349152
second_repl_offset:342824
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:170
repl_backlog_histlen:348983

[root@redis-slave01 src]# redis-cli  -h 192.168.10.203 -p 6379 info replication
# Replication
role:slave
master_host:192.168.10.205
master_port:6379
master_link_status:up
master_last_io_seconds_ago:0
master_sync_in_progress:0
slave_repl_offset:347546
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:a51f87958cea0b1e8fe2c83542ca6cebace53bf7
master_replid2:96a1fd63d0ad9e7903851a82382e32d690667bcc
master_repl_offset:347546
second_repl_offset:342824
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:283207
repl_backlog_histlen:64340

由上可知,當master節點(即192.168.10.202)的redis關閉後,slave02節點(即192.168.10.205)變成了新的master節點,而slave01(即192.168.10.203)成為
了slave02的從節點!

192.168.10.205節點此時被選舉為master,此時開啟的205節點的redis.conf檔案,slaveof配置項已被自動刪除了。
而203從節點的redis.conf檔案中slaveof配置項的值被自動修改為192.168.10.205 6379。
[root@redis-slave02 src]# cat /usr/local/redis/etc/redis.conf|grep slaveof
[root@redis-slave01 src]# cat /usr/local/redis/etc/redis.conf|grep slaveof
slaveof 192.168.10.205 6379

在這個新master上(即192.168.10.205)執行諸如set這樣的寫入操作將被成功執行
[root@redis-slave01 src]# redis-cli  -h 192.168.10.205 -p 6379
192.168.10.205:6379> set name beijing;
OK

[root@redis-master src]# redis-cli  -h 192.168.10.203 -p 6379
192.168.10.203:6379> get name
"beijing;"
192.168.10.203:6379> set name tianjin;
(error) READONLY You can't write against a read only slave.
192.168.10.203:6379> 

重啟192.168.10.202節點的redis,待所有節點的sentinel都檢測到後,再來檢視所有節點的主從資訊,此時192.168.10.205節點的master角色不會被重新搶佔,
而192.168.10.202節點的角色會從原來的master變為了slave。
[root@redis-master src]# /etc/init.d/redis-server restart
Stopping redis-server:                                     [FAILED]
Starting redis-server:                                     [  OK  ]
[root@redis-master src]# /etc/init.d/redis-server restart
Stopping redis-server:                                     [  OK  ]
Starting redis-server:                                     [  OK  ]

[root@redis-master src]# redis-cli -h 192.168.10.202 -p 6379 INFO|grep role
role:slave
[root@redis-master src]# redis-cli -h 192.168.10.203 -p 6379 INFO|grep role
role:slave
[root@redis-master src]# redis-cli -h 192.168.10.205 -p 6379 INFO|grep role
role:master
[root@redis-master src]# redis-cli  -h 192.168.10.205 -p 6379 info replication
# Replication
role:master
connected_slaves:2
slave0:ip=192.168.10.203,port=6379,state=online,offset=545410,lag=1
slave1:ip=192.168.10.202,port=6379,state=online,offset=545410,lag=1
master_replid:a51f87958cea0b1e8fe2c83542ca6cebace53bf7
master_replid2:96a1fd63d0ad9e7903851a82382e32d690667bcc
master_repl_offset:545556
second_repl_offset:342824
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:170
repl_backlog_histlen:545387

[root@redis-master src]# redis-cli  -h 192.168.10.202 -p 6379
192.168.10.202:6379> get name
"beijing;"

此時登入192.168.10.202節點的redis,執行"get name"得到的值為beijing,而不是原來的kevin,因為192.168.10.202節點的redis重啟後會自動從新的master中同步
資料。此時開啟192.168.10.202節點的redis.conf檔案,會在末尾找到如下資訊:
[root@redis-master src]# cat /usr/local/redis/etc/redis.conf|grep slaveof
slaveof 192.168.10.205 6379

到此,已經驗證出了redis sentinel可以自行實現主從的故障切換了!

10)客戶端如何連線redis sentinel?

客戶端配置連線的是sentinel資訊,比如連線sentinel.conf檔案中定義的master名稱。在sentinel監聽時,當master節點掛了,它會在slave節點中自動選舉出新
的master節點,而當掛了的老master節點重新恢復後就會成為新的slave節點。對於客戶端來說,redis主從切換後它不需要修改連線配置。

下面列出幾個客戶端連線redis sentinel的例子

1)java客戶端在jedis中使用redis sentinel哨兵方式連線
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans">
    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
         <property name="maxTotal" value="1000"/>
         <property name="maxIdle" value="10"/>
         <property name="minIdle" value="1"/>
         <property name="maxWaitMillis" value="30000"/>
         <property name="testOnBorrow" value="true"/>
         <property name="testOnReturn" value="true"/>
         <property name="testWhileIdle" value="true"/>
    </bean>

    <bean id="cacheService" class="sentinel.CacheServiceImpl" destroy-method="destroy">
        <property name="jedisSentinlePool">
            <bean class="redis.clients.jedis.JedisSentinelPool">
                 <constructor-arg index="0" value="mymaster" />
                 <constructor-arg index="1">
                     <set>
                         <value>192.168.10.202:26379</value>
                         <value>192.168.10.203:26379</value>
                         <value>192.168.10.205:26379</value>
                     </set>
                 </constructor-arg>
                 <constructor-arg index="2" ref="jedisPoolConfig" />
            </bean>
        </property>
    </bean>
</beans>

2)python連線redis sentinel叢集(需要安裝python redis客戶端,即執行"pip install redis")
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import redis
from redis.sentinel import Sentinel

# 連線哨兵伺服器(主機名也可以用域名)
sentinel = Sentinel([('192.168.10.202', 26379),
                     ('192.168.10.203', 26379),
                     ('192.168.10.205', 26379)
             ],
                    socket_timeout=0.5)

# 獲取主伺服器地址
master = sentinel.discover_master('mymaster')
print(master)
# 輸出:('192.168.10.202', 26379)

# 獲取從伺服器地址
slave = sentinel.discover_slaves('mymaster')
print(slave)
# 輸出:[('192.168.10.203', 26379), ('192.168.10.205', 26379), ('172.31.0.5', 26379)]

# 獲取主伺服器進行寫入
master = sentinel.master_for('mymaster', socket_timeout=0.5, password='redis_auth_pass', db=15)
w_ret = master.set('foo', 'bar')
# 輸出:True

# # 獲取從伺服器進行讀取(預設是round-roubin)
slave = sentinel.slave_for('mymaster', socket_timeout=0.5, password='redis_auth_pass', db=15)
r_ret = slave.get('foo')
print(r_ret)
# # 輸出:bar

3)Java客戶端連線Redis(單sentinel).
下面例子中好的redisMaster和20180408是在sentinel.conf中定義的master名稱和連線密碼
package com.hiifit.cloudplatform.gaia.test;
import java.util.HashSet;
import java.util.Set;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisSentinelPool;

public class RedisSentinelTest {

    @SuppressWarnings("deprecation")
    public static void main(String[] args) {

        Set<String> sentinels = new HashSet<String>();
        String hostAndPort1 = "192.168.10.205:26379";
        sentinels.add(hostAndPort1);

        String clusterName = "redisMaster";
        String password = "20180408";

        JedisSentinelPool redisSentinelJedisPool = new JedisSentinelPool(clusterName,sentinels,password);

        Jedis jedis = null;
        try {
            jedis = redisSentinelJedisPool.getResource();
            jedis.set("key", "value");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            redisSentinelJedisPool.returnBrokenResource(jedis);
        }

        redisSentinelJedisPool.close();
    }

}

相關文章