服務(wù)器
1. 命令請求的執(zhí)行過程
1.1 發(fā)送命令請求
下圖是客戶端接收并發(fā)送命令請求的過程。

1.2 讀取命令請求
當(dāng)客戶端與服務(wù)器之間的連接套接字因為客戶端的寫入變得可讀時,服務(wù)器會調(diào)用命令請求處理器執(zhí)行以下操作:
- 讀取套接字中協(xié)議格式的命令請求,并將其保存到客戶端狀態(tài)的輸入緩沖區(qū)里。
- 對輸入緩沖區(qū)的命令請求分析,提取命令請求中包含的命令參數(shù),命令參數(shù)個數(shù),然后分別將參數(shù)、參數(shù)個數(shù)保存到客戶端狀態(tài)的argv、argc屬性里。
- 調(diào)用命令執(zhí)行器,執(zhí)行客戶端指定的命令。
1.3 命令執(zhí)行器(1):查找命令實現(xiàn)
命令執(zhí)行器首先根據(jù)客戶端狀態(tài)的argv[0]參數(shù),在命令表中查找參數(shù)指定的命令,并將找到的命令保存到客戶端狀態(tài)的cmd屬性里。
命令表字典的值是一個個的redisCommand結(jié)構(gòu),每個redisCommand結(jié)構(gòu)記錄了一個Redis命令的實現(xiàn)信息。
下圖是redisCommand結(jié)構(gòu)的主要屬性。

下圖是sflags屬性的標(biāo)識。

下圖是命令表示例。

查找后,命令表會返回對應(yīng)的redisCommand結(jié)構(gòu),客戶端狀態(tài)的cmd指針會指向這個redisCommand結(jié)構(gòu)。

命令表使用大小寫無關(guān)的查找算法,SET、set、Set都是同一個命令。
1.4 命令執(zhí)行器(2):執(zhí)行預(yù)備操作
在真正執(zhí)行命令之前,程序還需要執(zhí)行一些預(yù)備操作,保證命令可以正確、順利地執(zhí)行,這些操作包括:

以上只列出了服務(wù)器在單機模式下執(zhí)行命令的檢查操作,當(dāng)服務(wù)器在復(fù)制、集群模式下執(zhí)行命令時,預(yù)備操作還會多一些。
1.5 命令執(zhí)行器(3):調(diào)用命令的實現(xiàn)函數(shù)
執(zhí)行命令后,命令回復(fù)會被保存到客戶端狀態(tài)的輸出緩沖區(qū)中,之后,實現(xiàn)函數(shù)還會為客戶端的套接字關(guān)聯(lián)命令回復(fù)處理器,這個處理器負責(zé)將命令回復(fù)返回給客戶端。
1.6 命令執(zhí)行器(4):執(zhí)行后續(xù)工作
執(zhí)行完函數(shù)后,服務(wù)器會做一些后續(xù)操作:

以上操作執(zhí)行完,服務(wù)器就可以繼續(xù)從文件事件處理器取出并處理下一個命令請求。
1.7 將命令回復(fù)發(fā)送給客戶端
當(dāng)客戶端套接字變?yōu)榭蓪憼顟B(tài)時,服務(wù)器會執(zhí)行命令回復(fù)處理器,將保存在客戶端輸出緩沖區(qū)中的命令回復(fù)發(fā)送給客戶端。
當(dāng)命令回復(fù)發(fā)送完畢后,回復(fù)處理器會清空客戶端狀態(tài)的輸出緩沖區(qū),為處理下一個命令請求做好準(zhǔn)備。
1.8 客戶端接收并打印命令回復(fù)
下圖是客戶端接收并打印命令回復(fù)的過程。

2. serverCron函數(shù)
struct redisServer{
// ...
// 保存秒級精度的系統(tǒng)當(dāng)前UNIX時間戳
time_t unixtime;
// 保存毫秒級精度的系統(tǒng)當(dāng)前UNIX時間戳
long long mstime;
// 默認(rèn)每10秒更新一次的時鐘緩存,用于計算鍵的空轉(zhuǎn)時長。
unsigned lruclock:22;
// 上一次進行抽樣的時間
long long ops_sec_last_sample_time;
// 上一次抽樣時,服務(wù)器已執(zhí)行命令的數(shù)量
long long ops_sec_last_sample_ops;
// 數(shù)組中的每個項都記錄了一次抽樣結(jié)果
// REDIS_OPS_SEC_SAMPLES默認(rèn)值為16,環(huán)形數(shù)組
long long ops_sec_samples[REDIS_OPS_SEC_SAMPLES];
// ops_sec_samples數(shù)組的索引值
// 每次抽樣后將值自增1
// 在值等于16時重置為0
// 讓ops_sec_samples數(shù)組構(gòu)成一個環(huán)形數(shù)組。
int ops_sec_idx;
// 已使用內(nèi)存峰值
size_t stat_peak_memory;
// 關(guān)閉服務(wù)器的標(biāo)識
// 值為1時,關(guān)閉服務(wù)器
// 值為0時,不做動作
int shutdown_asap;
// 如果值為1,表示有BGREWRITEAOF命令被延遲了
int aof_rewrite_scheduled;
// 記錄執(zhí)行BGSAVE命令的子進程的ID
// 如果服務(wù)器沒有在執(zhí)行BGSAVE,那么這個屬性的值為-1
pid_t rdb_child_pid;
// 記錄執(zhí)行BGREWRITEAOF命令的子進程的ID
// 如果服務(wù)器沒有在執(zhí)行BGREWRITEAOF,那么這個屬性的值為-1
pid_t aof_child_pid;
// serverCron函數(shù)的運行次數(shù)計數(shù)器
// serverCron函數(shù)每執(zhí)行一次,這個屬性的值就增加1
int cronloops;
//...
}
2.1 更新服務(wù)器時間緩存
每次獲取系統(tǒng)的當(dāng)前時間都需要執(zhí)行一次系統(tǒng)調(diào)用,為了減少系統(tǒng)調(diào)用的執(zhí)行次數(shù),unixtime、mstime被用作當(dāng)前時間的緩存。
serverCron默認(rèn)以每100毫秒一次更新unixtime、mstime屬性,所以這兩個屬性記錄的時間準(zhǔn)確度不高:

2.2 更新LRU時鐘
服務(wù)器狀態(tài)中的lruclock屬性保存了服務(wù)器的LRU時鐘,也是服務(wù)器時間緩存的一種。
每個Redis對象都會有一個lru屬性,保存對象最后一次被命令訪問的時間。
typedef struct redisObject{
// ...
unsigned lru:22;
// ...
} robj;
程序會用服務(wù)器的lruclock記錄的時間減去對象的lru記錄的時間,得到對象的空轉(zhuǎn)時間。
serverCron默認(rèn)以每10s一次的頻率更新lruclock屬性的值。
lruclock時鐘的當(dāng)前值可以通過INFO server命令的lru_clock域查看。
2.3 更新服務(wù)器每秒執(zhí)行命令次數(shù)
serverCron函數(shù)中的trackOperationsPerSecond函數(shù)以每100毫秒一次的頻率執(zhí)行,這個函數(shù)的功能是以抽樣計算的方式,估算并記錄服務(wù)器在最近一秒鐘處理的命令請求數(shù)量,這個值可以通過INFO status命令的instantaneous_ops_per_sec域查看。
trackOperationsPerSecond函數(shù)和服務(wù)器狀態(tài)中4個ops_sec_開頭的屬性有關(guān)。
trackOperationsPerSecond函數(shù)每次運行,都會根據(jù)ops_sec_last_sample_time記錄的上一次抽樣時間和服務(wù)器的當(dāng)前時間,以及ops_sec_last_sample_ops
記錄的上一次抽樣的已執(zhí)行命令數(shù)量和服務(wù)器當(dāng)前的已執(zhí)行命令數(shù)量,計算出兩次trackOperationsPerSecond調(diào)用之間,服務(wù)器平均每一毫秒處理了多少個命令請求,然后將這個平均值乘以1000
,就得到了服務(wù)器在一秒內(nèi)能處理多少個命令請求的估值值,這個估計值會被作為一個新的數(shù)組項放進ops_sec_samples環(huán)形數(shù)組里。
當(dāng)客戶端調(diào)用INFO命令時,服務(wù)器就會調(diào)用getOperationsPerSecond函數(shù),根據(jù)ops_sec_samples環(huán)形數(shù)組中的抽樣結(jié)果,計算出instantaneous_ops_per_sec值。

2.4 更新服務(wù)器內(nèi)存峰值記錄
stat_peak_memory記錄服務(wù)器的內(nèi)存峰值大小。
每次serverCron執(zhí)行時,程序都會查看服務(wù)器當(dāng)前使用的內(nèi)存數(shù)量,并與stat_peak_memory比較,如果當(dāng)前使用的內(nèi)存數(shù)量比stat_peak_memory
大,那么程序?qū)?dāng)前使用的內(nèi)存數(shù)量記錄到stat_peak_memory屬性。
INFO memory命令的used_memory_peak、used_memory_peak_human記錄了服務(wù)器的內(nèi)存峰值。
2.5 處理SIGTERM信號
在啟動服務(wù)器時,Redis會為服務(wù)器進程的SIGTERM信號關(guān)聯(lián)處理器sigtermHandler函數(shù),這個信號處理器負責(zé)在服務(wù)器接到SIGTERM信號時,打開服務(wù)器狀態(tài)的shutdown_asap標(biāo)志。

每次serverCron函數(shù)運行時,程序都會對服務(wù)器狀態(tài)的shutdown_asap屬性進行檢查,并根據(jù)屬性的值決定是否關(guān)閉服務(wù)器。
2.6 管理客戶端資源
serverCron每次執(zhí)行都會調(diào)用clientsCron函數(shù)。
clientsCron函數(shù)會對一定數(shù)量的客戶端進行以下兩個檢查:

2.7 管理數(shù)據(jù)庫資源
serverCron每次執(zhí)行時都會調(diào)用databasesCron函數(shù)。這個函數(shù)會對服務(wù)器中的一部分?jǐn)?shù)據(jù)庫進行檢查,刪除其中的過期鍵,并在有需要時,對字典進行收縮操作。
2.8 執(zhí)行被延遲的BGREWRITEAOF
在服務(wù)器執(zhí)行BGSAVE期間,如果客戶端向服務(wù)器發(fā)來BGREWRITEAOF命令,那么服務(wù)器會將BGREWRITEAOF命令的執(zhí)行時間延遲到BGSAVE命令執(zhí)行完畢后。
aof_rewrite_scheduled記錄了服務(wù)器是否延遲了BGREWRITEAOF命令。
每次serverCron函數(shù)執(zhí)行時,都會檢查BGSAVE、BGREWRITEAOF是否正在執(zhí)行,如果兩個命令都沒有在執(zhí)行,并且aof_rewrite_scheduled的值為1,那么服務(wù)器就會執(zhí)行之前被推延的BGREWRITEAOF命令。
2.9 檢查持久化操作的運行狀態(tài)
每次serverCron函數(shù)執(zhí)行時,程序都會檢查rdb_child_pid、aof_child_pid,只要其中一個屬性的值不為-1,程序就會執(zhí)行一次wait3函數(shù),檢查子進程是否有信號發(fā)來服務(wù)器進程:

rdb_child_pid、aof_child_pid都為-1,表示服務(wù)器沒有進行持久化操作,在這種情況下,程序執(zhí)行以下三個檢查:

2.10 將AOF緩沖區(qū)的內(nèi)容寫入AOF文件
如果服務(wù)器開啟了AOF持久化功能,并且AOF緩沖區(qū)里還有待寫入的數(shù)據(jù),那么serverCron會調(diào)用相應(yīng)的程序,將AOF緩沖區(qū)的內(nèi)容寫入到AOF文件里。
2.11 關(guān)閉異步客戶端
服務(wù)器會關(guān)閉那些輸出緩沖區(qū)大小超出限制的客戶端。
2.12 增加cronloops計數(shù)器的值
cronloops記錄了serverCron函數(shù)執(zhí)行的次數(shù)。
3. 初始化服務(wù)器
3.1 初始化服務(wù)器狀態(tài)結(jié)構(gòu)
初始化服務(wù)器的第一步就是創(chuàng)建一個struct redisServer類型的實例變量server作為服務(wù)器的狀態(tài),并為結(jié)構(gòu)中的各個屬性設(shè)置默認(rèn)值。
初始化server變量的工作由redis.c/initServerConfig完成。

以下是initServerConfig完成的主要工作。

3.2 載入配置選項
服務(wù)器在用initServerConfig初始化完server變量后,就開始載入用戶給定的配置參數(shù)、配置文件,并根據(jù)用戶設(shè)定的配置,對server變量相關(guān)屬性的值修改。
3.3 初始化服務(wù)器數(shù)據(jù)結(jié)構(gòu)
下圖是服務(wù)器狀態(tài)包含的其他數(shù)據(jù)結(jié)構(gòu)。

在這時,服務(wù)器將調(diào)用initServer函數(shù),為上面的數(shù)據(jù)結(jié)構(gòu)分配內(nèi)存,并在有需要時,為這些數(shù)據(jù)結(jié)構(gòu)設(shè)置、關(guān)聯(lián)初始化值。
服務(wù)器到現(xiàn)在才初始化數(shù)據(jù)結(jié)構(gòu)的原因在于,服務(wù)器必須先載入用戶指定的配置選項,然后才能正確地對數(shù)據(jù)結(jié)構(gòu)進行初始化。
服務(wù)器選擇了將server狀態(tài)的初始化分為兩步執(zhí)行,initServerConfig主要負責(zé)初始化一般屬性,initServer主要負責(zé)初始化數(shù)據(jù)結(jié)構(gòu)。
除了初始化數(shù)據(jù)結(jié)構(gòu)外,initServer還進行了一些非常重要的設(shè)置操作,包括:

當(dāng)initServer執(zhí)行完之后,服務(wù)器將打印Redis圖標(biāo)、Redis版本號。
3.4 還原數(shù)據(jù)庫狀態(tài)
在完成對服務(wù)器狀態(tài)server變量的初始化后,服務(wù)器需要載入RDB、AOF文件,并根據(jù)文件記錄的內(nèi)容來還原服務(wù)器的數(shù)據(jù)庫狀態(tài)。
根據(jù)服務(wù)器是否啟用了AOF持久化功能,服務(wù)器載入數(shù)據(jù)時所使用的目標(biāo)文件會有所不同:
- 如果服務(wù)器啟用了AOF持久化功能,那么服務(wù)器使用AOF文件來還原數(shù)據(jù)庫狀態(tài)。
- 如果服務(wù)器沒有啟用了AOF持久化功能,那么服務(wù)器使用RDB文件來還原數(shù)據(jù)庫狀態(tài)。
當(dāng)完成數(shù)據(jù)庫狀態(tài)還原后,服務(wù)器將打印出載入文件并還原數(shù)據(jù)庫狀態(tài)所耗費的時長。
3.5 執(zhí)行事件循環(huán)
在初始化最后一步,服務(wù)器將打印以下日志:

并開始執(zhí)行服務(wù)器的事件循環(huán)(loop)。