Redis 啟動(dòng)并初始化 Sentinel

2018-08-02 14:55 更新

啟動(dòng)一個(gè) Sentinel 可以使用命令:

$ redis-sentinel /path/to/your/sentinel.conf

或者命令:

$ redis-server /path/to/your/sentinel.conf --sentinel

這兩個(gè)命令的效果完全相同。

當(dāng)一個(gè) Sentinel 啟動(dòng)時(shí), 它需要執(zhí)行以下步驟:

  1. 初始化服務(wù)器。
  2. 將普通 Redis 服務(wù)器使用的代碼替換成 Sentinel 專用代碼。
  3. 初始化 Sentinel 狀態(tài)。
  4. 根據(jù)給定的配置文件, 初始化 Sentinel 的監(jiān)視主服務(wù)器列表。
  5. 創(chuàng)建連向主服務(wù)器的網(wǎng)絡(luò)連接。

本節(jié)接下來的內(nèi)容將分別對(duì)這些步驟進(jìn)行介紹。

初始化服務(wù)器

首先, 因?yàn)?Sentinel 本質(zhì)上只是一個(gè)運(yùn)行在特殊模式下的 Redis 服務(wù)器, 所以啟動(dòng) Sentinel 的第一步, 就是初始化一個(gè)普通的 Redis 服務(wù)器, 具體的步驟和《服務(wù)器》一章介紹的類似。

不過, 因?yàn)?Sentinel 執(zhí)行的工作和普通 Redis 服務(wù)器執(zhí)行的工作不同, 所以 Sentinel 的初始化過程和普通 Redis 服務(wù)器的初始化過程并不完全相同。

比如說, 普通服務(wù)器在初始化時(shí)會(huì)通過載入 RDB 文件或者 AOF 文件來還原數(shù)據(jù)庫狀態(tài), 但是因?yàn)?Sentinel 并不使用數(shù)據(jù)庫, 所以初始化 Sentinel 時(shí)就不會(huì)載入 RDB 文件或者 AOF 文件。

表 TABLE_SENTINEL_FUNCTION 展示了 Redis 服務(wù)器在 Sentinel 模式下運(yùn)行時(shí), 服務(wù)器各個(gè)主要功能的使用情況。


表 TABLE_SENTINEL_FUNCTION Sentinel 模式下 Redis 服務(wù)器主要功能的使用情況

功能 使用情況
數(shù)據(jù)庫和鍵值對(duì)方面的命令, 比如 SET 、 DEL 、FLUSHDB 。 不使用。
事務(wù)命令, 比如 MULTI 和 WATCH 。 不使用。
腳本命令,比如 EVAL 。 不使用。
RDB 持久化命令, 比如 SAVE 和 BGSAVE 。 不使用。
AOF 持久化命令, 比如 BGREWRITEAOF 。 不使用。
復(fù)制命令,比如 SLAVEOF 。 Sentinel 內(nèi)部可以使用,但客戶端不可以使用。
發(fā)布與訂閱命令, 比如 PUBLISH 和 SUBSCRIBE 。 SUBSCRIBE 、 PSUBSCRIBE 、 UNSUBSCRIBE PUNSUBSCRIBE 四個(gè)命令在 Sentinel 內(nèi)部和客戶端都可以使用, 但 PUBLISH 命令只能在 Sentinel 內(nèi)部使用。
文件事件處理器(負(fù)責(zé)發(fā)送命令請(qǐng)求、處理命令回復(fù))。 Sentinel 內(nèi)部使用, 但關(guān)聯(lián)的文件事件處理器和普通 Redis 服務(wù)器不同。
時(shí)間事件處理器(負(fù)責(zé)執(zhí)行 serverCron 函數(shù))。 Sentinel 內(nèi)部使用, 時(shí)間事件的處理器仍然是 serverCron 函數(shù), serverCron函數(shù)會(huì)調(diào)用 sentinel.c/sentinelTimer 函數(shù), 后者包含了 Sentinel 要執(zhí)行的所有操作。

使用 Sentinel 專用代碼

啟動(dòng) Sentinel 的第二個(gè)步驟就是將一部分普通 Redis 服務(wù)器使用的代碼替換成 Sentinel 專用代碼。

比如說, 普通 Redis 服務(wù)器使用 redis.h/REDIS_SERVERPORT 常量的值作為服務(wù)器端口:

#define REDIS_SERVERPORT 6379

而 Sentinel 則使用 sentinel.c/REDIS_SENTINEL_PORT 常量的值作為服務(wù)器端口:

#define REDIS_SENTINEL_PORT 26379

除此之外, 普通 Redis 服務(wù)器使用 redis.c/redisCommandTable 作為服務(wù)器的命令表:

struct redisCommand redisCommandTable[] = {
    {"get",getCommand,2,"r",0,NULL,1,1,1,0,0},
    {"set",setCommand,-3,"wm",0,noPreloadGetKeys,1,1,1,0,0},
    {"setnx",setnxCommand,3,"wm",0,noPreloadGetKeys,1,1,1,0,0},
    // ...
    {"script",scriptCommand,-2,"ras",0,NULL,0,0,0,0,0},
    {"time",timeCommand,1,"rR",0,NULL,0,0,0,0,0},
    {"bitop",bitopCommand,-4,"wm",0,NULL,2,-1,1,0,0},
    {"bitcount",bitcountCommand,-2,"r",0,NULL,1,1,1,0,0}
}

而 Sentinel 則使用 sentinel.c/sentinelcmds 作為服務(wù)器的命令表, 并且其中的 INFO 命令會(huì)使用 Sentinel 模式下的專用實(shí)現(xiàn)sentinel.c/sentinelInfoCommand 函數(shù), 而不是普通 Redis 服務(wù)器使用的實(shí)現(xiàn) redis.c/infoCommand 函數(shù):

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},
    {"info",sentinelInfoCommand,-1,"",0,NULL,0,0,0,0,0}
};

sentinelcmds 命令表也解釋了為什么在 Sentinel 模式下, Redis 服務(wù)器不能執(zhí)行諸如 SET 、 DBSIZE 、 EVAL 等等這些命令 —— 因?yàn)榉?wù)器根本沒有在命令表中載入這些命令: PING 、 SENTINEL 、 INFO 、 SUBSCRIBE 、 UNSUBSCRIBE 、 PSUBSCRIBE 和 PUNSUBSCRIBE 這七個(gè)命令就是客戶端可以對(duì) Sentinel 執(zhí)行的全部命令了。

初始化 Sentinel 狀態(tài)

在應(yīng)用了 Sentinel 的專用代碼之后, 接下來, 服務(wù)器會(huì)初始化一個(gè) sentinel.c/sentinelState 結(jié)構(gòu)(后面簡(jiǎn)稱“Sentinel 狀態(tài)”), 這個(gè)結(jié)構(gòu)保存了服務(wù)器中所有和 Sentinel 功能有關(guān)的狀態(tài) (服務(wù)器的一般狀態(tài)仍然由 redis.h/redisServer 結(jié)構(gòu)保存):

struct sentinelState {

    // 當(dāng)前紀(jì)元,用于實(shí)現(xiàn)故障轉(zhuǎn)移
    uint64_t current_epoch;

    // 保存了所有被這個(gè) sentinel 監(jiān)視的主服務(wù)器
    // 字典的鍵是主服務(wù)器的名字
    // 字典的值則是一個(gè)指向 sentinelRedisInstance 結(jié)構(gòu)的指針
    dict *masters;

    // 是否進(jìn)入了 TILT 模式?
    int tilt;

    // 目前正在執(zhí)行的腳本的數(shù)量
    int running_scripts;

    // 進(jìn)入 TILT 模式的時(shí)間
    mstime_t tilt_start_time;

    // 最后一次執(zhí)行時(shí)間處理器的時(shí)間
    mstime_t previous_time;

    // 一個(gè) FIFO 隊(duì)列,包含了所有需要執(zhí)行的用戶腳本
    list *scripts_queue;

} sentinel;

初始化 Sentinel 狀態(tài)的 masters 屬性

Sentinel 狀態(tài)中的 masters 字典記錄了所有被 Sentinel 監(jiān)視的主服務(wù)器的相關(guān)信息, 其中:

  • 字典的鍵是被監(jiān)視主服務(wù)器的名字。
  • 而字典的值則是被監(jiān)視主服務(wù)器對(duì)應(yīng)的 sentinel.c/sentinelRedisInstance 結(jié)構(gòu)。

每個(gè) sentinelRedisInstance 結(jié)構(gòu)(后面簡(jiǎn)稱“實(shí)例結(jié)構(gòu)”)代表一個(gè)被 Sentinel 監(jiān)視的 Redis 服務(wù)器實(shí)例(instance), 這個(gè)實(shí)例可以是主服務(wù)器、從服務(wù)器、或者另外一個(gè) Sentinel 。

實(shí)例結(jié)構(gòu)包含的屬性非常多, 以下代碼展示了實(shí)例結(jié)構(gòu)在表示主服務(wù)器時(shí)使用的其中一部分屬性, 本章接下來將逐步對(duì)實(shí)例結(jié)構(gòu)中的各個(gè)屬性進(jìn)行介紹:

typedef struct sentinelRedisInstance {

    // 標(biāo)識(shí)值,記錄了實(shí)例的類型,以及該實(shí)例的當(dāng)前狀態(tài)
    int flags;

    // 實(shí)例的名字
    // 主服務(wù)器的名字由用戶在配置文件中設(shè)置
    // 從服務(wù)器以及 Sentinel 的名字由 Sentinel 自動(dòng)設(shè)置
    // 格式為 ip:port ,例如 "127.0.0.1:26379"
    char *name;

    // 實(shí)例的運(yùn)行 ID
    char *runid;

    // 配置紀(jì)元,用于實(shí)現(xiàn)故障轉(zhuǎn)移
    uint64_t config_epoch;

    // 實(shí)例的地址
    sentinelAddr *addr;

    // SENTINEL down-after-milliseconds 選項(xiàng)設(shè)定的值
    // 實(shí)例無響應(yīng)多少毫秒之后才會(huì)被判斷為主觀下線(subjectively down)
    mstime_t down_after_period;

    // SENTINEL monitor <master-name> <IP> <port> <quorum> 選項(xiàng)中的 quorum 參數(shù)
    // 判斷這個(gè)實(shí)例為客觀下線(objectively down)所需的支持投票數(shù)量
    int quorum;

    // SENTINEL parallel-syncs <master-name> <number> 選項(xiàng)的值
    // 在執(zhí)行故障轉(zhuǎn)移操作時(shí),可以同時(shí)對(duì)新的主服務(wù)器進(jìn)行同步的從服務(wù)器數(shù)量
    int parallel_syncs;

    // SENTINEL failover-timeout <master-name> <ms> 選項(xiàng)的值
    // 刷新故障遷移狀態(tài)的最大時(shí)限
    mstime_t failover_timeout;

    // ...

} sentinelRedisInstance;

sentinelRedisInstance.addr 屬性是一個(gè)指向 sentinel.c/sentinelAddr 結(jié)構(gòu)的指針, 這個(gè)結(jié)構(gòu)保存著實(shí)例的 IP 地址和端口號(hào):

typedef struct sentinelAddr {

    char *ip;

    int port;

} sentinelAddr;

對(duì) Sentinel 狀態(tài)的初始化將引發(fā)對(duì) masters 字典的初始化, 而 masters 字典的初始化是根據(jù)被載入的 Sentinel 配置文件來進(jìn)行的。

舉個(gè)例子, 如果用戶在啟動(dòng) Sentinel 時(shí), 指定了包含以下內(nèi)容的配置文件:

#####################
# 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 12345 5

sentinel down-after-milliseconds master2 50000

sentinel parallel-syncs master2 5

sentinel failover-timeout master2 450000

那么 Sentinel 將為主服務(wù)器 master1 創(chuàng)建如圖 IMAGE_MASTER1 所示的實(shí)例結(jié)構(gòu), 并為主服務(wù)器 master2 創(chuàng)建如圖 IMAGE_MASTER2 所示的實(shí)例結(jié)構(gòu), 而這兩個(gè)實(shí)例結(jié)構(gòu)又會(huì)被保存到 Sentinel 狀態(tài)的 masters 字典中, 如圖 IMAGE_SENTINEL_STATE 所示。

創(chuàng)建連向主服務(wù)器的網(wǎng)絡(luò)連接

初始化 Sentinel 的最后一步是創(chuàng)建連向被監(jiān)視主服務(wù)器的網(wǎng)絡(luò)連接: Sentinel 將成為主服務(wù)器的客戶端, 它可以向主服務(wù)器發(fā)送命令, 并從命令回復(fù)中獲取相關(guān)的信息。

對(duì)于每個(gè)被 Sentinel 監(jiān)視的主服務(wù)器來說, Sentinel 會(huì)創(chuàng)建兩個(gè)連向主服務(wù)器的異步網(wǎng)絡(luò)連接:

  • 一個(gè)是命令連接, 這個(gè)連接專門用于向主服務(wù)器發(fā)送命令, 并接收命令回復(fù)。
  • 另一個(gè)是訂閱連接, 這個(gè)連接專門用于訂閱主服務(wù)器的 __sentinel__:hello 頻道。

為什么有兩個(gè)連接?

在 Redis 目前的發(fā)布與訂閱功能中, 被發(fā)送的信息都不會(huì)保存在 Redis 服務(wù)器里面, 如果在信息發(fā)送時(shí), 想要接收信息的客戶端不在線或者斷線, 那么這個(gè)客戶端就會(huì)丟失這條信息。

因此, 為了不丟失 __sentinel__:hello 頻道的任何信息, Sentinel 必須專門用一個(gè)訂閱連接來接收該頻道的信息。

而另一方面, 除了訂閱頻道之外, Sentinel 還又必須向主服務(wù)器發(fā)送命令, 以此來與主服務(wù)器進(jìn)行通訊, 所以 Sentinel 還必須向主服務(wù)器創(chuàng)建命令連接。

并且因?yàn)?Sentinel 需要與多個(gè)實(shí)例創(chuàng)建多個(gè)網(wǎng)絡(luò)連接, 所以 Sentinel 使用的是異步連接。

圖 IMAGE_SENTINEL_CONNECT_SERVER 展示了一個(gè) Sentinel 向被它監(jiān)視的兩個(gè)主服務(wù)器 master1 和 master2 創(chuàng)建命令連接和訂閱連接的例子。

接下來的一節(jié)將介紹 Sentinel 是如何通過命令連接和訂閱連接來與被監(jiān)視主服務(wù)器進(jìn)行通訊的。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)