概述
為了實現redis集群的高可用,redis經歷了好幾次迭代,從最開始的主從模式,到哨兵模式,再到現在的集群模式,可以說架構的優化越來越好,那本篇文章就介紹一下redis的哨兵模式,不過我司其實使用的是cluster模式,這里就當學習一下。
redis哨兵模式架構
聲明:本圖來源Redis ==> 集群的三種模式
要解釋什么是哨兵模式,要從redis的主從模式說起,redis的主從模式就是把上圖的所有的哨兵去掉,就變成了主從模式,主從模式是主節點負責寫請求,然后異步的同步給從節點,然后從節點負責讀請求,所以在主從架構中redis每個節點保存的數據是相同的,只是數據的同步可能會有一點延遲,那考慮到高可用性,如果主節點掛了,是沒有一個自動選主的機制的,需要人工來指定一個節點為主節點,然后再恢復成主從結構,所以其實也是不能做到高可用。
為了解決主從模式不能高可用的問題,就發明了哨兵模式,所謂的哨兵模式,就是在原來的主從架構的基礎上,又搞了一個集群,哨兵集群,這個集群會監控redis集群的主節點和從節點的狀態,如果發現主節點掛了,就會重新在從節點中選出來一個作為主節點,從而做到高可用。
那哨兵本身是一個集群,那這個集群之間是怎么通信的呢?哨兵模式既然可以監控redis集群,做到故障轉移,哨兵集群又是怎么監控redis集群的呢?下面就分析這兩個問題
哨兵模式工作原理
哨兵集群中的每個節點都會啟動三個定時任務
- 第一個定時任務: 每個sentinel節點每隔1s向所有的master、slaver、別的sentinel節點發送一個PING命令,作用:心跳檢測
- 第二個定時任務: 每個sentinel每隔2s都會向master的__sentinel__:hello這個channel中發送自己掌握的集群信息和自己的一些信息(比如host,ip,run id),這個是利用redis的pub/sub功能,每個sentinel節點都會訂閱這個channel,也就是說,每個sentinel節點都可以知道別的sentinel節點掌握的集群信息,作用:信息交換,了解別的sentinel的信息和他們對於主節點的判斷
- 第三個定時任務: 每個sentinel節點每隔10s都會向master和slaver發送INFO命令,作用:發現最新的集群拓撲結構
哨兵如何判斷master宕機
主觀下線
這個就是上面介紹的第一個定時任務做的事情,當sentinel節點向master發送一個PING命令,如果超過own-after-milliseconds(默認是30s,這個在sentinel的配置文件中可以自己配置)時間都沒有收到有效回復,不好意思,我就認為你掛了,就是說為的主觀下線(SDOWN),修改其flags狀態為SRI_S_DOWN
客觀下線
要了解什么是客觀下線要先了解幾個重要參數:
- quorum:如果要認為master客觀下線,最少需要主觀下線的sentinel節點個數,舉例:如果5個sentinel節點,quorum = 2,那只要2個sentinel主觀下線,就可以判斷master客觀下線
- majority:如果確定了master客觀下線了,就要把其中一個slaver切換成master,做這個事情的並不是整個sentinel集群,而是sentinel集群會選出來一個sentinel節點來做,那怎么選出來的呢,下面會講,但是有一個原則就是需要大多數節點都同意這個sentinel來做故障轉移才可以,這個大多數節點就是這個參數。注意:如果sentinel節點個數5,quorum=2,majority=3,那就是3個節點同意就可以,如果quorum=5,majority=3,這時候majority=3就不管用了,需要5個節點都同意才可以。
- configuration epoch:這個其實就是version,類似於中國每個皇帝都要有一個年號一樣,每個新的master都要生成一個自己的configuration epoch,就是一個編號
客觀下線處理過程
- 每個主觀下線的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收到命令之后,會根據發送過來的ip和端口檢查自己判斷的結果,如果自己也認為下線了,就會回復,回復包含三個參數:down_state(1表示已下線,0表示未下線),leader_runid(領頭sentinal id),leader_epoch(領頭sentinel紀元)。由於上面發送的runid參數是*,這里后兩個參數先忽略。
- sentinel收到回復之后,根據quorum的值,判斷達到這個值,如果大於或等於,就認為這個master客觀下線
選擇領頭sentinel的過程
到現在為止,已經知道了master客觀下線,那就需要一個sentinel來負責故障轉移,那到底是哪個sentinel節點來做這件事呢?需要通過選舉實現,具體的選舉過程如下:
- 判斷客觀下線的sentinel節點向其他節點發送SENTINEL is-master-down-by-addr ip port current_epoch runid(注意:這時的runid是自己的run id,每個sentinel節點都有一個自己運行時id)
- 目標sentinel回復,由於這個選擇領頭sentinel的過程符合先到先得的原則,舉例:sentinel1判斷了客觀下線,向sentinel2發送了第一步中的命令,sentinel2回復了sentinel1,說選你為領頭,這時候sentinel3也向sentinel2發送第一步的命令,sentinel2會直接拒絕回復
- 當sentinel發現選自己的節點個數超過majority(注意上面寫的一種特殊情況quorum>majority)的個數的時候,自己就是領頭節點
- 如果沒有一個sentinel達到了majority的數量,等一段時間,重新選舉
故障轉移過程
通過上面的介紹,已經有了領頭sentinel,下面就是要做故障轉移了,故障轉移的一個主要問題和選擇領頭sentinel問題差不多,到底要選擇哪一個slaver節點來作為master呢?按照我們一般的常識,我們會認為哪個slaver中的數據和master中的數據相識度高哪個slaver就是master了,其實哨兵模式也差不多是這樣判斷的,不過還有別的判斷條件,詳細介紹如下:
在進行選擇之前需要先剔除掉一些不滿足條件的slaver,這些slaver不會作為變成master的備選
- 剔除列表中已經下線的從服務
- 剔除有5s沒有回復sentinel的info命令的slaver
- 剔除與已經下線的主服務連接斷開時間超過 down-after-milliseconds*10+master宕機時長的slaver
選主過程
- 選擇優先級最高的節點,通過sentinel配置文件中的replica-priority配置項,這個參數越小,表示優先級越高
- 如果第一步中的優先級相同,選擇offset最大的,offset表示主節點向從節點同步數據的偏移量,越大表示同步的數據越多
- 如果第二步offset也相同,選擇run id較小的
后續事項
新的主節點已經選擇出來了,並不是到這里就完事了,后續還需要做一些事情,如下
- 領頭sentinel向別的slaver發送slaveof命令,告訴他們新的master是誰誰誰,你們向這個master復制數據
- 如果之前的master重新上線時,領頭sentinel同樣會給起發送slaveof命令,將其變成從節點
哨兵模式存在的問題
任何一種模式都不是完美的,哨兵模式也不例外,下面就介紹一下這種模式存在的一些問題
主節點寫壓力過大
由於只有主節點負責寫數據,如果有大量的寫請求的時候,主節點負載太高,有掛掉的風險
解決辦法:使用cluster模式^_^
集群腦裂
所謂的集群腦裂,就是一個集群中出現了兩個master,這種情況是如何產生的呢?當集群中的master的網絡出現了問題,和集群中的slaver和sentinel通信出現問題,但是本身並沒有掛,見下圖
聲明:本圖來源:redis 腦裂等極端情況分析
這時,由於sentinel連接不上master,就會重新選擇一個新的slaver變成master,這時候如果client還么有來得及切換,就會把數據寫入到原來的那個master中,一旦網絡恢復,原來的master就會被變成slaver,從新的master上復制數據,那client向原來的master上寫的數據就丟失了。
解決辦法:在redis的配置文件中有如下兩個配置項
- min-slavers-to-write 1
- min-slavers-max-lag 10
這兩個配置項組合在一起配置的意思就是至少有一個slaver和master數據同步延遲不超過10s,如果所有的slaver都超過10s,那master就會拒絕接收請求,為什么加了這兩個參數就可以解決問題呢?如果發生腦裂,如果client向舊的master寫數據,舊的master不能向別的slaver同步數據,所以client最多只能寫10s的數據。這里有些萌新可能就會有問題了,如果發生腦裂的時候,之前集群中的master和一個slaver都與別的slaver和sentinel無法通信了,但是這兩個哥們還可以通信,那這個slaver不就可以正常從master同步數據嗎,不就滿足了上面的兩個條件了嗎,對,確實會這樣^_^,所以你可以把min-slavers-to-write配置大一點
主從數據不一致
這個沒辦法,異步復制確實會有這個問題,如果系統可以接受這一點延遲,那就沒問題,如果一定要沒有一點延遲,那就指定讀主庫吧。
總結
本文介紹了什么是哨兵模式,以及哨兵模式中如何選擇領頭sentinel來做故障轉移和故障轉移的過程,之后又介紹了哨兵模式的一些問題,一般來說吧,如果redis中的數據量不是很大都可以使用這種模式,比如就幾個G的數據,使用哨兵模式沒什么問題。
番外話:最近覺得變成一個厲害的碼農,要精通的東西太多,但人的精力有限,覺得就像一個蝸牛要去攀登珠穆朗瑪峰一樣,爬呀爬,爬呀爬,回頭一看,發現自己還沒有到山腳下。
參考: