歡迎光臨
每天分享高質量文章

深入學習 Redis(4):哨兵

(點選上方公號,快速關註我們)


來源:程式設計迷思

www.cnblogs.com/kismetv/p/9609938.html


前言


在 深入學習Redis(3):主從複製 中曾提到,Redis主從複製的作用有資料熱備、負載均衡、故障恢復等;但主從複製存在的一個問題是故障恢復無法自動化。本文將要介紹的哨兵,它基於Redis主從複製,主要作用便是解決主節點故障恢復的自動化問題,進一步提高系統的高可用性。


文章主要內容如下:首先介紹哨兵的作用和架構;然後講述哨兵系統的部署方法,以及透過客戶端訪問哨兵系統的方法;然後簡要說明哨兵實現的基本原理;最後給出關於哨兵實踐的一些建議。文章內容基於Redis 3.0版本。


系列文章


深入學習Redis(1):Redis記憶體模型

深入學習Redis(2):持久化

深入學習Redis(3):主從複製


一、作用和架構


1.  作用


在介紹哨兵之前,首先從宏觀角度回顧一下Redis實現高可用相關的技術。它們包括:持久化、複製、哨兵和叢集,其主要作用和解決的問題是:


  • 持久化:持久化是最簡單的高可用方法(有時甚至不被歸為高可用的手段),主要作用是資料備份,即將資料儲存在硬碟,保證資料不會因行程退出而丟失。

  • 複製:複製是高可用Redis的基礎,哨兵和叢集都是在複製基礎上實現高可用的。複製主要實現了資料的多機備份,以及對於讀操作的負載均衡和簡單的故障恢復。缺陷:故障恢復無法自動化;寫操作無法負載均衡;儲存能力受到單機的限制。

  • 哨兵:在複製的基礎上,哨兵實現了自動化的故障恢復。缺陷:寫操作無法負載均衡;儲存能力受到單機的限制。

  • 叢集:透過叢集,Redis解決了寫操作無法負載均衡,以及儲存能力受到單機限制的問題,實現了較為完善的高可用方案。


下麵說回哨兵。


Redis Sentinel,即Redis哨兵,在Redis 2.8版本開始引入。哨兵的核心功能是主節點的自動故障轉移。下麵是Redis官方檔案對於哨兵功能的描述:


  • 監控(Monitoring):哨兵會不斷地檢查主節點和從節點是否運作正常。

  • 自動故障轉移(Automatic failover):當主節點不能正常工作時,哨兵會開始自動故障轉移操作,它會將失效主節點的其中一個從節點升級為新的主節點,並讓其他從節點改為複製新的主節點。

  • 配置提供者(Configuration provider):客戶端在初始化時,透過連線哨兵來獲得當前Redis服務的主節點地址。

  • 通知(Notification):哨兵可以將故障轉移的結果傳送給客戶端。


其中,監控和自動故障轉移功能,使得哨兵可以及時發現主節點故障並完成轉移;而配置提供者和通知功能,則需要在與客戶端的互動中才能體現。


這裡對“客戶端”一詞在文章中的用法做一個說明:在前面的文章中,只要透過API訪問redis伺服器,都會稱作客戶端,包括redis-cli、Java客戶端Jedis等;為了便於區分說明,本文中的客戶端並不包括redis-cli,而是比redis-cli更加複雜:redis-cli使用的是redis提供的底層介面,而客戶端則對這些介面、功能進行了封裝,以便充分利用哨兵的配置提供者和通知功能。


2.  架構


典型的哨兵架構圖如下所示:



它由兩部分組成,哨兵節點和資料節點:


  • 哨兵節點:哨兵系統由一個或多個哨兵節點組成,哨兵節點是特殊的redis節點,不儲存資料。

  • 資料節點:主節點和從節點都是資料節點。


二、部署


這一部分將部署一個簡單的哨兵系統,包含1個主節點、2個從節點和3個哨兵節點。方便起見:所有這些節點都部署在一臺機器上(區域網IP:192.168.92.128),使用埠號區分;節點的配置盡可能簡化。


1.  部署主從節點


哨兵系統中的主從節點,與普通的主從節點配置是一樣的,並不需要做任何額外配置。下麵分別是主節點(port=6379)和2個從節點(port=6380/6381)的配置檔案,配置都比較簡單,不再詳述。

#redis-6379.conf

port 6379

daemonize yes

logfile “6379.log”

dbfilename “dump-6379.rdb”

 

#redis-6380.conf

port 6380

daemonize yes

logfile “6380.log”

dbfilename “dump-6380.rdb”

slaveof 192.168.92.128 6379

 

#redis-6381.conf

port 6381

daemonize yes

logfile “6381.log”

dbfilename “dump-6381.rdb”

slaveof 192.168.92.128 6379

配置完成後,依次啟動主節點和從節點:

redisserver redis6379.conf

redisserver redis6380.conf

redisserver redis6381.conf

節點啟動後,連線主節點檢視主從狀態是否正常,如下圖所示:



2.  部署哨兵節點


哨兵節點本質上是特殊的Redis節點。


3個哨兵節點的配置幾乎是完全一樣的,主要區別在於埠號的不同(26379/26380/26381),下麵以26379節點為例介紹節點的配置和啟動方式;配置部分儘量簡化,更多配置會在後面介紹。

#sentinel-26379.conf

port 26379

daemonize yes

logfile “26379.log”

sentinel monitor mymaster 192.168.92.128 6379 2

其中,sentinel monitor mymaster 192.168.92.128 6379 2 配置的含義是:該哨兵節點監控192.168.92.128:6379這個主節點,該主節點的名稱是mymaster,最後的2的含義與主節點的故障判定有關:至少需要2個哨兵節點同意,才能判定主節點故障併進行故障轉移。

哨兵節點的啟動有兩種方式,二者作用是完全相同的:

redissentinel sentinel26379.conf

redisserver sentinel26379.confsentinel

按照上述方式配置和啟動之後,整個哨兵系統就啟動完畢了。可以透過redis-cli連線哨兵節點進行驗證,如下圖所示:可以看出26379哨兵節點已經在監控mymaster主節點(即192.168.92.128:6379),併發現了其2個從節點和另外2個哨兵節點。



此時如果檢視哨兵節點的配置檔案,會發現一些變化,以26379為例:



其中,dir只是顯式宣告了資料和日誌所在的目錄(在哨兵語境下只有日誌);known-slave和known-sentinel顯示哨兵已經發現了從節點和其他哨兵;帶有epoch的引數與配置紀元有關(配置紀元是一個從0開始的計數器,每進行一次領導者哨兵選舉,都會+1;領導者哨兵選舉是故障轉移階段的一個操作,在後文原理部分會介紹)。


3.  演示故障轉移


哨兵的4個作用中,配置提供者和通知需要客戶端的配合,本文將在下一章介紹客戶端訪問哨兵系統的方法時詳細介紹。這一小節將演示當主節點發生故障時,哨兵的監控和自動故障轉移功能。


(1)首先,使用kill命令殺掉主節點:



(2)如果此時立即在哨兵節點中使用info Sentinel命令檢視,會發現主節點還沒有切換過來,因為哨兵發現主節點故障並轉移,需要一段時間。



(3)一段時間以後,再次在哨兵節點中執行info Sentinel檢視,發現主節點已經切換成6380節點。



但是同時可以發現,哨兵節點認為新的主節點仍然有2個從節點,這是因為哨兵在將6380切換成主節點的同時,將6379節點置為其從節點;雖然6379從節點已經掛掉,但是由於哨兵並不會對從節點進行客觀下線(其含義將在原理部分介紹),因此認為該從節點一直存在。當6379節點重新啟動後,會自動變成6380節點的從節點。下麵驗證一下。


(4)重啟6379節點:可以看到6379節點成為了6380節點的從節點。



(5)在故障轉移階段,哨兵和主從節點的配置檔案都會被改寫。


對於主從節點,主要是slaveof配置的變化:新的主節點沒有了slaveof配置,其從節點則slaveof新的主節點。


對於哨兵節點,除了主從節點資訊的變化,紀元(epoch)也會變化,下圖中可以看到紀元相關的引數都+1了。



4.  總結


哨兵系統的搭建過程,有幾點需要註意:


(1)哨兵系統中的主從節點,與普通的主從節點並沒有什麼區別,故障發現和轉移是由哨兵來控制和完成的。


(2)哨兵節點本質上是redis節點。


(3)每個哨兵節點,只需要配置監控主節點,便可以自動發現其他的哨兵節點和從節點。


(4)在哨兵節點啟動和故障轉移階段,各個節點的配置檔案會被重寫(config rewrite)。


(5)本章的例子中,一個哨兵只監控了一個主節點;實際上,一個哨兵可以監控多個主節點,透過配置多條sentinel monitor即可實現。


三、客戶端訪問哨兵系統

上一小節演示了哨兵的兩大作用:監控和自動故障轉移,本小節則結合客戶端演示哨兵的另外兩個作用:配置提供者和通知。


1.  程式碼示例


在介紹客戶端的原理之前,先以Java客戶端Jedis為例,演示一下使用方法:下麵程式碼可以連線我們剛剛搭建的哨兵系統,併進行各種讀寫操作(程式碼中只演示如何連線哨兵,異常處理、資源關閉等未考慮)。

2.  客戶端原理


Jedis客戶端對哨兵提供了很好的支援。如上述程式碼所示,我們只需要向Jedis提供哨兵節點集合和masterName,構造JedisSentinelPool物件;然後便可以像使用普通redis連線池一樣來使用了:透過pool.getResource()獲取連線,執行具體的命令。


在整個過程中,我們的程式碼不需要顯式的指定主節點的地址,就可以連線到主節點;程式碼中對故障轉移沒有任何體現,就可以在哨兵完成故障轉移後自動的切換主節點。之所以可以做到這一點,是因為在JedisSentinelPool的建構式中,進行了相關的工作;主要包括以下兩點:


(1)遍歷哨兵節點,獲取主節點資訊:遍歷哨兵節點,透過其中一個哨兵節點+masterName獲得主節點的資訊;該功能是透過呼叫哨兵節點的sentinel get-master-addr-by-name命令實現,該命令示例如下:



一旦獲得主節點資訊,停止遍歷(因此一般來說遍歷到第一個哨兵節點,迴圈就停止了)。


(2)增加對哨兵的監聽:這樣當發生故障轉移時,客戶端便可以收到哨兵的通知,從而完成主節點的切換。具體做法是:利用redis提供的釋出訂閱功能,為每一個哨兵節點開啟一個單獨的執行緒,訂閱哨兵節點的+switch-master頻道,當收到訊息時,重新初始化連線池。


3.  總結


透過客戶端原理的介紹,可以加深對哨兵功能的理解:


(1)配置提供者:客戶端可以透過哨兵節點+masterName獲取主節點資訊,在這裡哨兵起到的作用就是配置提供者。


需要註意的是,哨兵只是配置提供者,而不是代理。二者的區別在於:如果是配置提供者,客戶端在透過哨兵獲得主節點資訊後,會直接建立到主節點的連線,後續的請求(如set/get)會直接發向主節點;如果是代理,客戶端的每一次請求都會發向哨兵,哨兵再透過主節點處理請求。


舉一個例子可以很好的理解哨兵的作用是配置提供者,而不是代理。在前面部署的哨兵系統中,將哨兵節點的配置檔案進行如下修改:

sentinel monitor mymaster 192.168.92.128 6379 2

改為

sentinel monitor mymaster 127.0.0.1 6379 2

然後,將前述客戶端程式碼在區域網的另外一臺機器上執行,會發現客戶端無法連線主節點;這是因為哨兵作為配置提供者,客戶端透過它查詢到主節點的地址為127.0.0.1:6379,客戶端會向127.0.0.1:6379建立redis連線,自然無法連線。如果哨兵是代理,這個問題就不會出現了。


(2)通知:哨兵節點在故障轉移完成後,會將新的主節點資訊傳送給客戶端,以便客戶端及時切換主節點。


四、基本原理


前面介紹了哨兵部署、使用的基本方法,本部分介紹哨兵實現的基本原理。


1.  哨兵節點支援的命令


哨兵節點作為執行在特殊樣式下的redis節點,其支援的命令與普通的redis節點不同。在運維中,我們可以透過這些命令查詢或修改哨兵系統;不過更重要的是,哨兵系統要實現故障發現、故障轉移等各種功能,離不開哨兵節點之間的通訊,而通訊的很大一部分是透過哨兵節點支援的命令來實現的。下麵介紹哨兵節點支援的主要命令。


(1)基礎查詢:透過這些命令,可以查詢哨兵系統的拓撲結構、節點資訊、配置資訊等。


  • info sentinel:獲取監控的所有主節點的基本資訊

  • sentinel masters:獲取監控的所有主節點的詳細資訊

  • sentinel master mymaster:獲取監控的主節點mymaster的詳細資訊

  • sentinel slaves mymaster:獲取監控的主節點mymaster的從節點的詳細資訊

  • sentinel sentinels mymaster:獲取監控的主節點mymaster的哨兵節點的詳細資訊

  • sentinel get-master-addr-by-name mymaster:獲取監控的主節點mymaster的地址資訊,前文已有介紹

  • sentinel is-master-down-by-addr:哨兵節點之間可以透過該命令詢問主節點是否下線,從而對是否客觀下線做出判斷


(2)增加/移除對主節點的監控


sentinel monitor mymaster2 192.168.92.128 16379 2:與部署哨兵節點時配置檔案中的sentinel monitor功能完全一樣,不再詳述


sentinel remove mymaster2:取消當前哨兵節點對主節點mymaster2的監控


(3)強制故障轉移


sentinel failover mymaster:該命令可以強制對mymaster執行故障轉移,即便當前的主節點執行完好;例如,如果當前主節點所在機器即將報廢,便可以提前透過failover命令進行故障轉移。


2.  基本原理


關於哨兵的原理,關鍵是瞭解以下幾個概念。


(1)定時任務:每個哨兵節點維護了3個定時任務。定時任務的功能分別如下:透過向主從節點傳送info命令獲取最新的主從結構;透過釋出訂閱功能獲取其他哨兵節點的資訊;透過向其他節點傳送ping命令進行心跳檢測,判斷是否下線。


(2)主觀下線:在心跳檢測的定時任務中,如果其他節點超過一定時間沒有回覆,哨兵節點就會將其進行主觀下線。顧名思義,主觀下線的意思是一個哨兵節點“主觀地”判斷下線;與主觀下線相對應的是客觀下線。


(3)客觀下線:哨兵節點在對主節點進行主觀下線後,會透過sentinel is-master-down-by-addr命令詢問其他哨兵節點該主節點的狀態;如果判斷主節點下線的哨兵數量達到一定數值,則對該主節點進行客觀下線。


需要特別註意的是,客觀下線是主節點才有的概念;如果從節點和哨兵節點發生故障,被哨兵主觀下線後,不會再有後續的客觀下線和故障轉移操作。


(4)選舉領導者哨兵節點:當主節點被判斷客觀下線以後,各個哨兵節點會進行協商,選舉出一個領導者哨兵節點,並由該領導者節點對其進行故障轉移操作。


監視該主節點的所有哨兵都有可能被選為領導者,選舉使用的演演算法是Raft演演算法;Raft演演算法的基本思路是先到先得:即在一輪選舉中,哨兵A向B傳送成為領導者的申請,如果B沒有同意過其他哨兵,則會同意A成為領導者。選舉的具體過程這裡不做詳細描述,一般來說,哨兵選擇的過程很快,誰先完成客觀下線,一般就能成為領導者。


(5)故障轉移:選舉出的領導者哨兵,開始進行故障轉移操作,該操作大體可以分為3個步驟:


  • 在從節點中選擇新的主節點:選擇的原則是,首先過濾掉不健康的從節點;然後選擇優先順序最高的從節點(由slave-priority指定);如果優先順序無法區分,則選擇複製偏移量最大的從節點;如果仍無法區分,則選擇runid最小的從節點。

  • 更新主從狀態:透過slaveof no one命令,讓選出來的從節點成為主節點;並透過slaveof命令讓其他節點成為其從節點。

  • 將已經下線的主節點(即6379)設定為新的主節點的從節點,當6379重新上線後,它會成為新的主節點的從節點。


透過上述幾個關鍵概念,可以基本瞭解哨兵的工作原理。為了更形象的說明,下圖展示了領導者哨兵節點的日誌,包括從節點啟動到完成故障轉移。



五、配置與實踐建議


1.  配置


下麵介紹與哨兵相關的幾個配置。


(1) sentinel monitor {masterName} {masterIp} {masterPort} {quorum}


sentinel monitor是哨兵最核心的配置,在前文講述部署哨兵節點時已說明,其中:masterName指定了主節點名稱,masterIp和masterPort指定了主節點地址,quorum是判斷主節點客觀下線的哨兵數量閾值:當判定主節點下線的哨兵數量達到quorum時,對主節點進行客觀下線。建議取值為哨兵數量的一半加1。


(2) sentinel down-after-milliseconds {masterName} {time}


sentinel down-after-milliseconds與主觀下線的判斷有關:哨兵使用ping命令對其他節點進行心跳檢測,如果其他節點超過down-after-milliseconds配置的時間沒有回覆,哨兵就會將其進行主觀下線。該配置對主節點、從節點和哨兵節點的主觀下線判定都有效。


down-after-milliseconds的預設值是30000,即30s;可以根據不同的網路環境和應用要求來調整:值越大,對主觀下線的判定會越寬鬆,好處是誤判的可能性小,壞處是故障發現和故障轉移的時間變長,客戶端等待的時間也會變長。例如,如果應用對可用性要求較高,則可以將值適當調小,當故障發生時儘快完成轉移;如果網路環境相對較差,可以適當提高該閾值,避免頻繁誤判。


(3) sentinel parallel-syncs {masterName} {number}


sentinel parallel-syncs與故障轉移之後從節點的複製有關:它規定了每次向新的主節點發起複制操作的從節點個數。例如,假設主節點切換完成之後,有3個從節點要向新的主節點發起複制;如果parallel-syncs=1,則從節點會一個一個開始複製;如果parallel-syncs=3,則3個從節點會一起開始複製。


parallel-syncs取值越大,從節點完成複製的時間越快,但是對主節點的網路負載、硬碟負載造成的壓力也越大;應根據實際情況設定。例如,如果主節點的負載較低,而從節點對服務可用的要求較高,可以適量增加parallel-syncs取值。parallel-syncs的預設值是1。


(4) sentinel failover-timeout {masterName} {time}


sentinel failover-timeout與故障轉移超時的判斷有關,但是該引數不是用來判斷整個故障轉移階段的超時,而是其幾個子階段的超時,例如如果主節點晉升從節點時間超過timeout,或從節點向新的主節點發起複制操作的時間(不包括複製資料的時間)超過timeout,都會導致故障轉移超時失敗。


failover-timeout的預設值是180000,即180s;如果超時,則下一次該值會變為原來的2倍。


(5)除上述幾個引數外,還有一些其他引數,如安全驗證相關的引數,這裡不做介紹。


2.  實踐建議


(1)哨兵節點的數量應不止一個,一方面增加哨兵節點的冗餘,避免哨兵本身成為高可用的瓶頸;另一方面減少對下線的誤判。此外,這些不同的哨兵節點應部署在不同的物理機上。


(2)哨兵節點的數量應該是奇數,便於哨兵透過投票做出“決策”:領導者選舉的決策、客觀下線的決策等。


(3)各個哨兵節點的配置應一致,包括硬體、引數等;此外,所有節點都應該使用ntp或類似服務,保證時間準確、一致。


(4)哨兵的配置提供者和通知客戶端功能,需要客戶端的支援才能實現,如前文所說的Jedis;如果開發者使用的庫未提供相應支援,則可能需要開發者自己實現。


(5)當哨兵系統中的節點在docker(或其他可能進行埠對映的軟體)中部署時,應特別註意埠對映可能會導致哨兵系統無法正常工作,因為哨兵的工作基於與其他節點的通訊,而docker的埠對映可能導致哨兵無法連線到其他節點。例如,哨兵之間互相發現,依賴於它們對外宣稱的IP和port,如果某個哨兵A部署在做了埠對映的docker中,那麼其他哨兵使用A宣稱的port無法連線到A。


六、總結


本文首先介紹了哨兵的作用:監控、故障轉移、配置提供者和通知;然後講述了哨兵系統的部署方法,以及透過客戶端訪問哨兵系統的方法;再然後簡要說明瞭哨兵實現的基本原理;最後給出了關於哨兵實踐的一些建議。


在主從複製的基礎上,哨兵引入了主節點的自動故障轉移,進一步提高了Redis的高可用性;但是哨兵的缺陷同樣很明顯:哨兵無法對從節點進行自動故障轉移,在讀寫分離場景下,從節點故障會導致讀服務不可用,需要我們對從節點做額外的監控、切換操作。


此外,哨兵仍然沒有解決寫操作無法負載均衡、及儲存能力受到單機限制的問題;這些問題的解決需要使用叢集,我將在後面的文章中介紹,歡迎關註。


參考文獻


https://redis.io/topics/sentinel

http://www.redis.cn/

《Redis開發與運維》

《Redis設計與實現》

【關於投稿】


如果大家有原創好文投稿,請直接給公號傳送留言。


① 留言格式:
【投稿】+《 文章標題》+ 文章連結

② 示例:
【投稿】
《不要自稱是程式員,我十多年的 IT 職場總結》:http://blog.jobbole.com/94148/


③ 最後請附上您的個人簡介哈~

看完本文有收穫?請轉發分享給更多人

關註「資料分析與開發」,提升資料技能

贊(0)

分享創造快樂