Redis 啟動并初始化 Sentinel

2018-08-02 14:55 更新

啟動一個 Sentinel 可以使用命令:

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

或者命令:

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

這兩個命令的效果完全相同。

當一個 Sentinel 啟動時, 它需要執(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)容將分別對這些步驟進行介紹。

初始化服務(wù)器

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

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

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

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


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

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

使用 Sentinel 專用代碼

啟動 Sentinel 的第二個步驟就是將一部分普通 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 命令會使用 Sentinel 模式下的專用實現(xiàn)sentinel.c/sentinelInfoCommand 函數(shù), 而不是普通 Redis 服務(wù)器使用的實現(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 等等這些命令 —— 因為服務(wù)器根本沒有在命令表中載入這些命令: PING 、 SENTINEL 、 INFO 、 SUBSCRIBE 、 UNSUBSCRIBE 、 PSUBSCRIBE 和 PUNSUBSCRIBE 這七個命令就是客戶端可以對 Sentinel 執(zhí)行的全部命令了。

初始化 Sentinel 狀態(tài)

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

struct sentinelState {

    // 當前紀元,用于實現(xiàn)故障轉(zhuǎn)移
    uint64_t current_epoch;

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

    // 是否進入了 TILT 模式?
    int tilt;

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

    // 進入 TILT 模式的時間
    mstime_t tilt_start_time;

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

    // 一個 FIFO 隊列,包含了所有需要執(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ù)器對應(yīng)的 sentinel.c/sentinelRedisInstance 結(jié)構(gòu)。

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

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

typedef struct sentinelRedisInstance {

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

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

    // 實例的運行 ID
    char *runid;

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

    // 實例的地址
    sentinelAddr *addr;

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

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

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

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

    // ...

} sentinelRedisInstance;

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

typedef struct sentinelAddr {

    char *ip;

    int port;

} sentinelAddr;

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

舉個例子, 如果用戶在啟動 Sentinel 時, 指定了包含以下內(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 所示的實例結(jié)構(gòu), 并為主服務(wù)器 master2 創(chuàng)建如圖 IMAGE_MASTER2 所示的實例結(jié)構(gòu), 而這兩個實例結(jié)構(gòu)又會被保存到 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ā)送命令, 并從命令回復中獲取相關(guān)的信息。

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

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

為什么有兩個連接?

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

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

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

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

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

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


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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號