redis 哨兵

哨兵做用

哨兵(sentinel) 是一個分佈式系統,是程序高可用性的一個保障。用於監視任意多個主服務器,以及這些主服務器屬下的全部從服務器,當出現故障時經過投票機制選擇新的master並將全部slave鏈接到新的master。html

監控

不斷地檢查master和slave是否正常運行 master存活檢測、master與slave運行狀況檢測。python

通知

當被監控地服務器出現問題時,向其餘(哨兵間,客戶端)發送通知。redis

自動故障轉移

斷開master與slave鏈接,選取一個slave做爲master,將其餘slave鏈接到新的master,並告知客戶端新的服務器地址。數組

注意

哨兵也是一臺redis服務器,只是不提供數據服務,一般哨兵配置數量爲單數服務器

啓動哨兵

配置文件

哨兵默認的配置文件 sentinel.conf數據結構

通常的以 sentinel_port.conf 命名 哨兵的配置文件併發

配置信息異步

port  26379  (端口號)
dir  /tmp  (哨兵運行信息存儲)
monitor mymaster 127.0.0.1 6379 2
# mymaster  (master 名字 隨意)
# 127.0.0.1 6379  (IP + 端口號)
# 2  (哨兵個數 //2 + 1  當有 2 個哨兵認爲 master 掛了 就掛了)
down-after-milliseconds mymaster 30000 (單位 毫秒 )

parallel-syncs mymaster 1 ( 新的master 一次有多少個 slave 同步,設置的越小,完成數據同步的時間越長,響應的服務器壓力越小。)
failover-timeout mymaster 180000( 3 分鐘 若是沒有同步完成 就斷定爲同步超時)

啓動

配置主從結構,以 1master 2 slave爲例。分佈式

1 先啓動 master 和 slave函數

主從配置 參看 主從篇博客主從

redis-server config_6379.conf
redis-server config_6380.conf
redis-server config_6381.conf

2 啓動哨兵

redis-sentinel sentinel_26379.conf
redis-sentinel sentinel_26380.conf
redis-sentinel sentinel_26381.conf

Sentinel 命令

PING:PONG
SENTINEL masters :列出全部被監視的主服務器,以及這些主服務器的當前狀態。
SENTINEL slaves :列出給定主服務器的全部從服務器,以及這些從服務器的當前狀態。
SENTINEL get-master-addr-by-name : 返回給定名字的主服務器的 IP 地址和端口號。 若是這個主服務器正在執行故障轉移操做, 或者針對這個主服務器的故障轉移操做已經完成, 那麼這個命令返回新的主服務器的 IP 地址和端口號。
SENTINEL reset : 重置全部名字和給定模式 pattern 相匹配的主服務器。 pattern 參數是一個 Glob 風格的模式。 重置操做清楚主服務器目前的全部狀態, 包括正在執行中的故障轉移, 並移除目前已經發現和關聯的, 主服務器的全部從服務器和 Sentinel 。
SENTINEL failover : 當主服務器失效時, 在不詢問其餘 Sentinel 意見的狀況下, 強制開始一次自動故障遷移 (不過發起故障轉移的 Sentinel 會向其餘 Sentinel 發送一個新的配置,其餘 Sentinel 會根據這個配置進行相應的更新)。

初始化Sentinel

初始化服務器

從下面啓動代碼能夠看出啓動方式由函數 checkForSentinelMode 來決定,是否使用 sentinel 的模式進行一個啓動, 添加的指令也是用的 sentinelcmds 的命令表

int checkForSentinelMode(int argc, char **argv) {
    int j;

    if (strstr(argv[0],"redis-sentinel") != NULL) return 1;
    for (j = 1; j < argc; j++)
        if (!strcmp(argv[j],"--sentinel")) return 1;
    return 0;
}

// 檢查服務器是否以 Sentinel 模式啓動
server.sentinel_mode = checkForSentinelMode(argc,argv);

// 初始化服務器
initServerConfig();  // 在第二步介紹該函數

// 若是服務器以 Sentinel 模式啓動,那麼進行 Sentinel 功能相關的初始化
// 併爲要監視的主服務器建立一些相應的數據結構
if (server.sentinel_mode) {
    initSentinelConfig();
    initSentinel();
}

從源碼咱們能夠看出哨兵的啓動有兩種方式

redis-sentinel sentinel_xxx.conf
redis-server sentinel_xxx.conf --sentinel

不管哪一種方式啓動redis,都會執行 initServerConfig ,不一樣的是 Sentinel 還會 執行initSentinelConfiginitSentinel 兩個初始化函數。接下來看看這兩個函數都幹了什麼~ 。

替換 Sentinel 的專用代碼

initSentinelConfig() 這個函數會用 Sentinel 配置的屬性覆蓋服務器默認的屬性。

void initSentinelConfig(void) {
    server.port = REDIS_SENTINEL_PORT;//26379
}

initSentinel() 會進行一個命令表的加載。一個主要的查詢命令 INFO 也不一樣於普通服務器,而是使用一個特殊的版本。

// 初始化服務器 Sentinel 服務器
void initSentinel(void) {
    int j;

    // 刪除 普通 Redis 服務器的命令表(該表用於普通模式)
    dictEmpty(server.commands,NULL);

    //  添加 sentinel 模式專用的命令。
    for (j = 0; j < sizeof(sentinelcmds)/sizeof(sentinelcmds[0]); j++) {
        int retval;
        struct redisCommand *cmd = sentinelcmds+j;

        retval = dictAdd(server.commands, sdsnew(cmd->name), cmd);
        redisAssert(retval == DICT_OK);
    }

    /* 初始化 Sentinel 的狀態 這是爲了故障轉移階段選取 切換執行者 記錄的狀態 */
    sentinel.current_epoch = 0;

    // 保存 主服務器 信息的字典 (這裏記錄了監測的主服務器的信息)
    sentinel.masters = dictCreate(&instancesDictType,NULL);

    // 初始化 TILT 模式的相關選項
    sentinel.tilt = 0;
    sentinel.tilt_start_time = 0;
    sentinel.previous_time = mstime();

    // 初始化腳本相關選項
    sentinel.running_scripts = 0;
    sentinel.scripts_queue = listCreate();
}

// sentinel 的指令集合
struct redisCommand sentinelcmds[] = {
    {"ping",pingCommand,1,"",0,NULL,0,0,0,0,0},
    {"sentinel",sentinelCommand,-2,"",0,NULL,0,0,0,0,0},
    {"subscribe",subscribeCommand,-2,"",0,NULL,0,0,0,0,0},
    {"unsubscribe",unsubscribeCommand,-1,"",0,NULL,0,0,0,0,0},
    {"psubscribe",psubscribeCommand,-2,"",0,NULL,0,0,0,0,0},
    {"punsubscribe",punsubscribeCommand,-1,"",0,NULL,0,0,0,0,0},
    {"publish",sentinelPublishCommand,3,"",0,NULL,0,0,0,0,0},
    {"info",sentinelInfoCommand,-1,"",0,NULL,0,0,0,0,0},
    {"shutdown",shutdownCommand,-1,"",0,NULL,0,0,0,0,0}
};

初始化 Sentinel 狀態

在完成命令表加載以後,緊接着會進行 sentinelStatesentinelRedisInstance 結構的一個初始化。

Sentinel 狀態中的 masters 字典記錄了全部被監視的主服務器信息,鍵爲服務器名字,值爲被監視主服務器對應的sentinel.c/sentinelRedisInstance結構。每一個sentinelRedisInstance實例結構表明監視一個Redis服務器實例,這個實例能夠是主服務器,也能夠是從服務器,或者另一個sentinel服務器。

對於sentinelState的初始化將引起對masters字典的初始化,而masters字典的初始化是根據被該入的sentinel配置文件(sentinel_26379.conf)來進行的。主要爲被監控 masterip port

注意 這些都是有 sentinel 來維護和使用的。

sentinelState

struct sentinelState {

    // 當前紀元 用作故障轉移
    uint64_t current_epoch;     /* Current epoch. */

    // 保存了全部被這個 sentinel 監視的主服務器
    // 字典的鍵是主服務器的名字
    // 字典的值則是一個指向 sentinelRedisInstance 結構的指針,能夠是主服務器,從服務器或者其餘sentinel節點
    dict *masters;      /* Dictionary of master sentinelRedisInstances.
                           Key is the instance name, value is the
                           sentinelRedisInstance structure pointer. */

    // 是否進入了 TILT 模式?
    int tilt;           /* Are we in TILT mode? */

    // 目前正在執行的腳本的數量
    int running_scripts;    /* Number of scripts in execution right now. */

    // 進入 TILT 模式的時間
    mstime_t tilt_start_time;   /* When TITL started. */

    // 最後一次執行時間處理器的時間
    mstime_t previous_time;     /* Last time we ran the time handler. */

    // 一個 FIFO 隊列,包含了全部須要執行的用戶腳本
    list *scripts_queue;    /* Queue of user scripts to execute. */

} sentinel;

sentinelRedisInstance

name
實例的名字
主服務器的名字由用戶在配置文件中設置
從服務器以及 Sentinel 的名字由 Sentinel 自動設置
格式爲 ip:port ,例如 "127.0.0.1:26379"

runid
實例的運行 ID

sentinelAddr
實例的地址

主服務器實例特有的屬性

sentinels
其餘一樣監控這個主服務器的全部 sentinel

slaves
若是這個實例表明的是一個主服務器
那麼這個字典保存着主服務器屬下的從服務器
字典的鍵是從服務器的名字,字典的值是從服務器對應的 sentinelRedisInstance 結構

quorum
判斷這個實例爲客觀下線(objectively down)所需的支持投票數量

parallel_syncs
SENTINEL parallel-syncs 選項的值
在執行故障轉移操做時,能夠同時對新的主服務器進行同步的從服務器數量

auth_pass
鏈接主服務器和從服務器所需的密碼

從服務器實例特有的屬性

master_link_down_time
主從服務器鏈接斷開的時間

slave_priority
從服務器優先級

slave_reconf_sent_time
執行故障轉移操做時,從服務器發送 SLAVEOF 命令的時間

master
主服務器的實例(在本實例爲從服務器時使用)

slave_master_host
INFO 命令的回覆中記錄的主服務器 IP

slave_master_port
INFO 命令的回覆中記錄的主服務器端口號

slave_master_link_status
INFO 命令的回覆中記錄的主從服務器鏈接狀態

slave_repl_offset
從服務器的複製偏移量

結構中的 sentinelAddr 保存着對象的 地址和端口。

/* Address object, used to describe an ip:port pair. */
/* 地址對象,用於保存 IP 地址和端口 */
typedef struct sentinelAddr {
    char *ip;
    int port;
} sentinelAddr;

創建鏈接

sentinel 會先去鏈接 sentinel masters 中的每個 master,並在每個 mastersentinel之間建立兩個異步鏈接 一個 命令鏈接 一個 訂閱連接。此時 sentinel將成爲 master 的客戶端它能夠向主服務器發送命令,並從命令回覆中獲取相關信息。

命令鏈接

專門用於向主服務器發送命令,並接收命令回覆。好比sentinel向主服務器發送INFO命令。

訂閱鏈接

專門用於訂閱主服務器的 _sentinel_:hello頻道。 好比 sentinel向主,從,其它sentinel發送sentinel自己和主庫信息。

redis在發佈與訂閱功能中,被髮送的信息都不會保存在redis服務器中,若消息到來時,須要接收的客戶端不在線或者斷線,那麼這個客戶端就會丟失這條信息。爲了避免丟失_sentinel_:hello頻道的任何信息,sentinel必須專門的用一個訂閱鏈接來接收該頻道的信息。

獲取主服務器信息

Sentinel 默認會以每10秒一次的頻率向主服務器發送INFO命令,經過分析命令回覆來獲取主服務器的當前信息。Sentinel能夠獲取如下兩方面的信息:

1主服務器自己的信息,包括服務器run_id,role的服務器角色。

2 主服務器對應的全部從服務器的信息(從服務器IP和端口)。

獲取從服務器信息

Sentinel發現有新的從服務器出現時,Sentinel除了會爲這個新的從服務器建立相應的實例結構(sentinelRedisInstance)以外,還會建立到從服務器的命令鏈接訂閱鏈接

Sentinel依然會像對待主服務器那樣,每10s 發送一個INFO命令來獲取從服務器的當前信息。

run_id、role、ip、port 、master_link_status(主從服務器的鏈接狀態)、slave_priority(從服務器的優先級)等信息。

向主從服務器發送信息

在默認狀況下, Sentinel會以每2秒一次的頻率,經過命令鏈接向,全部被監視的主服務器和從服務器發送如下格式的命令:

PUBLISH __sentinel__:hello "<s_ip>,<s_port>,<s_runid>,<s_epoch>,<m_name>,<m_ip>,<m_port>,<m_epoch>"

這條命令向服務器的_sentinel_:hello頻道發送了一條信息,信息的內容由多個參數組成:

(1) s_開頭的參數記錄的是sentinel自己的信息。

(2) m_開頭的參數記錄的則是主服務器的信息,若是sentinel正在監視的是主服務器,那麼這些參數就是主服務器的信息,若是sentinel正在監視的是從服務器,那麼這些參數記錄就是從服務器正在複製的主服務器的信息。

參數 描述
S_ip Sentinel的ip地址
S_port Sentinel的端口號
S_runid Sentinel的運行ID
S_epoch Sentinel 的當前配置紀元
m_name 主服務器的名字
M_ip 主服務器的IP地址
M_port 主服務器的端口號
M_epoch 主服務器的當前配置紀元

例如

"127.0.0.1,26379,e955b4c77398ef6b5f055bc7ebfd5e828dbed4fc,0,mymaster,127.0.0.1,6379,0"
# --------------------------------解釋------------------------------------------
127.0.0.1  # sentinel ip 地址
26379  # sentinel 端口號
e955b4c77398ef6b5f055bc7ebfd5e828dbed4fc  # sentinel的運行 id
0 # sentinel 當前配置紀元
mymaster # sentinel 監控的 master name
127.0.0.1 # master ip 地址
6379 # master 端口號
0 # master 當前配置紀元

接收來自主從服務器的頻道信息

Sentinel與一個主服務器或者從服務器創建起訂閱鏈接以後,Sentinel就會經過訂閱鏈接向服務器發送 subscribe_sentinel_:hello

對於每一個與 Sentinel 鏈接的服務器,Sentinel既經過命令連向服務器的_sentinel_:hello頻道發送信息,又經過訂閱鏈接從服務器的_sentinel_:hello頻道接收信息。

所以當有新的Sentinel 鏈接進來時, 向訂閱鏈接中發送的 subscribe_sentinel_:hello 被已有的Sentinel 接收(同時本身也會接受到來自本身的這條消息)。

// 發送 PUBLISH 命令的間隔
#define SENTINEL_PUBLISH_PERIOD 2000

if ((now - ri->last_pub_time) > SENTINEL_PUBLISH_PERIOD) {
        /* PUBLISH hello messages to all the three kinds of instances. */
        sentinelSendHello(ri);
    }

/* 接收來自主服務器和從服務器的頻道信息
當 sentinel 與一個主服務器或者從服務器創建起訂閱鏈接以後, sentinel 就會經過訂閱鏈接,向服務器發送如下命令:
*/
SUBSCRIBE __sentinel__:hello

/* Now we subscribe to the Sentinels "Hello" channel. */
// 發送 SUBSCRIBE __sentinel__:hello 命令,訂閱頻道
retval = redisAsyncCommand(ri->pc,
        sentinelReceiveHelloMessages, NULL, "SUBSCRIBE %s",
        SENTINEL_HELLO_CHANNEL);

當一個Sentinel_sentinel_:hello頻道收到一條信息時,Sentinel會對這條信息進行分析,提取出信息中 ip 、port、run_id 等8個參數,並進行如下檢查:若是這條消息是本身發的,就直接忽略。若是是新進來的Sentinel , 此時Sentinel 會對 對應的主服務器實例結構進行更新,即將新加進來的 Sentinel 添加到 sentinels 字典中。

每一個Sentinel都有本身的一個sentinels字典,Sentinels字典信息保存了除本身以外的全部Sentinel信息。

下線狀態

對於Redis的Sentinel中關於下線有兩個不一樣的概念:(1)主觀下線(Subjectively Down, 簡稱 Sdown) 指的是單個 Sentinel 實例對服務器作出的下線判斷,此時不會進行故障轉移。(2) 客觀下線(Objectively Down, 簡稱 Odown)指的是多個 Sentinel 實例在對同一個服務器作出 Sdown 判斷,此時目標sentinel會對主服務器進行故障轉移。本篇具體詳細介紹。

主觀下線狀態

默認的Sentinel會以每秒一次的頻率向全部與它建立命令鏈接的實例(包括主、從、其餘sentinel在內)發送PING命令,並經過實例回覆來判斷實例是否在線。

合法的回覆

+pong-loading -masterdown

無效回覆

除此以外的全部回覆或者無回覆都被視做無效回覆。無回覆指在指定的時間內沒有回覆就認爲是無回覆。

down-after-milliseconds  # 指定的時間 未收到回覆 視爲無效

用戶設置down-after-milliseconds選項的值,不只會被sentinel用來判斷主服務器的主觀下線狀態,還會被用於判斷主服務器下的全部從服務器,以及一樣監視主服務器的其餘sentinel的主觀下線狀態。

-- 例如用戶向sentinel設置以了下配置:
sentinel  monitor master 127.0.0.1 6379 2
sentinel  down-after-milliseconds master 50000

這裏的master是主服務器的名稱, 端口默認63792表明sentinel集羣中有2sentinel認爲master 狀態下線時,才能真正認爲該master已經不可用了(也就是客觀下線)。

這50000毫秒不只會成爲sentinel判斷master進入主觀下線的標準,還會判斷全部從庫、其它sentinel進入主觀下線的標準。

當多個sentinel設置的主觀下線時長可能不一樣

對於多個sentinel共同監視同一個主服務器時,這些sentinel在配置文件sentinle.conf中所設置的down-after-milliseconds值也可能不一樣,所以當一個sentinel將主服務器判斷爲主觀下線時,其它sentinel可能仍然會認爲主服務器處於在線狀態。只有所有的sentine都判斷進入了主觀下線狀態時,纔會認爲主master進入了主觀下線狀態。

客觀下線狀態

Sentinel將一個主服務器判斷爲主觀下線以後,爲了確認這個主服務器是否真的下線了,會向一樣監視這一主服務器的其它Sentinel進行詢問,當有半數以上(看具體配置, 通常的是半數以上 例如sentinel monitor mymaster 127.0.0.1 6379 2 中 就爲當 2 個斷定下線時,就認爲時客觀下線了)

master, 被肯定客觀下線以後sentinel 們 會選出一個 決策者 去執行故障轉移操做。客觀下線條件只適用於主服務器

is-master-down-by-addr命令用來判斷是否客觀下線

sentinel is-master-down-by-addr  ip  port  current_epoch  run_id

sentinel當前的配置紀元 current_epoch 用於選舉 決策者 sentinel, run_id能夠是*或者sentinel的 運行id。

決策者選取

假設如今有4個sentinel 這四個sentinel 既是投票者,也是候選者(這四個必須時健康的)。

1 不能有下面三個標記中的一個:SRI_S_DOWN|SRI_O_DOWN|SRI_DISCONNECTED

2 ping 心跳正常

3 優先級不能爲 0(slave->slave_priority)

4 INFO 數據不能超時

5 主從鏈接斷線會時間不能超時

投票的過程很簡單,每一個sentinel 都將本身的ipportcurrent_epochrun_idis-master-down 發送到 hello 頻道。

sentinel 第一個獲取到誰的 is-master-down 信息, 就將本身的票投給對應的sentinel

一次事後 current_epoch 最大的,且超過了半數以上。則被選爲決策者 不然再來一輪,每增長一輪 current_epoch + 1, 直到選出爲止。

故障轉移

選取候選Slave

1 在線的

2 響應速度快的

3 與原 master 斷開鏈接最短的

4 優先原則

優先級>offset>runid

最終選取出 新的 master 以後向新的 master 發送

slaveof no one  # 斷開主從

而後聲明新的master

slaveof ip port  # 發送新的IP 和  新的port

最後將原來的 master 做爲從機。當從新上線時,sentinel 會發送 salveof 命令使其成爲從機。

總結

  • sentinel只是一個運行在特殊模式下的redis服務器,它使用了和普通模式不一樣的命令表,以及區別與普通模式下使用的命令不一樣。

  • sentinel向主服務器發送INFO命令來得到主服務器屬下全部從服務器的地址信息,併爲這些從服務器建立相應的實例結構,以及連向這些從服務器的命令鏈接和訂閱鏈接。

  • 通常狀況下,sentinel以每10秒一次的頻率向被監視的主服務器和從服務器發送INFO命令,當主服務器處於下線狀態,或者sentinel正在對主服務器進行故障轉移操做時,sentinel向從服務器發送INFO命令的頻率會改成1秒一次。

  • 對於監視同一個主服務器和從服務器的多個sentinel來講,它們會以每2秒一次的頻率,經過向被監視的_sentinel_:hello頻道發送消息來向其餘sentinel宣告本身的存在。

  • 每一個sentinel也會從_sentinel_:hello中頻道中接收其餘sentinel發來的信息,並根據這些信息爲其餘sentinel建立相應的實例結構,以及命令鏈接。

  • sentinel只會與主服務器和從服務器建立命令鏈接和訂閱鏈接,sentinelsentinel之間則只建立命令鏈接。

  • sentinel以每秒一次的頻率向實例(包括主,從,其它sentinel)發送PING命令,並根據實例的回覆來判斷實例是否在線,當一個實例在指定的時長中連續向sentinel發送無效回覆時,sentinel會將這個實例判斷爲主觀下線。

  • sentinel將一個主服務器判斷爲主觀下線時,它會向一樣的監視這個主服務器的其餘sentinel進行詢問,看它們是否贊成這個主服務器已經進入主觀下線狀態。

  • sentinel收集到足夠多的主觀下線投票以後,它會將主服務器判斷爲客觀下線,併發起一次針對主服務器的故障轉移操做。

相關文章
相關標籤/搜索