redis 哨兵 sentinel master slave 連接建立過程


* 本文講哨兵模式按照配置運行起來之后 哨兵 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,s1-m2,s1-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 中創建連接,發送消息是異步的,所以整個連接順序可能不會是上邊講的那樣,可能會交叉着連接,但是正常的話都會達到一個收斂狀態

 

有錯誤歡迎及時指正!!!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM