天天看點

Redis Sentinel 源碼:Redis的高可用模型分析

摘要:本文通過對Redis Sentinel源碼的了解,詳細說明Sentinel的代碼實作方式。

Redis Sentinel 是Redis提供的高可用模型解決方案。Sentinel可以自動監測一個或多個Redis主備執行個體,并在主執行個體當機的情況下自動實行主備倒換。本文通過對Redis Sentinel源碼的了解,詳細說明Sentinel的代碼實作方式。

Sentinel使用Redis核心相同的事件驅動代碼架構, 但Sentinel有自己獨特的初始化步驟。在這篇文章裡,會從Sentinel的初始化、Sentinel主時間事件函數、Sentinel 網絡連接配接和Tilt模式三部分進行講解。

我們可以通過redis-sentinel <path-to-configfile> 或者 redis-server <path-to-configfile> --sentinel 這兩種方式啟動并運作Sentinel執行個體,這兩種方式是等價的。在Redis server.c 的main函數中,我們會看到Redis如何判斷使用者指定以Sentinel方式運作的邏輯:

其中checkForSentinelMode函數會監測以下兩種條件:

程式使用redis-sentinel可執行檔案執行。

程式參數清單中有--sentinel 标志。

以上任何一種條件成立則Redis會使用Sentinel的方式運作。

在Redis 判斷是否以Sentinel的方式運作以後,我們會看到如下代碼段:

在initSentinelConfig函數中,會使用Sentinel特定的端口(預設為26379)來替代Redis的預設端口(6379)。另外,在Sentinel模式下,需要禁用伺服器運作保護模式。

與此同時,initSentinel函數會做如下操作:

1、使用Sentinel自帶的指令表去替代Redis伺服器原生的指令. Sentinel 支援的指令表如下:

2、初始化Sentinel主狀态結構,Sentinel主狀态的定義及注釋如下。

其中masters字典指針中的每個值都對應着一個Sentinel檢測的主執行個體。

在讀取配置資訊後,Redis伺服器主函數會調用sentinelIsRunning函數, 做以下幾個工作:

檢查配置檔案是否被設定,并且檢查程式對配置檔案是否有寫權限,因為如果Sentinel狀态改變的話,會不斷将自己目前狀态記錄在配置檔案中。

如果在配置檔案中指定運作ID,Sentinel 會使用這個ID作為運作ID,相反地,如果沒有指定運作ID,Sentinel會生成一個ID用來作為Sentinel的運作ID。

對所有的Sentinel監測執行個體産生初始監測事件。

Sentinel 使用和Redis伺服器相同的事件處理機制:分為檔案事件和時間事件。檔案事件處理機制使用I/O 多路複用來處理伺服器端的網絡I/O 請求,例如用戶端連接配接,讀寫等操作。時間處理機制則在主循環中周期性調用時間函數來處理定時操作,例如伺服器端的維護,定時更新,删除等操作。Redis伺服器主時間函數是在server.c中定義的serverCron函數,在預設情況下,serverCron會每100ms被調用一次。在這個函數中,我們看到如下代碼:

其中當伺服器以sentinel模式運作的時候,serverCron會調用sentinelTimer函數,來運作Sentinel中的主邏輯,sentinelTimer函數在sentinel.c中的定義如下:

Sentinel Timer函數會做如下幾個操作:

檢查Sentinel目前是否在Tilt 模式(Tilt模式将會在稍後章節介紹)。

檢查Sentinel與其監控主備執行個體,以及其他Sentinel執行個體的連接配接,更新目前狀态,并在主執行個體下線的時候自動做主備倒換操作。

檢查回調腳本狀态,并做相應操作。

更新伺服器頻率(調用serverCron函數的頻率),加上一個随機因子,作用是防止監控相同主節點的Sentinel在選舉Leader的時候時間沖突,導緻選舉無法産生絕對多的票數。

其中SentinelHandleDictOfRedisInstances函數的定義如下:

SentinelHandleDictOfRedisInstances函數主要做的工作是:

調用sentinelHandleDictOfRedisInstance函數處理Sentinel與其它特定執行個體連接配接,狀态更 新,以及主備倒換工作。

如果目前處理執行個體為主執行個體,遞歸調用SentinelHandleDictOfRedisInstances函數處理其下屬的從執行個體以及其他監控這個主執行個體的Sentinel。

在主備倒換成功的情況下,更新主執行個體為更新為主執行個體的從執行個體。

其中在sentinelHandleRedisInstance的定義如下:

這個函數會做以下兩部分操作:

1、檢查Sentinel和其他執行個體(主備執行個體以及其他Sentinel)的連接配接,如果連接配接沒有設定或已經斷開連接配接,Sentinel會重試相對應的連接配接,并定時發送響應指令。 需要注意的是:Sentinel和每個主備執行個體都有兩個連接配接,指令連接配接和釋出訂閱連接配接。但是與其他監聽相同主備執行個體的Sentinel隻保留指令連接配接,這部分細節會在網絡章節單獨介紹。

2、第二部分操作主要做的是監測主備及其他Sentinel執行個體,并監測其是否在主觀下線狀态,對于主執行個體來說,還要檢測是否在客觀下線狀态,并進行相應的主備倒換操作。

需要注意的是第二部分操作如果Sentinel在Tilt模式下是忽略的,下面我們來看一下這個函數第二部分的的具體實作細節。

sentinelCheckSubjectivelyDown 函數會監測特定的Redis執行個體(主備執行個體以及其他Sentinel)是否處于主觀下線狀态,這部分函數代碼如下:

主觀下線狀态意味着特定的Redis執行個體滿足以下條件之一:

在執行個體配置的down_after_milliseconds時間内沒有收到Ping的回複。

Sentinel認為執行個體是主執行個體,但收到執行個體為從執行個體的回複,并且上次執行個體角色回複時間大于在執行個體配置的down_after_millisecon時間加上2倍INFO指令間隔。

如果任何一個條件滿足,Sentinel會打開執行個體的S_DOWN标志并認為執行個體進入主觀下線狀态。

主觀下線狀态意味着Sentinel主觀認為執行個體下線,但此時Sentinel并沒有詢問其他監控此執行個體的其他Sentinel此執行個體的線上狀态。

sentinelCheckObjectivelyDown 函數會檢查執行個體是否為客觀下線狀态,這個操作僅僅對主執行個體進行。sentinelCheckObjectivelyDown函數定義如下:

這個函數主要進行的操作是循環檢視監控此主執行個體的其他Sentinel SRI_MASTER_DOWN 标志是否打開,如果打開則意味着其他特定的Sentinel認為主執行個體處于下線狀态,并統計認為主執行個體處于下線狀态的票數,如果票數大于等于主執行個體配置的quorum值,則Sentinel會把主執行個體的SRI_O_DOWN标志打開,并認為主執行個體處于客觀下線狀态。

sentinelStartFailoverIfNeeded函數首先會檢查執行個體是否處于客觀下線狀态(SRI_O_DOWN标志是否打開),并且在2倍主執行個體配置的主備倒換逾時時間内沒有進行主備倒換工作,Sentinel會打開SRI_FAILOVER_IN_PROGRESS标志并設定倒換狀态為SENTINEL_FAILOVER_STATE_WAIT_START。并開始進行主備倒換工作。主備倒換的細節将在主備倒換的章節裡介紹。

上文提到每個Sentinel執行個體會維護與所監測的主從執行個體的兩個連接配接,分别是指令連接配接(Command Connection)和釋出訂閱連接配接(Pub/Sub Connection)。但是需要注意的是,Sentinel和其他Sentinel之間隻有一個指令連接配接。下面将分别介紹指令連接配接和釋出訂閱連接配接的作用。

Sentinel維護指令連接配接是為了與其他主從執行個體以及Sentinel執行個體通過發送接收指令的方式進行通信,例如:

Sentinel會預設以每1s間隔發送PING 指令給其他執行個體以主觀判斷其他執行個體是否下線。

Sentinel會通過Sentinel和主執行個體之間的指令連接配接每隔10s發送INFO指令給主從執行個體以得到主執行個體和從執行個體的最新資訊。

在主執行個體下線的情況下,Sentinel會通過Sentinel和從執行個體的指令連接配接發送SLAVEOF NO ONE指令給標明的從執行個體進而使從執行個體提升為新的主節點。

Sentinel會預設每隔1s發送is-master-down-by-addr指令以詢問其他Sentinel節點關于監控的主節點是否下線。

在sentinel.c中的sentinelReconnectInstance函數中,指令連接配接的初始化如下:

Sentinel維護和其他主從節點的釋出訂閱連接配接作用是為了獲知其他監控相同主從執行個體的Sentinel執行個體的存在,并且從其他Sentinel執行個體中更新對所監控的主從執行個體以及發送的Sentinel執行個體的認知。例如在主備倒換完成後,其他Sentinel通過讀取領頭的Sentinel的頻道消息來更新新的主節點的相關資訊(位址,端口号等)。

Sentinel在預設每隔2秒鐘會發送Hello消息包到其對應的主從執行個體的__sentinel__:hello頻道中。Hello消息格式如下:

__sentinel_:hello <sentinel位址> <sentinel端口号> <sentinel運作id> <sentinel配置紀元> <主節點名字 > <主節點位址> <主節點端口号> <主節點配置紀元>

當Sentinel通過訂閱連接配接收到其他Sentinel發送的的Hello包時,會更新對主從節點以及S發送Sentinel的認知,如果收到自己發送的Hello包,則簡單的丢棄不做任何處理。這部分代碼邏輯是在sentinel.c中的sentinelProcessHelloMessage函數中定義的,由于篇幅原因在這裡不做詳細介紹。

在sentinel.c中的sentinelReconnectInstance函數中,釋出訂閱連接配接初始化如下:

is-master-down-by-addr 指令

Sentinel會預設每隔1s通過指令連接配接發送is-master-down-by-addr指令以詢問其他Sentinel節點關于監控的主節點是否下線。另外,在主執行個體下線的情況下,Sentinel之間也通過is-master-down-by-addr指令來獲得投票并選舉領頭Sentinel。is-master-down-by-addr格式如下:

is-master-down-by-addr: <主執行個體位址> <主執行個體端口号> <目前配置紀元> <運作ID>

如果不是在選舉領頭Sentinel過程中, <runid>項總為*,相反地,如果在Sentinel向其他Sentinel發送投票請求情況下,<runid>項為自己的運作id。這部分代碼如下:

is-master-down-by-addr的指令回複格式如下:

<主節點下線狀态>

<領頭Sentinel運作ID >

<領頭Sentinel配置紀元>

Sentinel在收到其他Sentinel指令回複後,會記錄其他Sentinel回複的主執行個體線上狀态資訊,以及在選舉領頭Sentinel過程中的投票情況,這部分的代碼邏輯定義在sentinel.c中的sentinelReceiveIsMasterDownByReply函數:

Sentinel的Tilt模式會在以下兩種情況下開啟:

Sentinel程序被阻塞超過SENTINEL_TILT_TRIGGER時間(預設為2s),可能因為程序或系統I/O(記憶體,網絡,存儲)請求過多。

系統時鐘調整到之前某個時間值。

Tilt模式是一種保護機制,處于該模式下Sentinel除了發送必要的PING及INFO指令外,不會主動做其他操作,例如主備倒換,标志主觀、客觀下線等。但可以通過INFO 指令及釋出訂閱連接配接的HELLO消息包來擷取外界資訊并對自身結構進行更新,直到SENTINEL_TILT_PERIOD時長(預設為30s)結束為止,我們可以認為Tilt模式是Sentinel的被動模式。

判斷Tilt模式的代碼邏輯定義如下:

繼續閱讀