* 本文講哨兵模式按照配置運作起來之後 哨兵 master slave 之間連接配接建立過程. 我覺得了解了建立過程以及正常運作時的一個連接配接拓撲 對了解整個監視過程非常有幫助。因為之後的故障轉移就是繼續維持一個這樣的拓撲。
1 假設有三個master以及各自的兩個從節點: m1(r1, r2), m2(r3, r4), m3(r5, r6) 這裡為了友善展示 将主節點的從節點畫在一起
2 現在啟動三個sentinel s1 s2 s3, 每個sentinel執行個體都監視m1(r1, r2), m2(r3, r4), m3(r5, r6). 我們先看s1與 m1(r1, r2), m2(r3, r4), m3(r5, r6) 建立連接配接實作監視的過程。
1) 當 redis 以 sentinel 模式啟動以後,會建立一個 struct sentinelState 結構的全局執行個體 sentinel,也就是s1.這個結構存放為監視的m1(r1, r2), m2(r3, r4), m3(r5, r6) 而建立的 struct sentinelRedisInstance 執行個體.
2) 假設sentinel.conf 中配置的監視 m1,m2,m3 三個master (s2, s3也如此)
sentinel monitor m1 127.0.0.1 6379 2
sentinel monitor m2 127.0.0.1 7379 2
sentinel monitor m3 127.0.0.1 8379 2
sentinel 啟動時會首先調用 createSentinelRedisInstance 建立三個sentinelRedisInstance執行個體用于處理與m1,m2,m3的連接配接,起名為 s1-m1,s2-m2,s3-m3.這三個執行個體已經分别儲存了m1,m2,m3的 ip及port 以及一些必要的資料結構 這裡不展開
3) 當s1-m1,s2-m2,s3-m3 執行個體建立成功以後并沒有與m1,m2,m3建立連接配接而是隻是準備好了處理m1,m2,m3連接配接的結構,緊接着 sentinel 會在每個tick中調用 sentinelTimer, 這那裡周遊他們,然後分别開始建立與m1,m2,m3的連接配接。過程是一樣的,這裡以s1-m1 與 m1(r1,r2)建立連接配接的過程為例
首先 s1-m1 與 m1 建立兩條連接配接,分别是 link->cc, link->pc. 通過link->cc 向m1 發送 info 指令 和 ping 指令, 通過 pc 向m1 注冊 SENTINEL_HELLO_CHANNEL "__sentinel__:hello" 頻道并定時釋出消息,建立完連接配接之後的結構如下:
注:為了友善s1-m1 與 m1建立過程中沒有畫出m1,m2,m3 的副本 r1-r6
以上s1-m1 與 m1 之間的連接配接是雙向的,由于連接配接是異步的,在連接配接建立時已經注冊好了回調函數。建立link->cc連接配接時,注冊了 sentinelLinkEstablishedCallback 以及 sentinelDisconnectCallback 并且馬上向m1 發送ping
建立 link->pc 時 首先注冊 __sentinel__:hello 頻道,并且設定回調函數 sentinelReceiveHelloMessages。此時 m1 如果沒有hello頻道會建立該頻道。注意sentinel 建立的sentinelRedisInstance 隻與master及slave才會建立link->pc 連接配接, 不會與其它sentinel建立
也就是說隻有master和slave上有 __sentinel__:hello 頻道, sentinel執行個體身上是沒有 __sentinel__:hello 頻道的。
4) s1-m1 與 m1建立兩條連接配接之後 同樣是在同一個 tick 裡 sentinelTimer裡 會向 m1 通過link->cc 發送 info 和 ping 指令,通過 link->pc 向 m1 publish hello消息. sentinel 正是通過處理 m1 收到這三條消息之後傳回來的資訊建立與m1的副本及其他sentinel之間的連接配接關系的。當然目前階段還沒有其他sentinel(s2, s3)參與,是以s1在目前階段通過info消息處理m1的副本r1,r2的連接配接.
info: (此處注明,處理m1回傳資訊中的副本slaves 隻是info處理函數得一部分功能,之後的故障遷移也會通過該處理函數處理) 當m1 首次收到 s1-m1的info消息後,傳回給s1的消息裡帶有m1目前slaves(r1, r2)的ip 端口等資訊。sentnel的info回調函數正是通過這些資訊處理的。處理步驟如下:
11) info的回調函數 sentinelInfoReplyCallback 被調用 根據info資訊中的slave字段,找到slave 的ip 和 port
22)檢查s1-m1 中是否建立了處理r1和r2對應的sentinelRedisInstance執行個體,如果沒有建立則建立,這樣在本次回調處理後s1-m1中會建立兩個處理r1,r2的執行個體 叫做s1-r1,s1-r2.
原諒這個糟糕的圖,表達的東西應該是沒錯...
s1 通過s1-m1發送給m1的info資訊的回調建立s1-r1,s1-r2執行個體的過程 與 剛開始s1建立s1-m1,s1-m2,s1-m3的過程類似,都是隻建立執行個體沒有建立連接配接,連接配接由每個tick 調用sentinelTimer 去建立。
由上圖可以發現,s1-r1,s1-r2 屬于s1-m1執行個體,實際上s1-m1執行個體中有一個map用來存放s1-r1,s1-r2執行個體,與m1(r1,r2)對應.同時還有一個sentinel map 同來存放其他監視m1的sentinel(s2,s3).
s1-r1,s1-r2執行個體在sentinel info回調建立完之後,在sentinelTimer 中去建立于 副本r1,r2的連接配接,連接配接同樣是兩個類似于s1-m1和m1之間的兩條連接配接,這裡不再贅述,連接配接建立完之後如下:
注: s1-r1和s1-r2所在的紅色框隻是邏輯上的屬于s1-m1中的一個map 别無他意,同理之後的s1-m1中的sentinel map亦如此
5) 至此,以s1-m1與m1為例建立連接配接的過程結束. 之後再每個tick 裡 s1-m1:m1, s1-r1:r1, s1-r2:r2 之間會按照條件一直發送 info , ping, 釋出hello, 來保持連接配接,進而實作s1 監視m1(r1, r2).
同理 s2-m2(r3, r4) 與 m2, s3-m3 與 m3(r5, r6) 的建立過程與 s1-m1 與 m1 建立過程是一樣的.
注: 因為我們是以 s1-m1 與 m1(r1, r2) 建立連接配接為例展開的,實際上由于是異步,s2-m2 與 m2(r3,r4),s3-m3 與 m3(r5,r6) 連接配接建立不一定等到 1-m1與m1(r1, r2) 全部建立連接配接結束之後才開始,可能會穿插這進行,但最後都會達到收斂狀态
即連接配接全部建立完。
下面附上s1 與m1(r1, r2), m2(r3, r4), m3(r5, r6)全部建立完連接配接之後的結構:
3 以上是s1 監視m1(r1, r2), m2(r3, r4), m3(r5, r6) 後的結果并沒有其他sentinel(s2, s3) 加入監視。
現在s2 也開始監視m1(r1, r2), m2(r3, r4), m3(r5, r6) ,相對于s2 本身它自己與m1(r1, r2), m2(r3, r4), m3(r5, r6)連接配接的過程以及方式以及建立之後的結果與s1 是一樣的
但是 s2 加入之後對 s1 的結構會有影響,同時,當 s2 建立好與 s1 一樣的結構之後,同樣會感覺到s1,此時s1 對 s2 結構的改變與s2對s1結構的改變是一樣的。
從 s2 的加入對s1 結構的改變開始講起.
3.1 我們前面講到當s1完成與 m1(r1, r2), m2(r3, r4), m3(r5, r6) 建立連接配接之後 在 m1(r1, r2), m2(r3, r4), m3(r5, r6)的各個redis執行個體上邊都建立了 __sentinel__:hello 頻道 并且 s1 的各個 sentinelRedisInstance 執行個體都會定期發送 publish 消息,
同樣當 s2 中的執行個體與 建立完s2-m1, s2-m2, s2-m3 執行個體 并且與 m1,m2,m3建立連接配接之後 也會向 m1,m2,m3 publish 消息. 那麼此時 s1 會受到s2 中的執行個體向m1,m2,m3的hello頻道發的消息,其實s1 和 s2 通過對應的hello頻道的消息感覺對方的存在。我們先來看實 例publish 了什麼 再看收到這些publish消息後是如何處理的.
publish: s1 通過各個sentinelRedisInstance執行個體 向master及其副本publish消息,這個過程是在tick時完成的。publish的消息含有8個字段
分别是,s1所在的redis 執行個體本身的ip,port,myid,及s1此時的curent_epoch 這是前四個字段,另外四個字段:如果目前執行個體是master執行個體如s1-m1則直接發送其存儲的m1的ip和port以及s1-m1的名字和其目前的config_epoch.如果目前執行個體是s1-m1的副本執行個體s1-r1或者s1-r2則找到->master 即s1-m1發送同樣的字段。s1-m1 publish 到 m1, s1-m2 publish 到 m2, s1-m3 publish 到 m3。當隻有s1完成監控時,各個被監控的redis執行個體的hello頻道隻有s1建立的對應的執行個體完成注冊。
當 s2 監聽過程執行到類似s1 的下面過程後(将下圖中s1 改成 s2)
下一個tick, 分别開始建立與m1,m2,m3的連接配接,并在連接配接建立完成之後向m1,m2,m3 publish消息,此時,由于s1中的各個執行個體已經注冊了hello的回調,并且參數就是自己執行個體本身,是以當s2 的 s2-m1,s2-m2,s2-m3執行個體 分别向m1,m2,m3 publish 消息後,對應s1會收到通知,處理s2 publish的消息,由s1-m1, s1-m2, s1-m3 分别處理。
注: 此時的s1結構是上邊建立完成連接配接之後的結構,這裡為了顯示簡便,沒有全部畫出
當s2 完成了建立s2-m1, s2-m2,s2-m3,并在下一個tick建立與m1,m2,m3的連接配接後,分别如上圖所示 publish:msg1, msg2, msg3到m1,m2,m3
msg1, msg2, msg3 三個消息的前四個字段是相同的,都是s2所在伺服器執行個體的ip,port,myid,及s2此時的curent_epoch,之後的四個字段是各自s2-m*執行個體的名字以及各自的config_epoch以及所連接配接m1,m2,m3的ip, port。
publish 到m1,m2,m3後 s1中的回調會分别用 s1-m1,s1-m2,s1-m3 處理各自收到的msg1,msg2,msg3。
現在再回到s1,s1 所在伺服器通過調用回調函數 sentinelProcessHelloMessage 處理連接配接上的資料,即s1-m1,s1-m2,s1-m3各自的 link->pc 連接配接處理msg1, msg2, msg3.
現在隻看回調通過s1-m1處理msg1.其他的跟此一樣.
首先通過msg1消息中的mastername也就是s2-m1的名字來找到s1中的對應的s1-m1執行個體,因為當不同sentinel監視同一個master執行個體時,建立的對應的sentinelInstance執行個體的名字必須是一樣的
例如 sentinel monitor m1 127.0.0.1 6379 2. s1-m1.s2-m3.執行個體的名字都是 "m1".
找到了s1-m1之後,便根據msg1中過的s2的ip和port和s2所在伺服器市裡的runid(myid) 查找s1-m1是否建立了處理s2的執行個體s1-s2,如果沒建立,就建立一個. 第一次當時是沒建立是以還在s1-m1的sentinel map中建立一個s1-m1-s2執行個體
然後再下一個tick 與 s2 所在的伺服器執行個體連接配接,之前的s1,s2的各個執行個體建立的連接配接邏輯上是歸各自執行個體的,但是s1-s2建立的連接配接邏輯上就是與s2 所在的伺服器直接相連的
注,上圖s1-s2所在紅圈中表示在之前基礎上添加的新執行個體
注意s1-m1-s2執行個體與s2伺服器所在執行個體隻有一條link->cc連接配接 沒有link->pc連接配接。以上表示s1-m1處理msg1之後的與s2連接配接建立過程,同理s1-m2,s1-m3與此過程相同,是以當s1中的master執行個體分别處理完s2的master執行個體釋出的消息之後結構變成
注: 下邊圖就是上邊的s1 部分 結合着看
當s1 處理完s2發來的msg1,2,3 之後,s1的每個master執行個體中的兩個map 一個slave map 一個sentinel map 裡分别有了 {s1-r1, s1-r2}, {s1-m1-s2} 執行個體,如果之後的s3 以同樣的方式開始監視
m1(r1, r2), m2(r3, r4), m3(r5,r6).則s1 的master執行個體中的sentinel map裡會繼續增加一個 {s1-m1-s2, s1-m1-s3}.
這裡有一個細節,上圖中,s1 的 三個master執行個體中的sentinel map 中都會有s*-m*-s2, 也就是會有三個連接配接去連 s2 所在的伺服器執行個體,目前版本的redis燒餅模式實作認為這是多餘的,是以,如果當s1 中的任何master執行個體在處理其msg*的消息中建立了s*-m*-s2 之後,其他的master執行個體處理自己的msg*消息時,首先會查找有沒有其他master執行個體已經建立了對應的sentinel執行個體,有的話就共享已經建立的sentinel執行個體中的link->cc連接配接. 因為例如s1 當其建立完一個sentinelInstance執行個體後,在下一個tick中會為這個執行個體建立連接配接。當其處理msg*消息時,會先檢查其他master執行個體是否已經建立的sentinel執行個體,有的話,就直接把目前sentienl執行個體的link->cc連接配接直接用已經建立的sentienl執行個體的Link->cc指派,這樣的話,哨兵s1中的所有處理s2的哨兵執行個體公用一個連接配接,這樣就節省了大量的連接配接建立.
這樣一來上圖左邊的s1-m*-s2的三條連接配接實際上隻有一條,他們共享最快與s2建立連接配接的sentinel執行個體的link->cc 與s2通信。用官方的話講,加入5個哨兵 每個都監視同樣的100個master 那樣的話 所建立的sentinel 執行個體一共隻會建立5條和5個哨兵的連接配接 而不是500條。
到現在為止,s1 處理完了當 s2 也加入監視行列之後 對自己結構的影響,同理s2 處理s1 對自己的結構影響也是一樣的,這時候如果s3 也加入監視,建立完連接配接之後,整個系統就會達到收斂狀态,s2,s3的結構以及與m1(r1, r2), m2(r3, r4), m3(r5,r6) 的連接配接與s1的是一樣的 這裡就不在贅述 就是照着s1 在整張圖上補充上 s2, s3 的結構和連接配接 就是整個的收斂狀态.
*******另外這裡補充一下。本文舉得例子s1,s2,s3 三個哨兵指的是 sentinelState 執行個體,當redis以哨兵模式啟動時,會直接建立一個全局 sentinelState。而 這些執行個體所在的 server 執行個體就是redis伺服器。如果上邊沒有說清,這裡補充一下 希望不要搞混。
同時也要再強調一下,因為哨兵中建立完執行個體之後 在每次tick 中建立連接配接,發送消息是異步的,是以整個連接配接順序可能不會是上邊講的那樣,可能會交叉着連接配接,但是正常的話都會達到一個收斂狀态
有錯誤歡迎及時指正!!!