Redis学习(八)哨兵机制
? ? ? ? 哨兵(Sentinel)是redis高可用性的解決方案,由一個或多個哨兵實例組成的哨兵系統,可以監視任意多個主服務器,以及這些主服務器屬下的從服務器。
當被監視的主服務器下線時,根據某些規則挑選一個從服務器,作為新的主服務器。接著,其他從服務器會向新的主服務器發送復制指令,并且完成復制。同時,哨兵會監視下線的原主服務器,在它重新上線后,將它也置為從服務器。
目錄
Sentinel啟動與初始化
與服務器通信
故障檢測與處理
總結
Sentinel啟動與初始化
1、啟動命令
redis-sentinel /path/to/sentinel/config/sentinel.conf或 redis-server/path/to/sentinel/config/sentinel.conf --sentinel啟動一個哨兵可以使用這兩個命令中的一個,效果完全相同,當一個哨兵啟動時,需要完成以下步驟:
2、初始化服務器
Sentinel 本質上只是一個運行在特殊模式下的 Redis 服務器,所以啟動 Sentinel 的第一步,就是初始化一個普通的 Redis 服務器, 具體的步驟可見Redis客戶端/服務器。
不過, 因為 Sentinel 執行的工作和普通 Redis 服務器執行的工作不同, 所以 Sentinel 的初始化過程和普通 Redis 服務器的初始化過程并不完全相同。比如,普通服務器在初始化時會通過載入 RDB 文件或者 AOF 文件來還原數據庫狀態,但是因為 Sentinel 并不使用數據庫,所以初始化 Sentinel 時就不會載入 RDB 文件或者 AOF 文件。
下表為 Redis 服務器在 Sentinel 模式下運行時, 服務器各個主要功能的使用情況:
| 數據庫和鍵值對方面的命令, 比如?SET?、?DEL?、?FLUSHDB?。 | 不使用。 |
| 事務命令, 比如?MULTI?和?WATCH?。 | 不使用。 |
| 腳本命令,比如?EVAL?。 | 不使用。 |
| RDB 持久化命令, 比如?SAVE?和?BGSAVE?。 | 不使用。 |
| AOF 持久化命令, 比如?BGREWRITEAOF?。 | 不使用。 |
| 復制命令,比如?SLAVEOF?。 | Sentinel 內部可以使用,但客戶端不可以使用。 |
| 發布與訂閱命令, 比如?PUBLISH?和?SUBSCRIBE?。 | SUBSCRIBE?、?PSUBSCRIBE?、?UNSUBSCRIBE?PUNSUBSCRIBE?四個命令在 Sentinel 內部和客戶端都可以使用, 但?PUBLISH?命令只能在 Sentinel 內部使用。 |
| 文件事件處理器(負責發送命令請求、處理命令回復)。 | Sentinel 內部使用, 但關聯的文件事件處理器和普通 Redis 服務器不同。 |
| 時間事件處理器(負責執行?serverCron?函數)。 | Sentinel 內部使用, 時間事件的處理器仍然是?serverCron?函數,?serverCron?函數會調用?sentinel.c/sentinelTimer?函數, 后者包含了 Sentinel 要執行的所有操作。 |
3、使用 Sentinel 專用代碼
啟動 Sentinel 的第二個步驟就是將一部分普通 Redis 服務器使用的代碼替換成 Sentinel 專用代碼。
- 普通 Redis 服務器使用?REDIS_SERVERPORT?常量的值作為服務器端口:#define REDIS_SERVERPORT 6379 ;而 Sentinel 則使用?REDIS_SENTINEL_PORT?常量的值作為服務器端口:#define REDIS_SENTINEL_PORT 26379
- 普通 Redis 服務器使用?redis.c/redisCommandTable?作為服務器的命令表,而 Sentinel 則使用?sentinel.c/sentinelcmds?作為服務器的命令表
sentinelcmds?命令表也解釋了為什么在 Sentinel 模式下, Redis 服務器不能執行諸如?SET?、?DBSIZE?、?EVAL?等等這些命令 —— 因為服務器根本沒有在命令表中載入這些命令:?PING?、 SENTINEL?、?INFO?、?SUBSCRIBE?、?UNSUBSCRIBE?、?
PSUBSCRIBE?和?PUNSUBSCRIBE?這七個命令就是客戶端可以對 Sentinel 執行的全部命令了。
4、初始化?Sentinel 狀態
在應用了 Sentinel 的專用代碼之后,?服務器會初始化一個?sentinel.c/sentinelState?結構(后面簡稱“Sentinel 狀態”),這個結構保存了服務器中所有和 Sentinel 功能有關的狀態 (服務器的一般狀態仍然由?redis.h/redisServer?結構保存):
struct sentinelState {// 當前紀元,用于實現故障轉移uint64_t current_epoch;// 保存了所有被這個 sentinel 監視的主服務器// 字典的鍵是主服務器的名字// 字典的值則是一個指向 sentinelRedisInstance 結構的指針dict *masters;// 是否進入了 TILT 模式?int tilt;// 目前正在執行的腳本的數量int running_scripts;// 進入 TILT 模式的時間mstime_t tilt_start_time;// 最后一次執行時間處理器的時間mstime_t previous_time;// 一個 FIFO 隊列,包含了所有需要執行的用戶腳本list *scripts_queue;} sentinel;5、Sentinel 監視初始化
Sentinel 狀態中的?masters?字典記錄了所有被 Sentinel 監視的主服務器的相關信息, 其中:
- 字典的鍵是被監視主服務器的名字。
- 而字典的值則是被監視主服務器對應的?sentinelRedisInstance?結構。
每個?sentinelRedisInstance?結構(后面簡稱“實例結構”)代表一個被 Sentinel 監視的 Redis 服務器實例(instance),這個實例可以是主服務器、從服務器、或者另外一個 Sentinel 。
typedef struct sentinelRedisInstance {// 標識值,記錄了實例的類型,以及該實例的當前狀態int flags;// 實例的名字// 主服務器的名字由用戶在配置文件中設置// 從服務器以及 Sentinel 的名字由 Sentinel 自動設置// 格式為 ip:port ,例如 "127.0.0.1:26379"char *name;// 實例的運行 IDchar *runid;// 配置紀元,用于實現故障轉移uint64_t config_epoch;// 實例的地址sentinelAddr *addr;// SENTINEL down-after-milliseconds 選項設定的值// 實例無響應多少毫秒之后才會被判斷為主觀下線(subjectively down)mstime_t down_after_period;// SENTINEL monitor <master-name> <IP> <port> <quorum> 選項中的 quorum 參數// 判斷這個實例為客觀下線(objectively down)所需的支持投票數量int quorum;// SENTINEL parallel-syncs <master-name> <number> 選項的值// 在執行故障轉移操作時,可以同時對新的主服務器進行同步的從服務器數量int parallel_syncs;// SENTINEL failover-timeout <master-name> <ms> 選項的值// 刷新故障遷移狀態的最大時限mstime_t failover_timeout;// ...} sentinelRedisInstance;其中實例的地址addr?屬性是一個指向?sentinelAddr?結構的指針, 這個結構保存著實例的 IP 地址和端口號:
typedef struct sentinelAddr {char *ip;int port; } sentinelAddr;對 Sentinel 狀態的初始化將引發對?masters?字典的初始化,而masters字典的初始化是根據被載入的Sentinel配置文件來進行的。
# 載入master1 configure #####################sentinel monitor master1 127.0.0.1 6379 2sentinel down-after-milliseconds master1 30000sentinel parallel-syncs master1 1sentinel failover-timeout master1 900000# 載入master2 configure #####################sentinel monitor master2 127.0.0.1 12345 5sentinel down-after-milliseconds master2 50000sentinel parallel-syncs master2 5sentinel failover-timeout master2 450000那么 Sentinel 將為主服務器?master1?和主服務器?master2?創建如圖所示的實例結構,而這兩個實例結構又會被保存到 Sentinel 狀態的?masters?字典中。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
6、連向主服務器的網絡連接
初始化 Sentinel 的最后一步是創建連向被監視主服務器的網絡連接: Sentinel 將成為主服務器的客戶端, 它可以向主服務器發送命令, 并從命令回復中獲取相關的信息。
對于每個被 Sentinel 監視的主服務器來說, Sentinel 會創建兩個連向主服務器的異步網絡連接:
- 命令連接, 這個連接專門用于向主服務器發送命令,并接收命令回復。
- 訂閱連接, 這個連接專門用于訂閱主服務器的?__sentinel__:hello?頻道。
為什么有兩個連接?
在 Redis 目前的發布與訂閱功能中,被發送的信息都不會保存在 Redis 服務器里面,如果在信息發送時,想要接收信息的客戶端不在線或者斷線,那么這個客戶端就會丟失這條信息。
因此,為了不丟失?__sentinel__:hello?頻道的任何信息,Sentinel 必須專門用一個訂閱連接來接收該頻道的信息。
而另一方面, 除了訂閱頻道之外,Sentinel 還又必須向主服務器發送命令,以此來與主服務器進行通訊,所以Sentinel 還必須向主服務器創建命令連接。
并且因為 Sentinel 需要與多個實例創建多個網絡連接, 所以 Sentinel 使用的是異步連接。
與服務器通信
1、獲取主服務器信息
sentinel 默認會每10秒一次,給主服務器發送info命令,并且通過分析info命令,來判斷當前主服務器的信息。通過分析info命令,sentinel會得到兩方面的信息:
- 一是run_id域記錄的主服務器的運行ID以及其role域記錄的服務器角色
- 二是關于主服務器屬下所有從服務器的信息,每一個從服務器由slave字符串開頭,每行IP記錄一個IP地址、端口號、offset(用于與主服務器aof)、lag(延遲時間)等
因此,sentinel無需建立和從服務器的連接,也可以知道從服務器的情況。sentinel只需要將info獲得的返回結果,分析并更新到sentinel的sentinelState結構體的相應屬性即可。另外,sentinel在更新結構體時,還會分析每一個從服務器是否存在,如果是現有的則更新結構體,如果不是現有的則新增一個結構體。
? ? ? ? ??
從上述可知,主從服務器sentinelRedisInstance結構的主要區別:
2、獲取從服務器信息
當 sentinel 發現有新的從服務器,除了會為其創建實例結構,還會與其建立連接,連接也是包括訂閱連接和命令連接。且默認下,每10秒也會給從服務器發送info命令。
? ? ? ? ? ? ? ? ? ? ? ? ? ??
根據info命令,可以獲取從服務器的以下主要信息:從服務器運行ID、從服務器角色、主服務器ip與端口號、主從服務器連接狀態、從服務器優先級、從服務器的復制偏移量。
根據這些信息,Sentinel 會對從服務器的實例結構?sentinelRedisInstance?進行更新。
3、與主從服務器進行通信
默認情況下,sentinel會每兩秒一次,通過命令連接向被其監聽的所有主服務器和從服務器發送以下格式的命令:
publish __sentinel__:hello “<s_ip>,<s_port>,<s_runid>,<s_epoch>,<m_name>,<m_ip>,<m_port>,<m_epoch>”這條命令向?__sentinel__:hello頻道發送了一條信息,信息的內容中:
- s_ 開頭的參數記錄的是sentinel本身的信息:分別為Sentinel的IP地址、端口號、運行ID以及當前配置紀元
- m_ 開頭的是監聽的主服務器相關的信息:分別是主服務器的名字、IP地址、端口號以及當前的配置紀元
sentinel會和每個其監聽的服務器建立發布訂閱連接,對于每個與Sentinel連接的服務器,Sentinel即通過命令連接向服務器的__sentinel__:hello?頻道發送信息,又通過訂閱連接從服務器的__sentinel__:hello?頻道接受信息,Sentinel會一直監聽__sentinel__:hello 頻道,直到與服務器連接斷開為止。
對于監視同一服務器的多個Sentinel來說,一個Sentinel發送的信息會被其他的Sentinel接收到,這些信息會被用于更新其他Sentinel對發送信息的Sentinel的認知,也會被用于更新其他Sentinel對被監視服務器的認知。
sentinel接收到信息將與sentinel自己的運行id進行比對,如果一致則表示信息是自己發送的,sentinel將丟棄不處理信息;如果不一致表示是其他sentinel發送的命令,則會進行比對并更新相應內容(因為發布和訂閱都在同一頻道,所以自己會收到自己在此頻道發布的信息)。
4、更新sentinels字典
sentinel為主服務器建立的實例結構中的?sentinels?字典除了保存自身信息,還會保存其他所有同樣監視這個主服務器的sentinel的信息,字典的鍵是sentinel的ip:port,值是對應的?sentinel實例結構。
當一個 Sentinel 接收到其他的 Sentinel 發來的信息時(發送信息的為源Sentinel,接受信息的為目標Sentinel),目標Sentinel會從信息中分析并提取出以下信息:
- 源Sentinel的IP地址、端口號、運行ID以及當前配置紀元;
- 源Sentinel正在監視的主服務器的名字、IP地址、端口號以及當前的配置紀元
根據信息中的主服務器參數,目標Sentinel會在自己的Sentinel狀態的 masters 字典中查找相應的主服務器實例結構,然后根據提取出的 Sentinel 參數,檢查主服務器實例結構的 sentinels 字典中源Sentinel的實例結構是否存在。
- 如果源Sentinel的實例結構已經存在,那么對源Sentinel的實例結構進行更新。
- 如果源Sentinel的實例結構不存在,說明源Sentinel是剛剛才開始監視主服務器的新的Sentinel,目標Sentinel會為源Sentinel創建一個新的實例結構,并將這個實例結構添加到 sentinels 字典里面,完成對于新的Sentinel的認知。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
由上可以看出,一個Sentinel可以通過分析接收到的頻道信息獲知其他Sentinel的存在,并通過發送頻道信息來讓其他Sentinel知道自己的存在,所以用戶在使用Sentinel的時候并不需要提供各個Sentinel的地址信息,監視一個主服務器的各個Sentinel能自動互相發現對方的存在。
5、創建連向其他sentinel的連接
當sentinel發現其他sentinel,不僅會記錄其結構信息,還會建立一個命令連接,而新的sentinel也會向該sentinel建立命令連接。最終監視主服務器的多個sentinel將形成網絡。
另外,sentinel不會和其他sentinel建立訂閱連接,因為不需要訂閱相關信息。sentinel之間發送命令和訂閱信息,只需要互相用命令連接發送即可。
故障檢測與處理
1、檢測主觀下線狀態
默認情況下,sentinel會每秒1次,向所有與它創建了命令連接的實例(包括主服務器、從服務器、其他Sentinel)發送ping命令,通過實例返回回復判斷實例是否在線。
有效的回復包括pong、loading和mastdown,無效的回復包括超時的回復以及前三個之外的回復。
在sentinel的配置文件中,down_after_milliseconds指定了sentinel判斷主觀下線的方式,主服務器連續沒給出正確的回復超過這個設定的時間,則表示該主服務器被這個sentinel認定為主觀下線。
此時,sentinel會在自身記錄主服務器的flags加上一個標簽,使主服務器此時的flags標簽變為SRI_MASTER | SRI_S_DOWN。
配置主觀下線的down_after_milliseconds,不僅sentinel會對該主服務器進行這樣的判斷,也同樣會將該參數帶到該主服務器下的全部從服務器。
另外,不同的sentinel對同一個主服務器,設定的主觀下線的值可以不同。每個sentinel會按照自身的配置文件,來判斷檢測的主服務器是否主觀下線。
主觀下線狀態可理解為當前的Sentinel主觀的認為此服務器已經下線,但不代表這個服務器真的下線,是否真的下線還要看其他也同樣監視這個服務器的Sentinel是不是也同樣主觀認為此服務器已是下線狀態,當超過一定數量的監視者Sentinel都主觀認為此服務器處于下線狀態了,那么此時才會真的認為此服務器是已經下線的,也就是客觀下線狀態,然后進行故障轉移。
2、檢測客觀下線狀態
發送檢測主觀下線命令
當sentinel認定該主服務器主觀下線,會同時給其他幾個監測此主服務器的sentinel發送命令,確認在其他sentinel處是否也認為該主服務器主觀下線。命令格式為:
sentinel is-master-down-by-addr <ip><port> <current_epoch> <runid>其中,runid?如果是 * ,表示僅僅檢測該主服務器是否主觀下線,如果是用sentinel的運行id,則表示選舉領頭sentinel。
接收命令回復
當目標Sentinel收到源Sentinel的?is-master-down-by-addr命令后,目標sentinel會返回包含三個參數的multi bulk回復,如下:
- <down_state>:目標sentinel對該主服務器的檢查結果,1表示該sentinel也認為主服務器主觀下線,0表示未下線。
- <leader_runid>:*?或者?sentinel運行ID,*?表示僅僅用于檢測該主服務器是的下線狀態,sentinel id表示局部領頭Sentinel的運行ID用于選舉領頭的Sentinel。
- <leader_epoch>:領頭紀元,用于選舉領頭的Sentinel。
3、選舉領頭Sentinel
根據其他Sentinel的is-master-down-by-addr命令回復,當前Sentinel將統計其他同意主服務器已下線的Sentinel的數量,當這一數量達到配置指定的判斷客觀下線所需數量時(配置文件中設定的quorum的值),則sentinel將此主服務器的實例結構的flags屬性的 SRI_MASTER|SRI_S_DOWN|SRI_O_DOWN?打開,認為該主服務器客觀下線。
不同的sentinel可以設置不同的客觀下線數量,因此可能部分sentinel認為主服務器客觀下線,另一部分認為還沒客觀下線。
當一個主服務器被判定為客觀下線,監視該主服務器的各個sentinel,會協商選舉一個領頭sentinel,對下線主服務器進行故障轉移工作。具體方式如下:
根據命令請求的先后順序不同,可能會有某個Sentinel的sentinelis-master-down-by-addr命令比其他Sentinel都更快到達,從而在這次領頭選舉中勝出,然后領頭sentinel將對主服務器進行故障轉移。
需要注意的是,Sentinel發送的命令還是有可能會同時到達的,導致沒法選取超過半數的領頭,所以Redis這里采取的是其改進的raft分布式一致性算法,若沒有選舉出領頭,則會讓所以參與選舉的候選Sentinel都隨機的睡眠一段時間,先蘇醒的Sentinel會第一時間發送選舉的命令,所以最終還是會選出領頭的Sentinel。
4、故障轉移
選舉領頭后,領頭sentinel將進行故障轉移,工作包括:
(1)選出新主服務器
sentinel會將下線的主服務器的從服務器列表中,選出一個從服務器,選擇規則如下:
- 不考慮已經下線和斷線的從服務器,以保證選出的是正常在線的。
- 不考慮最近5秒內沒有回復過領頭sentinel的info命令的從服務器,以保證選出的是成功通信的。
- 不考慮所有和已下線主服務器斷開連接超過down-after-millisecond*10毫秒的從服務器,以保證選出的是數據最新的。
- 將剩余的從服務器,按照從服務器優先級選擇主服務器。
- 如果優先級一樣,則按照偏移量最大的選擇。
- 如果優先級和偏移量都一樣的,按照運行ID最小的作為主服務器。
選出主服務器后,sentinel給其發送slaveof no one命令,讓其變成主服務器。并且,會每秒一次的頻率發送info命令給新主服務器(正常是10秒一次發送info),直到回復中role從slave變成master,表示升級成功。
(2)修改其他從服務器的復制目標
即給其余的從服務器,發送slaveof <new_ip> <new_port>,讓其余的從服務器重新選擇新的主服務器執行復制操作。
(3)將舊主服務器變成從服務器
由于其已經下線,無法直接發送slaveof命令,sentinel會將其保存在實例結構里面,監控并待其重新上線,再發送命令,讓其變為新的主服務器的從服務器。
總結
- Sentinel 只是一個運行在特殊模式下的 Redis 服務器,它使用了和普通模式不同的命令表,所以 Sentinel 模式能夠使用的命令和普通 Redis 服務器能夠使用的命令不同。
- Sentinel 會讀入用戶指定的配置文件, 為每個要被監視的主服務器創建相應的實例結構, 并創建連向主服務器的命令連接和訂閱連接, 其中命令連接用于向主服務器發送命令請求, 而訂閱連接則用于接收指定頻道的消息。
- Sentinel 通過向主服務器發送?INFO?命令來獲得主服務器屬下所有從服務器的地址信息, 并為這些從服務器創建相應的實例結構, 以及連向這些從服務器的命令連接和訂閱連接。
- 在一般情況下, Sentinel 以每十秒一次的頻率向被監視的主服務器和從服務器發送?INFO?命令, 當主服務器處于下線狀態, 或者 Sentinel 正在對主服務器進行故障轉移操作時, Sentinel 向從服務器發送?INFO?命令的頻率會改為每秒一次。
- 對于監視同一個主服務器和從服務器的多個 Sentinel 來說, 它們會以每兩秒一次的頻率,通過向被監視服務器的?__sentinel__:hello頻道發送消息來向其他 Sentinel 宣告自己的存在。
- 每個 Sentinel 也會從?__sentinel__:hello?頻道中接收其他 Sentinel 發來的信息,并根據這些信息為其他 Sentinel 創建相應的實例結構,以及命令連接。
- Sentinel 只會與主服務器和從服務器創建命令連接和訂閱連接,Sentinel 與 Sentinel 之間則只創建命令連接。
- Sentinel 以每秒一次的頻率向實例(包括主服務器、從服務器、其他 Sentinel)發送?PING?命令, 并根據實例對?PING?命令的回復來判斷實例是否在線: 當一個實例在指定的時長中連續向 Sentinel 發送無效回復時, Sentinel 會將這個實例判斷為主觀下線。
- 當 Sentinel 將一個主服務器判斷為主觀下線時, 它會向同樣監視這個主服務器的其他 Sentinel 進行詢問, 看它們是否同意這個主服務器已經進入主觀下線狀態。
- 當 Sentinel 收集到足夠多的主觀下線投票之后, 它會將主服務器判斷為客觀下線, 并發起一次針對主服務器的故障轉移操作。
?
?
?
參考文章:
《Redis設計與實現》
?
總結
以上是生活随笔為你收集整理的Redis学习(八)哨兵机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: windows 命令行创建虚拟WIFI
- 下一篇: mysqljdbc设置参数