Redis面試系列:Redis Cluster如何通信?客戶端怎麼知道數據位於哪個節點?滴滴三面問到你懷疑人生(五)

前言

如何保證Redis的高併發和高可用?Redis的哨兵原理能介紹一下麼?Redis Cluster如何通信?客戶端怎麼知道數據位於哪個節點?MOVED具體是怎麼實現的?

一、Sentinel(哨兵)模式

Sentinel本質上只是一個運行在特殊模式下的Redis服務器

Sentinel(哨兵)是Redis的高可用性(high availability)解決方案:由一個或多個Sentinel實例(instance)組成的Sentinel系統(system)可以監視任意多個主服務器,以及這些主服務器屬下的所有從服務器,並在被監視的主服務器進入下線狀態時,自動將下線主服務器屬下的某個從服務器升級爲新的主服務器,然後由新的主服務器代替已下線的主服務器繼續處理命令請求。

在這裏插入圖片描述

Sentinel模式其實就是主從的技術方案(可以實現讀寫分離)的基礎上,加入了Sentinel系統來監視整個主從集羣的健康情況,如果發現有主服務器宕機,Sentinel系統就會對宕機服務器執行故障轉移操作:

  • Sentinel系統會挑選主服務屬下的其中一個從服務器,並將這個被選中的從服務器升級爲新的主服務器
  • Sentinel系統會向主服務屬下的所有從服務器發送新的複製指令,讓它們成爲新的主服務器的從服務器,當所有從服務器都開始複製新的主服務器時,故障轉移操作執行完畢。
  • Sentinel系統還會繼續監視已下線的服務,並在它重新上線時,將它設置爲新的主服務器的從服務器。

在這裏插入圖片描述

啓動一個Sentinel都做了哪些事情:

  1. 初始化服務器。
  2. 將普通Redis服務器使用的代碼替換成Sentinel專用代碼。
  3. 初始化Sentinel狀態。
  4. 根據給定的配置文件,初始化Sentinel的監視主服務器列表。
  5. 創建連向主服務器的網絡連接。

創建連向被監視主服務器的網絡連接後,Sentinel將成爲主服務器的客戶端,它可以向主服務器發送命令,並從命令回覆中獲取相關的信息。

對於每個被Sentinel監視的主服務器來說,Sentinel會創建兩個連向主服務器的異步網絡連接:

  • 一個是命令連接,這個連接專門用於向主從服務器發送命令,並接收命令回覆。
  • 另一個是訂閱連接,這個連接專門用於訂閱主從服務器的__sentinel__:hello頻道。

啓動完成之後,Sentinel默認會以每十秒一次的頻率,通過命令連接向被監視的主服務器發送INFO命令,並通過分析INFO命令的回覆來獲取主服務器的信息:

# Server  主服務器本身的信息
...
run_id:7611c59dc3a29aa6fa0609f841bb6a1019008a9c
...
# Replication 主服務器屬下所有從服務器的信息
role:master
...
slave0:ip=127.0.0.1,port=11111,state=online,offset=43,lag=0
slave1:ip=127.0.0.1,port=22222,state=online,offset=43,lag=0
slave2:ip=127.0.0.1,port=33333,state=online,offset=43,lag=0
...
# Other sections
...

Sentinel還會創建連接到從服務器的命令連接和訂閱連接。同時也會每十秒一次的向從服務器發送INFO命令,從服務器會返回以下信息:

# Server
...
run_id:32be0699dd27b410f7c90dada3a6fab17f97899f
...
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
slave_repl_offset:11887
slave_priority:100
# Other sections
...
  • 從服務器的運行ID run_id
  • 從服務器的角色role
  • 主服務器的IP地址master_host,以及主服務器的端口號master_port
  • 主從服務器的連接狀態master_link_status
  • 從服務器的優先級slave_priority
  • 從服務器的複製偏移量slave_repl_offset

發送信息

PUBLISH sentinel:hello “<s_ip>,<s_port>,<s_runid>,<s_epoch>,<m_name>,<m_ip>,<m_port>,<m_epoch>”

每隔兩秒鐘,每個哨兵都會向自己監控主服務器和從服務器對應的__sentinel__:hello
頻道里發送一個消息(包括自己的host、ip和runid還有對這個master的監控配置)。

接收信息

SUBSCRIBE sentinel:hello

對於每個與Sentinel連接的服務器,Sentinel既通過命令連接向服務器的__sentinel__:hello頻道發送信息,又通過服務器的__sentinel__:hello頻道接收信息

Sentinel在連接主服務器或者從服務器時,會同時創建命令連接和訂閱連接,但是在連接其他Sentinel時,卻只會創建命令連接,而不創建訂閱連接。這是因爲Sentinel需要通過接收主服務器或者從服務器發來的頻道信息來發現未知的新Sentinel,所以才需要建立訂閱連接,而相互已知的Sentinel只要使用命令連接來進行通信就足夠了。

sdown和odown轉換機制
sdown,即主觀宕機,如果一個Sentinel它自己覺得master宕機了,就是主觀宕機。
odown,即客觀宕機,如果quorum數量的哨兵都認爲一個master宕機了,則爲客觀宕機。

Sentinel會以每秒一次的頻率向所有與它創建了命令連接的實例(包括主服務器、從服務器、其他Sentinel在內)發送PING命令,並通過實例返回的PING命令回覆來判斷實例是否在線。

哨兵在ping一個主服務器的時候,如果超過了is-master-down-after-milliseconds指定的毫秒數之後,就是達到了sdown,就主觀認爲主服務器宕機了。

如果一個Sentinel在指定時間內,收到了quorum指定數量的其他Sentinel也認爲那個主服務器是sdown了,那麼就認爲是odown了,客觀認爲主服務器宕機,就完成了sdown到odown的轉換。

當一個主服務器被判斷爲客觀下線時,監視這個下線主服務器的各個Sentinel會進行協商,選舉出一個領頭Sentinel,並由領頭Sentinel對下線主服務器執行故障轉移操作。

從服務器(slave)到主服務器(master)選舉算法

主要通過下面幾個步驟:

  1. 刪除列表中所有處於下線或者斷線狀態的從服務器,這可以保證列表中剩餘的從服務器都是正常在線的。
  2. 刪除列表中所有最近五秒內沒有回覆過領頭Sentinel的INFO命令的從服務器,這可以保證列表中剩餘的從服務器都是最近成功進行過通信的。
  3. 刪除所有與已下線主服務器連接斷開超過down-after-milliseconds10毫秒的從服務器:down-after-milliseconds選項指定了判斷主服務器下線所需的時間,而刪除斷開時長超過down-after-milliseconds10毫秒的從服務器,則可以保證列表中剩餘的從服務器都沒有過早地與主服務器斷開連接,換句話說,列表中剩餘的從服務器保存的數據都是比較新的。

接下來會對slave進行排序

  1. 按照slave優先級進行排序,slave priority越低,優先級就越高。
  2. 如果slave priority相同,那麼看replica offset,哪個slave複製了越多的數據,offset越靠後,優先級就越高。
  3. 如果上面兩個條件都相同,那麼選擇一個run id比較小的那個slave

哨兵集羣至少要 3 個節點,來確保自己的健壯性。

如果哨兵集羣 是2 個節點的問題:
2個哨兵的majority是2(3個的majority=2,4個的majority=2,5個的majority=3)
只有2個節點,其中一個宕機,那麼哨兵就只有一個了,此時就無法來通過majority來進行故障轉移。

redis主從 + Sentinel的架構,不是保證數據的零丟失的,它是爲了保證Redis集羣的高可用。

Sentinel架構的缺點是什麼?

二、Cluster(集羣)模式

https://redis.io/topics/cluster-tutorial/ 建議閱讀官方文檔

Redis集羣是Redis提供的分佈式數據庫方案,集羣通過分片(sharding)來進行數據共享,並提供複製和故障轉移功能。

一個Redis集羣由多個節點組成。

可以這樣理解一下Redis集羣模式:
在這裏插入圖片描述
一組主從就是一個節點,把各個獨立的節點連接起來就構成了一個集羣。

Redis集羣數據分片

Redis Cluster不使用一致的哈希,而是使用一種不同形式的分片:槽(slot)。
Redis集羣中有16384個哈希槽,每個鍵都是屬於這16384個槽中的其中一個,要計算一個鍵屬於哪個哈希槽,只需對key的CRC16取模16384。

Redis羣集中的每個節點都負責哈希槽的子集,如果有一個包含3個節點的羣集,其中:
節點A包含從0到5500的哈希槽。
節點B包含從5501到11000的哈希槽。
節點C包含從11001到16383的哈希槽。

一個節點可以隨意分配槽點,不是必須連續的。
爲什麼是16384個?

傳播節點的槽信息

一個節點除了保存自己負責處理的槽信息,還會將自己的slots數組通過消息發送給集羣中的其他節點,來告訴其他節點自己目前負責處理哪裏槽。

這個很重要記一下,後面會用到。

每個節點都保存了不同的數據,客戶端查詢的時候,怎麼知道去哪個節點找key呢?key換節點了怎麼處理的?

在集羣中執行命令邏輯:

在這裏插入圖片描述

MOVED錯誤

在節點發現key所在的槽不是自己負責處理的時候,就會返回一個MOVED錯誤:

MOVED <slot> <ip>:<port>

讓客戶端重定向( redirected)到正確的 Redis 節點。

因爲節點本身會存儲一份其他節點負責的槽點,所以MOVED很容易實現。

重新分片

Redis集羣的重新分片操作可以將任意數量已經指派給某個節點(源節點)的槽改爲指派給另一個節點(目標節點),並且相關槽所屬的鍵值對也會從源節點被移動到目標節點。
重新分片操作可以在線(online)進行,在重新分片的過程中,集羣不需要下線,並且源節點和目標節點都可以繼續處理命令請求。

ASK錯誤

在進行重新分片期間,源節點向目標節點遷移一個槽的過程中,可能會出現這樣一種情況:屬於被遷移槽的一部分鍵值對保存在源節點裏面,而另一部分鍵值對則保存在目標節點裏面。

當客戶端向源節點發送一個與數據庫鍵有關的命令,並且命令要處理的數據庫鍵恰好就屬於正在被遷移的槽時,怎麼處理?

  1. 源節點會先在自己的數據庫裏面查找指定的鍵,如果找到的話,就直接執行客戶端發送的命令。
  2. 相反地,如果源節點沒能在自己的數據庫裏面找到指定的鍵,那麼這個鍵有可能已經被遷移到了目標節點,源節點將向客戶端返回一個ASK錯誤,指引客戶端轉向正在導入槽的目標節點,並再次發送之前想要執行的命令。

MOVED和ASK的區別

  • MOVED錯誤代表槽的負責權已經從一個節點轉移到了另一個節點:在客戶端收到關於槽i的MOVED錯誤之後,客戶端每次遇到關於槽i的命令請求時,都可以直接將命令請求發送至MOVED錯誤所指向的節點,因爲該節點就是目前負責槽i的節點。

  • 與此相反,ASK錯誤只是兩個節點在遷移槽的過程中使用的一種臨時措施:在客戶端收到關於槽i的ASK錯誤之後,客戶端只會在接下來的一次命令請求中將關於槽i的命令請求發送至ASK錯誤所指示的節點,但這種轉向不會對客戶端今後發送關於槽i的命令請求產生任何影響,客戶端仍然會將關於槽i的命令請求發送至目前負責處理槽i的節點,除非ASK錯誤再次出現。

節點間的內部通信機制

集羣中的各個節點通過發送和接收消息(message)來進行通信,採有gossip協議。

gossip協議原理:在一個有界網絡中,每個節點都隨機地與其他節點通信,經過一番雜亂無章的通信,最終所有節點的狀態都會達成一致。每個節點可能知道所有其他節點,也可能僅知道幾個鄰居節點,只要這些節可以通過網絡連通,最終他們的狀態都是一致的,很像疫情傳播。
優點在於元數據的更新比較分散,不是集中在一個地方,更新請求會陸陸續續,打到所有節點上去更新,有一定的延時,降低了壓力;
缺點在於元數據更新有延時可能導致集羣的一些操作會有一些滯後。

節點發送的消息主要有以下五種:

  1. MEET消息:當發送者接到客戶端發送的CLUSTER MEET命令時,發送者會向接收者發送MEET消息,請求接收者加入到發送者當前所處的集羣裏面。

  2. PING消息:集羣裏的每個節點默認每隔一秒鐘就會從已知節點列表中隨機選出五個節點,然後對這五個節點中最長時間沒有發送過PING消息的節點發送PING消息,以此來檢測被選中的節點是否在線。除此之外,如果節點A最後一次收到節點B發送的PONG消息的時間,距離當前時間已經超過了節點A的cluster-node-timeout選項設置時長的一半,那麼節點A也會向節點B發送PING消息,這可以防止節點A因爲長時間沒有隨機選中節點B作爲PING消息的發送對象而導致對節點B的信息更新滯後。

  3. PONG消息:當接收者收到發送者發來的MEET消息或者PING消息時,爲了向發送者確認這條MEET消息或者PING消息已到達,接收者會向發送者返回一條PONG消息。另外,一個節點也可以通過向集羣廣播自己的PONG消息來讓集羣中的其他節點立即刷新關於這個節點的認識,例如當一次故障轉移操作成功執行之後,新的主節點會向集羣廣播一條PONG消息,以此來讓集羣中的其他節點立即知道這個節點已經變成了主節點,並且接管了已下線節點負責的槽。

  4. FAIL消息:當一個主節點A判斷另一個主節點B已經進入FAIL狀態時,節點A會向集羣廣播一條關於節點B的FAIL消息,所有收到這條消息的節點都會立即將節點B標記爲已下線。

  5. PUBLISH消息:當節點接收到一個PUBLISH命令時,節點會執行這個命令,並向集羣廣播一條PUBLISH消息,所有接收到這條PUBLISH消息的節點都會執行相同的PUBLISH命令。

主備切換原理

1.判斷節點宕機

集羣中的每個節點都會定期地向集羣中的其他節點發送PING消息,以此來檢測對方是否在線,如果接收PING消息的節點沒有在規定的時間內,向發送PING消息的節點返回PONG消息,那麼發送PING消息的節點就會將接收PING消息的節點標記爲疑似下線(probable fail,PFAIL)。

如果在一個集羣裏面,半數以上負責處理槽的主節點都將某個主節點x報告爲疑似下線,那麼這個主節點x將被標記爲已下線(FAIL),將主節點x標記爲已下線的節點會向集羣廣播一條關於主節點x的FAIL消息,所有收到這條FAIL消息的節點都會立即將主節點x標記爲已下線。

2.選舉新的主節點

  • 集羣的配置紀元是一個自增計數器,它的初始值爲0。
  • 當集羣裏的某個節點開始一次故障轉移操作時,集羣配置紀元的值會被增一。
  • 對於每個配置紀元,集羣裏每個負責處理槽的主節點都有一次投票的機會,而第一個向主節點要求投票的從節點將獲得主節點的投票。
  • 當從節點發現自己正在複製的主節點進入已下線狀態時,從節點會向集羣廣播一條CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST消息,要求所有收到這條消息、並且具有投票權的主節點向這個從節點投票。
  • 如果一個主節點具有投票權(它正在負責處理槽),並且這個主節點尚未投票給其他從節點,那麼主節點將向要求投票的從節點返回一條CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,表示這個主節點支持從節點成爲新的主節點。
  • 每個參與選舉的從節點都會接收CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,並根據自己收到了多少條這種消息來統計自己獲得了多少主節點的支持。
  • 如果集羣裏有N個具有投票權的主節點,那麼當一個從節點收集到大於等於N/2+1張支持票時,這個從節點就會當選爲新的主節點。
  • 因爲在每一個配置紀元裏面,每個具有投票權的主節點只能投一次票,所以如果有N個主節點進行投票,那麼具有大於等於N/2+1張支持票的從節點只會有一個,這確保了新的主節點只會有一個。
  • 如果在一個配置紀元裏面沒有從節點能收集到足夠多的支持票,那麼集羣進入一個新的配置紀元,並再次進行選舉,直到選出新的主節點爲止。

故障轉移

當一個從節點發現自己正在複製的主節點進入了已下線狀態時,從節點將開始對下線主節點進行故障轉移,以下是故障轉移的執行步驟:

  • 複製下線主節點的所有從節點裏面,會有一個從節點被選中;
  • 被選中的從節點會執行SLAVEOF no one命令,成爲新的主節點;
  • 新的主節點會撤銷所有對已下線主節點的槽指派,並將這些槽全部指派給自己;
  • 新的主節點向集羣廣播一條PONG消息,這條PONG消息可以讓集羣中的其他節點立即知道這個節點已經由從節點變成了主節點,並且這個主節點已經接管了原本由已下線節點負責處理的槽;
  • 新的主節點開始接收和自己負責處理的槽有關的命令請求,故障轉移完成。

總結

Redis的集羣就這些內容了。一般來說小公司使用Sentinel就夠了,大公司都會使用Cluster模式。
瞭解完這個你應該對怎麼設計一個10億數據的讀寫的Redis架構有個概念。
細節的東西還是非常多的,也需要大家多花點時間去看書學習的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章