redis(五:哨兵)

從上篇文章可知,從庫與客戶端的交互只負責讀數據,而寫數據由主庫進行,并進行主從同步(生成 RDB 文件和傳輸 RDB 文件,注意生成 RDB 需要在主線程fork子進程會阻塞正常請求)。

當主庫宕機了,客戶端如何進行寫操作?


此時就需要哨兵來幫忙,將一個從庫升級為主庫。

哨兵主要負責的就是三個任務:監控、選擇主庫和通知。

監控
監控是指哨兵進程在運行時,周期性地給所有的主從庫發送 PING 命令,檢測它們是否仍然在線運行。如果從庫沒有在規定時間內響應哨兵的 PING 命令,哨兵就會把它標記為“下線狀態”;
同樣,如果主庫也沒有在規定時間內響應哨兵的 PING 命令,哨兵就會判定主庫下線,然后開始自動切換主庫的流程。

下線判斷分為“主觀下線”和“客觀下線”兩種。

選擇主庫
主庫掛了以后,哨兵就需要從很多個從庫里,按照一定的規則選擇一個從庫實例,把它作為新的主庫。

選擇新主庫的過程稱為“篩選 + 打分”。

通知
哨兵會把新主庫的連接信息發給其他從庫,讓它們執行 replicaof 命令,和新主庫建立連接,并進行數據復制。
同時,哨兵會把新主庫的連接信息通知給客戶端,讓它們把請求操作發到新主庫上。


主觀下線
哨兵進程會使用 PING 命令檢測它自己和主、從庫的網絡連接情況,用來判斷實例的狀態。
如果哨兵發現從庫對 PING 命令的響應超時了。由于從庫的下線影響一般不太大,可直接標記為“主觀下線”。
主庫則不可這么隨便,一旦啟動了主從切換,后續的選主和通知操作都會帶來額外的計算和通信開銷。為了避免哨兵誤判(一般會發生在集群網絡壓力較大、網絡擁塞,或者是主庫本身壓力較大的情況下)。
通常會采用多實例組成的集群模式進行部署,這也被稱為哨兵集群。引入多個哨兵實例一起來判斷,就可以避免單個哨兵因為自身網絡狀況不好,而誤判主庫下線的情況。同時,多個哨兵的網絡同時不穩定的概率較小,由它們一起做決策,誤判率也能降低。

客觀下線
在哨兵集群的情況下,只有大多數的哨兵實例,都判斷主庫已經“主觀下線”了,主庫才會被標記為“客觀下線”。判斷原則就是:少數服從多數。


當有 N 個哨兵實例時,最好要有 N/2 + 1 個實例判斷主庫為“主觀下線”,才能最終判定主庫為“客觀下線”。

篩選
檢查從庫的當前在線狀態,判斷它之前的網絡連接狀態。(down-after-milliseconds)
如果在 down-after-milliseconds 毫秒內,主從節點都沒有通過網絡聯系上,我們就可以認為主從節點斷連了。如果發生斷連的次數超過了 n 次,就說明這個從庫的網絡狀況不好,不適合作為新主庫。

要保證所有哨兵實例的配置是一致的,尤其是主觀下線的判斷值 down-after-milliseconds。否則可能導致集群服務不穩定。

打分
1,優先級最高的從庫得分高(slave-priority 配置項)
2,和舊主庫同步程度最接近的從庫得分高(repl_backlog_buffer:詳見上篇文章)
3,ID 號小的從庫得分高


哨兵見如何互相通信?
通過 pub/sub (發布/訂閱)機制,哨兵只要和主庫建立起了連接,每個哨兵都把自己的信息發送給主庫,然后從主庫訂閱其他哨兵的消息,這樣就可以互相知道其他哨兵的地址了。

哨兵是如何知道從庫的 IP 地址和端口的呢?
這是由哨兵向主庫發送 INFO 命令來完成的。就像下圖所示,哨兵 2 給主庫發送 INFO 命令,主庫接受到這個命令后,就會把從庫列表返回給哨兵。

通過 pub/sub 機制,哨兵之間可以組成集群,同時,哨兵又通過 INFO 命令,獲得了從庫連接信息,也能和從庫建立連接,并進行監控了

主從切換后,哨兵是如何告知客戶端新的主庫
通過 pub/sub (發布/訂閱)機制,客戶端可以從哨兵訂閱消息。哨兵提供的消息訂閱頻道有很多,不同頻道包含了主從庫切換過程中的不同關鍵事件。

主庫故障以后,哨兵集群有多個實例,那怎么確定由哪個哨兵來進行實際的主從切換呢?
1,任何一個實例只要自身判斷主庫“主觀下線”后,就會給其他實例發送 is-master-down-by-addr 命令。接著,其他實例會根據自己和主庫的連接情況,做出 Y 或 N 的響應,Y 相當于贊成票,N 相當于反對票。
2,一個哨兵獲得了仲裁所需的贊成票數后,就可以標記主庫為“客觀下線”。
3,此時,這個哨兵就可以再給其他哨兵發送命令,表明希望由自己來執行主從切換,并讓所有其他哨兵進行投票。這個投票過程稱為“Leader 選舉”。

如果哨兵集群只有 2 個實例,此時,一個哨兵要想成為 Leader,必須獲得 2 票,而不是 1 票。所以,如果有個哨兵掛掉了,那么,此時的集群是無法進行主從庫切換的。因此,通常我們至少會配置 3 個哨兵實例。


哨兵在操作主從切換的過程中,客戶端能否正常地進行請求操作?
如果客戶端使用了讀寫分離,那么讀請求可以在從庫上正常執行,不會受到影響。但是由于此時主庫已經掛了,而且哨兵還沒有選出新的主庫,所以在這期間寫請求會失敗,失敗持續的時間 = 哨兵切換主從的時間 + 客戶端感知到新主庫 的時間。
如果不想讓業務感知到異常,客戶端只能把寫失敗的請求先緩存起來或寫入消息隊列中間件中,等哨兵切換完主從后,再把這些寫請求發給新的主庫,但這種場景只適合對寫入請求返回值不敏感的業務,而且還需要業務層做適配,另外主從切換時間過長,也會導致客戶端或消息隊列中間件緩存寫請求過多,切換完成之后重放這些請求的時間變長。
哨兵檢測主庫多久沒有響應就提升從庫為新的主庫,這個時間是可以配置的(down-after-milliseconds參數)。配置的時間越短,哨兵越敏感,哨兵集群認為主庫在短時間內連不上就會發起主從切換,這種配置很可能因為網絡擁塞但主庫正常而發生不必要的切換,當然,當主庫真正故障時,因為切換得及時,對業務的影響最小。如果配置的時間比較長,哨兵越保守,這種情況可以減少哨兵誤判的概率,但是主庫故障發生時,業務寫失敗的時間也會比較久,緩存寫請求數據量越多。

哨兵提升一個從庫為新主庫后,客戶端因為某些原因錯過了哨兵的通知怎么辦?
哨兵提升一個從庫為新主庫后,哨兵會把新主庫的地址寫入自己實例的pubsub(switch-master)中??蛻舳诵枰嗛嗊@個pubsub,當這個pubsub有數據時,客戶端就能感知到主庫發生變更,同時可以拿到最新的主庫地址,然后把寫請求寫到這個新主庫即可,這種機制屬于哨兵主動通知客戶端。
如果客戶端因為某些原因錯過了哨兵的通知,或者哨兵通知后客戶端處理失敗了,安全起見,客戶端也需要支持主動去獲取最新主從的地址進行訪問。
所以,客戶端需要訪問主從庫時,不能直接寫死主從庫的地址了,而是需要從哨兵集群中獲取最新的地址(sentinel get-master-addr-by-name命令),這樣當實例異常時,哨兵切換后或者客戶端斷開重連,都可以從哨兵集群中拿到最新的實例地址。
即為推拉結合。

redis主從切換維持數據最終一致性嗎?(數據是否會丟失?)
redis不是最終一致性,leader未同步數據給flow節點時掛了發生主從切換,未同步的數據就丟失了。
而像ZooKeeper這類ZAB協議,是要Leader同步超過半數Flow節點,才告知客戶的寫操作成功,如果Leader掛了重新選出的Leader也有最新的數據。

哨兵實例是不是越多越好?
并不是,哨兵在判定“主觀下線”和選舉“哨兵領導者”時,都需要和其他節點進行通信,交換信息,哨兵實例越多,通信的次數也就越多,而且部署多個哨兵時,會分布在不同機器上,節點越多帶來的機器故障風險也會越大,這些問題都會影響到哨兵的通信和選舉,出問題時也就意味著選舉時間會變長,切換主從的時間變久。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容