1.概述
Sentinel(哨崗迅栅、哨兵)是Redis高可用性的解決方案:由一個或多個Sentinel實例組成的Sentinel系統(tǒng)可以監(jiān)視任意多個主服務器暂吉,以及這個主服務器下的從服務器,并在被監(jiān)視主服務器下線時江锨,自動將下線主服務器下的某個從服務器升級為新的主服務器巫财,然后由新的主服務器代替已下線的主服務器繼續(xù)處理命令請求净赴。
當主服務器下線時,Sentinel會采取如下操作:
Sentinel會選出一個從服務器作為新的主服務器驮捍,假設此時選出的從服務器為:從服務器2疟呐,此時系統(tǒng)會如下圖所示:
當已下線的主服務器重新上線后,變?yōu)槿缦滤荆?/p>
2.啟動并初始化Sentinel
啟動Sentinel可以使用命令:
redis-sentinel /path/to/your/sentinel.conf
#或者命令
redis-server /path/to/your/sentinel.conf --sentinel
這兩個命令的效果完全一樣东且。
一個Sentinel實例啟動時启具,它需要執(zhí)行以下步驟:
- 初始化服務器
- 將普通Redis服務器使用的代扣替換為Sentinel專用代碼
- 初始化Sentinel 狀態(tài)
- 根據(jù)給定的配置文件,初始化Sentinel的監(jiān)視主服務器列表
- 創(chuàng)建連向主服務器的網(wǎng)絡連接
2.1 初始化服務器
Sentinel本質上是一個運行在特殊模式下的Redis服務器珊泳,Sentinel的啟動第一步就是啟動一個普通的Redis服務器鲁冯。但是Sentinel和普通Redis服務器執(zhí)行的工作不一樣囤踩,所以Sentinel的初始化過程和普通Redis服務器并不完全相同。
Sentinel在初始化時晓褪,不需要載入RDB文件或者AOF文件堵漱。
并且Sentinel向外提供的命令和普通Redis服務器也不是完全一樣的,像SET這一類命令Sentinel是沒有的涣仿。
2.2 使用Sentinel專用代碼
- 使用與普通Redis服務器不同的默認端口號
- 載入Sentinel需要使用的命令列表
Sentinle支持:PING勤庐、SENTINEL、INFO好港、SUBSCRIBE愉镰、UNSUBSCRIBE、PSUBSCRIBE和PUNSUBSCRIBE這七個命令
2.3 初始化Sentinel狀態(tài)
在應用了Sentinel的專用代碼之后钧汹,接下來服務器會初始化一個snetinel.c/sentinelState的結構丈探,這個結構中保存了服務器中所有和Sentinel功能相關的狀態(tài)。
比較重要的一個屬性:
#保存了所有這個Sentinle監(jiān)視的主服務器
#字典的鍵是主服務器的名字
#字典的值則是一個指向sentinelRedisInstance結構的指針
dict *masters;
2.4 初始化Sentinel狀態(tài)的masters屬性
對Sentinel狀態(tài)的初始化會引起對于masters字典的初始化拔莱,即初始化Sentinel監(jiān)控的所有主服務器碗降,它是根據(jù)Sentinel的配置文件來進行初始化的。
配置文件示例如下:
# master1 configure
sentinel monitor master1 127.0.0.1 6379 2
sentinel down-after-milliseconds master1 30000
sentinel parallel-syncs master1 1
sentinel failover-timeout master1 900000
# master2 configure
sentinel monitor master2 127.0.0.1 6379 2
sentinel down-after-milliseconds master2 30000
sentinel parallel-syncs master2 1
sentinel failover-timeout master2 900000
此時Sentinel將主服務器master1會創(chuàng)建為如下結構:
此時Sentinel狀態(tài)的結構如下所示:
2.5 創(chuàng)建連向主服務器的連接
初始化Sentinel的最后一步是創(chuàng)建連向主服務器的網(wǎng)絡連接塘秦。Sentinel將成為主服務器的客戶端讼渊,它可以向主服務器發(fā)送命令,并從命令回復中獲取相關信息尊剔。
Sentinel對每個被監(jiān)視的主服務器會創(chuàng)建兩個異步網(wǎng)絡連接:
- 命令連接爪幻,這個連接專門用于向主服務器發(fā)送命令,并接收命令回復
- 訂閱連接须误,這個連接專門用于訂閱主服務器的sentinel:hello 頻道
為什么使用兩個連接挨稿?
Redis在使用訂閱功能時,如果在發(fā)送消息時京痢,想要接收信息的客戶端不在線或者斷線奶甘,那么這個客戶端就會丟失這條消息,因此為了不丟失sentinel:hello 頻道的任何信息历造,sentinel必須開通一條專門的連接來接收該頻道的消息甩十。但是由于Snetinel還必須向主服務器發(fā)送命令來獲取主服務器的相關信息,因此必須再開通一條命令連接吭产。
因為Sentinel需要與多個實例創(chuàng)建多個網(wǎng)絡連接侣监,所有Sentinel使用的異步連接
如下圖展示了一個Snetinel與兩個master建立連接的情況:
3.獲取主服務器信息
Sentinel會以每10秒一次的頻率,通過命令連接向被監(jiān)視的主服務器發(fā)送INFO
命令臣淤,并通過分析INFO
的回復來獲取主服務器當前的狀態(tài)橄霉。
通過分析INFO
的回復,Sentinel可以獲取以下兩個方面的信息:
- 主服務器本身的信息邑蒋,包括run_id域記錄的服務器運行ID姓蜂,以及role域記錄的服務器角色
- 主服務器下從服務器的信息按厘,每個從服務器都由一個
slave
字符串開頭的行記錄,每行的ip
記錄了從服務器的IP地址钱慢,port
記錄了從服務器的端口號逮京,根據(jù)ip
和port
的信息Sentinel無需用戶來配置從服務器信息,即可自動發(fā)現(xiàn)從服務器束莫。
根據(jù)run_id和role記錄的信息懒棉,Sentinel對主服務器的實例進行更新。
從服務器的信息會更新至主服務器實例結構中的slaves
字典中览绿,這個字典記錄了主服務器下從服務器的名單:
- 字典的鍵是由Sentinel自動設置的從服務器的名字策严,格式為:ip:port
- 字典的值則是對應從服務器的實例結構。
具體結構如下圖所示:
注意:從服務器的flags
值為SRI_SLAVE
4.獲取從服務器信息
當Sentinel發(fā)現(xiàn)主服務器有新的從服務器出現(xiàn)時饿敲,Sentinel除了會為這個新的從服務器創(chuàng)建相應的實體結構之外妻导,Sentinel還會創(chuàng)建連接到從服務器的命令連接和訂閱連接。
在創(chuàng)建命令連接后怀各,會以每10秒一次的頻率發(fā)送INFO
命令倔韭,并解析返回的信息。提取出如下信息對從服務器實例進行更新:
- 從服務器運行run_id
- 從服務器角色role
- 主服務器的ip地址和端口號
- 主服務器的連接狀態(tài):master_link_status
- 從服務器器優(yōu)先級:slave_priority
- 從服務器的復制偏移量:slave_repl_offset
更新后從服務器結構如下所示:
5.向主服務器和從服務器發(fā)送消息
默認情況下Sentinel會以兩秒每次的頻率渠啤,通過命令連接向所有被監(jiān)視的主服務器和從服務器發(fā)送如下格式的命令:
#s開頭的參數(shù)是Sentinel的信息
#m開頭的信息是主服務器的信息:如果Sentinel正在監(jiān)視的為主服務器那么就是主服務器自身的信息狐肢;
#如果Sentinel監(jiān)視的是從服務器那么就是從服務器復制的主服務器的信息
PUBLISH __sentinel__:hello "<s_ip>,<s_port>,<s_runid>,<s_epoch>,<m_name>,<m_ip>,<m_port>,<m_epoch>"
具體參數(shù):
參數(shù) | 含義 |
---|---|
s_ip | Sentinel的ip |
s_port | Sentinel的port |
s_runid | Sentinel的runid |
s_epoch | Sentinel當前的配置紀元(在選舉領頭Sentinel時使用) |
m_name | 主服務器的名字 |
m_ip | 主服務器的ip |
m_port | 主服務器的port |
m_epoch | 主服務器的配置紀元 |
6.接收來自主服務器和從服務器的頻道信息
Sentinel當與一個主服務器或從服務器建立器訂閱連接之后添吗,Sentinel就會通過訂閱連接向服務器發(fā)送以下命令來進行訂閱消息:
SUBSCRIBE __sentinel__:hello
Senetinel會一直對sentinel:hello繼續(xù)訂閱直到Sentinel與服務器斷開連接為止沥曹。也就是說Sentinel既會通過命令連接向服務器發(fā)送sentinel:hello消息又會通過訂閱連接從服務器接收消息。
對于監(jiān)視同一個服務器的多個Sentinel來說碟联,一個Sentinel發(fā)送的消息會被其他Sentinel接收到妓美,這些信息會被用于更新其他Sentinel對于發(fā)送消息的Sentnel的認知,也會被用于更新其他Sentinel對于被監(jiān)視服務的認知鲤孵。
當一個Sentinel從sentinel:hello收到一條信息時壶栋,Sentinel會對這條信息進行分析,提取出信息中的<s_ip>,<s_port>,<s_runid>等上面提到的8個參數(shù):
- 如果記錄的<s_ip>與當前Sentinel一致普监,那么說明是自身發(fā)送的消息爷耀, 那么會丟棄這條消息
- 如果不一致盛撑,那么說明還有另外一個Sentinel在監(jiān)視同一個服務器,接收消息的Sentinel會對器監(jiān)視的主服務器實例結構中的sentinels字典進行更新
6.1 更新sentinels字典
- sentinels字典中鍵為Sentinel的名字,格式:ip:port
- sentinels字典中值為對應的Sentinel的實例結構
具體如下圖所示:
6.2 創(chuàng)建連接其他Sentinel的命令連接
Sentinel與Sentinel之間只會創(chuàng)建命令連接窄做,不會創(chuàng)建訂閱連接,因為Sentinel需要通過接收主服務器和從服務器的訂閱信息來發(fā)現(xiàn)未知的Sentinel枣购,對于相互已知的Sentinel不需要再建立訂閱連接來進行通信剥汤。
7.檢測主觀下線狀態(tài)
默認情況下Sentinel會向其監(jiān)控的服務器(主服務器、從服務器允睹、Sentinel)以每秒一次的頻率發(fā)送PING命令运准,并通過PING命令的回復來判斷具體實例的在線狀態(tài)幌氮。實例回復可以分為以下兩種情況:
- 有效回復:+PONG、-LOADING胁澳、-MASTERDOWN三種回復中的其中一種
- 無效回復:+PONG该互、-LOADING、-MASTERDOWN三種回復之外的回復韭畸,或者在指定時間內(nèi)沒有回復
Sentinel的配置文件中的down-after-milliseconds
指定了Sentinel判斷實例進入主觀下線的時間長度:如果一個實例在down-after-milliseconds
毫秒內(nèi)連續(xù)向Sentinel返回無效回復慢洋,那么Sentinel會修改這個實例對應的實例結構,在flags
屬性中打開SRI_S_DOWN
標識陆盘,以此來表示實例進入主觀下線狀態(tài)普筹。
具體如下:
Sentinel的配置文件中的down-after-milliseconds
不但用來判斷主服務器的主觀下線時間,還用于此主服務器下所有的從服務器的主觀下線狀態(tài)的判斷隘马。
每個Senttinel配置文件中down-after-milliseconds
的配置不一定會一樣太防,因此對于同一個服務器而言,一個Sentinel認為其下線但是另外一個Sentinel可能沒有認為其主觀下線酸员。
8.檢測客觀下線狀態(tài)
當Sentinel將一個主服務器檢測為主觀下線后蜒车,為確認這個主服務器是否真的下線,它會向同時在監(jiān)控這臺主服務器的其他Sentinel進行詢問幔嗦,看他們是否也認為服務器進入下線狀態(tài)(可以是主觀下線或客觀下線)酿愧,如果Sentinel從其他Sentinel哪里接收到足夠的數(shù)量的已下線判斷后,Sentinel就會將主服務器判定為客觀下線邀泉,對其執(zhí)行故障轉移
檢測客觀下線主要分以下三步:
8.1 發(fā)送SENTINEL is-master-down-by-addr命令
命令格式:
SENTINEL is-master-down-by-addr <ip> <port> <current_epoch> <runid>
具體參數(shù)含義:
參數(shù) | 含義 |
---|---|
ip | 被Sentinel判斷為主觀下線的主服務器ip地址 |
port | 被Sentinel判斷為主觀下線的主服務器port地址 |
current_epoch | Sentinel當前配置紀元嬉挡,用于選舉領頭Sentinel |
runid | 可以為或Sentinel的運行ID;當為時汇恤,代表命令僅用于檢測主服務器客觀下線狀態(tài)庞钢;當為Sentinel的運行ID時,則用于選舉領頭Sentinel |
82. 接收SENTINEL is-master-down-by-addr命令
當一個Sentinel接收到另一個Sentinel發(fā)送的SENTINEL is-master-down-by-addr
時因谎,會檢測當前監(jiān)控的主服務的主觀下線狀態(tài)基括,并做出如下格式的回復:
- <down_state>
- <leader_runid>
- <leader_epoch>
具體參數(shù)含義:
參數(shù) | 含義 |
---|---|
down_state | 目標Sentinel主服務器主觀下線狀態(tài):1代表主服務已下線,0代表主服務器未下線 |
leader_runid | 可以為或目標Sentinel的局部領頭Sentinel的運行ID财岔;當為時风皿,代表命令僅用于檢測主服務器客觀下線狀態(tài);當為目標Sentinel的局部領頭Sentinel的運行ID時匠璧,則用于選舉領頭Sentinel |
leader_epoch | 目標Sentinel局部領頭Sentinel的配置紀元桐款,僅在leader_runid部位時有效,如果leader_runid為患朱,則leader_epoch總是為0 |
8.3 接收SENTINEL is-master-down-by-addr命令回復
根據(jù)其他Sentinel返回的回復鲁僚,Sentinel將統(tǒng)計其他Sentinel同意主服務器已下線的數(shù)量,這一數(shù)量達到配置指定的判斷客觀下線所需的數(shù)量時,Sentinel會將主服務器實例結構中的flags
屬性的SRI_O_DOWN
打開冰沙,表示主服務器已進入客觀下線狀態(tài)侨艾。具體如下圖所示:
配置文件中對于客觀下線的配置:
#此表示表示總共需要兩個Sentinel認為主服務器已進入主觀下線狀態(tài),那么就可以判斷主服務為客觀下線
sentinel monitor master 127.0.0.1 6379 2
另:不同的Sentinel配置文件不同拓挥,因此對于同一主服務器認為其客觀下線的判斷也不一樣唠梨。
9.選舉領頭Sentinel
當一個主服務器被判定為主觀下線時,監(jiān)控這個主服務器的各個Sentinel會進行協(xié)商侥啤,選舉一個領頭Sentinel当叭,并由選舉出的這個領頭Sentinel對主服務器進行故障轉移。
具體選舉步驟如下:
1)所有在線Sentinel都有被選為領頭Sentinel的資格
2) 每次進行領頭Sentinel選舉之后盖灸,無論選舉是否成功蚁鳖,所有Sentinel的配置紀元值都會自增一次,配置紀元實際上就是一個計數(shù)器赁炎,并無其他特別之處
3) 在一個配置紀元里面醉箕,所有Sentinel都有一次將某個Sentinel設置為局部領頭Sentinel的機會,并且局部領頭Sentinel一旦設置徙垫,在這個配置紀元里面就不能在更改
4)每個發(fā)現(xiàn)主服務器客觀下線
的Sentinel都會要求其他Sentinel將其設置為局部領頭Sentinel
5)當一個Sentinel向另一個Sentinel(目標)發(fā)送SENTINEL is-master-down-by-addr時讥裤,并且命令中runid不為*而是源Sentinel自身的runid時,即表示源Sentinel要求目標Sentinel將其設置為自己的局部領頭Sentinel姻报。設置局部領頭Sentinel的規(guī)則是先到先到己英,最先向目標Sentinel發(fā)送命令的源Sentinel被成功設置為目標Sentinel的局部領頭Sentinel。
6) 目標Sentinel在接收SENTINEL is-master-down-by-addr命令后吴旋,會向源Sentinel返回一條命令回復损肛,回復中的<leader_runid>和<leader_epoch>分別記錄了被成功設置為自身局部領頭Sentinel的runid和epoch,源Sentinel在收到目標Sentinel返回的命令回復后邮府,會對目標Sentinel回復的<leader_runid>和<leader_epoch>進行檢查荧关,如果都與自身信息一致則表示目標Sentinel將自身設置為局部領頭Sentinel
7) 如果有半數(shù)以上的Sentinel將源Sentinel設置為局部領頭Sentinel,那么這個Sentinel就成為領頭Sentinel褂傀。因為需要半數(shù)以上,因此每次最多會有一個領個Sentinel被選舉出加勤。
8) 如果在給定的時間內(nèi)仙辟,沒有選出領頭Sentinel,那么各個Sentinel將在一段時間后再次進行選舉鳄梅,直到選出領頭Sentinel
10.故障轉移
在選出領頭Sentinel之后叠国,該Sentinel會對被判定為客觀下線的主服務器執(zhí)行故障轉移:
1) 在已下線的主服務器屬下的從服務器里面挑選一個從服務器,并將其轉換為從服務器戴尸。
2) 讓已下線的主服務器下的剩余的從服務器改為復制新的主服務器
3) 將已下線的主服務器設置為新的主服務器的從服務器粟焊,當這個舊的主服務器重新上線時,它會成為新的主服務器的從服務器
10.1 選出新的主服務器
領頭Sentinel挑選出一個狀態(tài)良好、數(shù)據(jù)完整的從服務器项棠,然后向這個從服務器發(fā)送SLAVEOF no one
命令悲雳,將這個從服務器轉換為主服務器。
具體挑選從服務器的規(guī)則如下:
- 刪除列表中所有處于下線或斷線狀態(tài)的從服務器
- 刪除最近5秒內(nèi)沒有回復過領頭Sentinel的INFO命令的從服務器
- 刪除所有與已下線主服務器連接斷開查過 down-after-milliseconds*10時間的從服務器
- 根據(jù)從服務器的優(yōu)先級進行排序
- 如果有多個同優(yōu)先級的從服務器香追,那么按照復制偏移量進行排序
- 如果復制偏移量相同的從服務器出現(xiàn)多臺合瓢,那么將從服務器的運行id進行排序,并選出運行id最小的從服務器
11.參考資料
《Redis設計與實現(xiàn)》