啟動一個 Sentinel 可以使用命令:
~~~
$ redis-sentinel /path/to/your/sentinel.conf
~~~
或者命令:
~~~
$ redis-server /path/to/your/sentinel.conf --sentinel
~~~
這兩個命令的效果完全相同。
當一個 Sentinel 啟動時, 它需要執行以下步驟:
1. 初始化服務器。
2. 將普通 Redis 服務器使用的代碼替換成 Sentinel 專用代碼。
3. 初始化 Sentinel 狀態。
4. 根據給定的配置文件, 初始化 Sentinel 的監視主服務器列表。
5. 創建連向主服務器的網絡連接。
本節接下來的內容將分別對這些步驟進行介紹。
## 初始化服務器
首先, 因為 Sentinel 本質上只是一個運行在特殊模式下的 Redis 服務器, 所以啟動 Sentinel 的第一步, 就是初始化一個普通的 Redis 服務器, 具體的步驟和《服務器》一章介紹的類似。
不過, 因為 Sentinel 執行的工作和普通 Redis 服務器執行的工作不同, 所以 Sentinel 的初始化過程和普通 Redis 服務器的初始化過程并不完全相同。
比如說, 普通服務器在初始化時會通過載入 RDB 文件或者 AOF 文件來還原數據庫狀態, 但是因為 Sentinel 并不使用數據庫, 所以初始化 Sentinel 時就不會載入 RDB 文件或者 AOF 文件。
表 TABLE_SENTINEL_FUNCTION 展示了 Redis 服務器在 Sentinel 模式下運行時, 服務器各個主要功能的使用情況。
* * *
表 TABLE_SENTINEL_FUNCTION Sentinel 模式下 Redis 服務器主要功能的使用情況
| 功能 | 使用情況 |
| --- | --- |
| 數據庫和鍵值對方面的命令, 比如?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 要執行的所有操作。 |
* * *
## 使用 Sentinel 專用代碼
啟動 Sentinel 的第二個步驟就是將一部分普通 Redis 服務器使用的代碼替換成 Sentinel 專用代碼。
比如說, 普通 Redis 服務器使用?`redis.h/REDIS_SERVERPORT`?常量的值作為服務器端口:
~~~
#define REDIS_SERVERPORT 6379
~~~
而 Sentinel 則使用?`sentinel.c/REDIS_SENTINEL_PORT`?常量的值作為服務器端口:
~~~
#define REDIS_SENTINEL_PORT 26379
~~~
除此之外, 普通 Redis 服務器使用?`redis.c/redisCommandTable`?作為服務器的命令表:
~~~
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`?作為服務器的命令表, 并且其中的?INFO?命令會使用 Sentinel 模式下的專用實現`sentinel.c/sentinelInfoCommand`?函數, 而不是普通 Redis 服務器使用的實現?`redis.c/infoCommand`?函數:
~~~
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 服務器不能執行諸如?SET?、?DBSIZE?、?EVAL?等等這些命令 —— 因為服務器根本沒有在命令表中載入這些命令:?PING?、?SENTINEL?、?INFO?、?SUBSCRIBE?、?UNSUBSCRIBE?、?PSUBSCRIBE?和?PUNSUBSCRIBE?這七個命令就是客戶端可以對 Sentinel 執行的全部命令了。
## 初始化 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;
~~~
## 初始化 Sentinel 狀態的?`masters`?屬性
Sentinel 狀態中的?`masters`?字典記錄了所有被 Sentinel 監視的主服務器的相關信息, 其中:
* 字典的鍵是被監視主服務器的名字。
* 而字典的值則是被監視主服務器對應的?`sentinel.c/sentinelRedisInstance`?結構。
每個?`sentinelRedisInstance`?結構(后面簡稱“實例結構”)代表一個被 Sentinel 監視的 Redis 服務器實例(instance), 這個實例可以是主服務器、從服務器、或者另外一個 Sentinel 。
實例結構包含的屬性非常多, 以下代碼展示了實例結構在表示主服務器時使用的其中一部分屬性, 本章接下來將逐步對實例結構中的各個屬性進行介紹:
~~~
typedef struct sentinelRedisInstance {
// 標識值,記錄了實例的類型,以及該實例的當前狀態
int flags;
// 實例的名字
// 主服務器的名字由用戶在配置文件中設置
// 從服務器以及 Sentinel 的名字由 Sentinel 自動設置
// 格式為 ip:port ,例如 "127.0.0.1:26379"
char *name;
// 實例的運行 ID
char *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;
~~~
`sentinelRedisInstance.addr`?屬性是一個指向?`sentinel.c/sentinelAddr`?結構的指針, 這個結構保存著實例的 IP 地址和端口號:
~~~
typedef struct sentinelAddr {
char *ip;
int port;
} sentinelAddr;
~~~
對 Sentinel 狀態的初始化將引發對?`masters`?字典的初始化, 而?`masters`?字典的初始化是根據被載入的 Sentinel 配置文件來進行的。
舉個例子, 如果用戶在啟動 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 12345 5
sentinel down-after-milliseconds master2 50000
sentinel parallel-syncs master2 5
sentinel failover-timeout master2 450000
~~~
那么 Sentinel 將為主服務器?`master1`?創建如圖 IMAGE_MASTER1 所示的實例結構, 并為主服務器?`master2`?創建如圖 IMAGE_MASTER2 所示的實例結構, 而這兩個實例結構又會被保存到 Sentinel 狀態的?`masters`?字典中, 如圖 IMAGE_SENTINEL_STATE 所示。



## 創建連向主服務器的網絡連接
初始化 Sentinel 的最后一步是創建連向被監視主服務器的網絡連接: Sentinel 將成為主服務器的客戶端, 它可以向主服務器發送命令, 并從命令回復中獲取相關的信息。
對于每個被 Sentinel 監視的主服務器來說, Sentinel 會創建兩個連向主服務器的異步網絡連接:
* 一個是命令連接, 這個連接專門用于向主服務器發送命令, 并接收命令回復。
* 另一個是訂閱連接, 這個連接專門用于訂閱主服務器的?`__sentinel__:hello`?頻道。
為什么有兩個連接?
在 Redis 目前的發布與訂閱功能中, 被發送的信息都不會保存在 Redis 服務器里面, 如果在信息發送時, 想要接收信息的客戶端不在線或者斷線, 那么這個客戶端就會丟失這條信息。
因此, 為了不丟失?`__sentinel__:hello`?頻道的任何信息, Sentinel 必須專門用一個訂閱連接來接收該頻道的信息。
而另一方面, 除了訂閱頻道之外, Sentinel 還又必須向主服務器發送命令, 以此來與主服務器進行通訊, 所以 Sentinel 還必須向主服務器創建命令連接。
并且因為 Sentinel 需要與多個實例創建多個網絡連接, 所以 Sentinel 使用的是異步連接。
圖 IMAGE_SENTINEL_CONNECT_SERVER 展示了一個 Sentinel 向被它監視的兩個主服務器?`master1`?和?`master2`?創建命令連接和訂閱連接的例子。

接下來的一節將介紹 Sentinel 是如何通過命令連接和訂閱連接來與被監視主服務器進行通訊的。
- 介紹
- 前言
- 致謝
- 簡介
- 第一部分:數據結構與對象
- 簡單動態字符串
- SDS 的定義
- SDS 與 C 字符串的區別
- SDS API
- 重點回顧
- 參考資料
- 鏈表
- 鏈表和鏈表節點的實現
- 鏈表和鏈表節點的 API
- 重點回顧
- 字典
- 字典的實現
- 哈希算法
- 解決鍵沖突
- rehash
- 漸進式 rehash
- 字典 API
- 重點回顧
- 跳躍表
- 跳躍表的實現
- 跳躍表 API
- 重點回顧
- 整數集合
- 整數集合的實現
- 升級
- 升級的好處
- 降級
- 整數集合 API
- 重點回顧
- 壓縮列表
- 壓縮列表的構成
- 壓縮列表節點的構成
- 連鎖更新
- 壓縮列表 API
- 重點回顧
- 對象
- 對象的類型與編碼
- 字符串對象
- 列表對象
- 哈希對象
- 集合對象
- 有序集合對象
- 類型檢查與命令多態
- 內存回收
- 對象共享
- 對象的空轉時長
- 重點回顧
- 第二部分:單機數據庫的實現
- 數據庫
- 數據庫鍵空間
- 重點回顧
- RDB 持久化
- RDB 文件結構
- 重點回顧
- AOF 持久化
- AOF 持久化的實現
- 重點回顧
- 事件
- 文件事件
- 重點回顧
- 參考資料
- 客戶端
- 客戶端屬性
- 重點回顧
- 服務器
- 命令請求的執行過程
- 重點回顧
- 第三部分:多機數據庫的實現
- 復制
- 舊版復制功能的實現
- 重點回顧
- Sentinel
- 啟動并初始化 Sentinel
- 重點回顧
- 參考資料
- 集群
- 節點
- 重點回顧
- 第四部分:獨立功能的實現
- 發布與訂閱
- 頻道的訂閱與退訂
- 重點回顧
- 參考資料
- 事務
- 事務的實現
- 重點回顧
- Lua 腳本
- 創建并修改 Lua 環境
- 重點回顧
- 排序
- SORT <key> 命令的實現
- 重點回顧
- 二進制位數組
- GETBIT 命令的實現
- 重點回顧
- 慢查詢日志
- 慢查詢記錄的保存
- 慢查詢日志的閱覽和刪除
- 添加新日志
- 重點回顧
- 監視器
- 成為監視器
- 向監視器發送命令信息
- 重點回顧
- 源碼、相關資源和勘誤