15.服務(wù)器

服務(wù)器

1. 命令請求的執(zhí)行過程

1.1 發(fā)送命令請求

下圖是客戶端接收并發(fā)送命令請求的過程。

客戶端接收并發(fā)送命令請求的過程

1.2 讀取命令請求

當(dāng)客戶端與服務(wù)器之間的連接套接字因為客戶端的寫入變得可讀時,服務(wù)器會調(diào)用命令請求處理器執(zhí)行以下操作:

  1. 讀取套接字中協(xié)議格式的命令請求,并將其保存到客戶端狀態(tài)的輸入緩沖區(qū)里。
  2. 對輸入緩沖區(qū)的命令請求分析,提取命令請求中包含的命令參數(shù),命令參數(shù)個數(shù),然后分別將參數(shù)、參數(shù)個數(shù)保存到客戶端狀態(tài)的argv、argc屬性里。
  3. 調(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)的主要屬性。

redisCommand結(jié)構(gòu)的主要屬性

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

sflags屬性的標(biāo)識

下圖是命令表示例。

命令表示例

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

設(shè)置客戶端狀態(tài)的cmd指針

命令表使用大小寫無關(guān)的查找算法,SET、set、Set都是同一個命令。

1.4 命令執(zhí)行器(2):執(zhí)行預(yù)備操作

在真正執(zhí)行命令之前,程序還需要執(zhí)行一些預(yù)備操作,保證命令可以正確、順利地執(zhí)行,這些操作包括:

預(yù)備操作

以上只列出了服務(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í)行后續(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ù)的過程。

客戶端接收并打印命令回復(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)確度不高:

Redis對時間的不同精確度策略

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值。

getOperationsPerSecond函數(shù)

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)志。

sigtermHandler函數(shù)

每次serverCron函數(shù)運行時,程序都會對服務(wù)器狀態(tài)的shutdown_asap屬性進行檢查,并根據(jù)屬性的值決定是否關(guān)閉服務(wù)器。

2.6 管理客戶端資源

serverCron每次執(zhí)行都會調(diào)用clientsCron函數(shù)。

clientsCron函數(shù)會對一定數(shù)量的客戶端進行以下兩個檢查:

clientsCron函數(shù)執(zhí)行的檢查

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ù)器進程:

子信號發(fā)來服務(wù)器進程

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

rdb_child_pid、aof_child_pid都為-1

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

以下是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ù)器狀態(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è)置操作,包括:

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)文件會有所不同:

  1. 如果服務(wù)器啟用了AOF持久化功能,那么服務(wù)器使用AOF文件來還原數(shù)據(jù)庫狀態(tài)。
  2. 如果服務(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ù)器將打印以下日志:

服務(wù)器初始化最后日志

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 命令請求的執(zhí)行過程 客戶端發(fā)送一個set命令到收到回復(fù)期間的步驟如下 1.客戶端向服務(wù)端發(fā)生命令請求SET KEY...
    簡書徐小耳閱讀 344評論 0 0
  • 數(shù)據(jù)庫 多數(shù)據(jù)庫結(jié)構(gòu) 一個Redis實例可以支持多個數(shù)據(jù)庫,當(dāng)客戶端與服務(wù)端連接并指定到某個數(shù)據(jù)庫時,兩者的結(jié)構(gòu)如...
    宇宙最強架構(gòu)師閱讀 745評論 0 3
  • 一、Redis高可用概述 在介紹Redis高可用之前,先說明一下在Redis的語境中高可用的含義。 我們知道,在w...
    空語閱讀 1,684評論 0 2
  • 1.Redis特性 1)速度快:數(shù)據(jù)存放在內(nèi)存上、基于C語言實現(xiàn)、單線程架構(gòu)預(yù)防多線程競爭問題;2)基于鍵值對的數(shù)...
    Sponge1128閱讀 748評論 0 1
  • redis為何需要持久化 redis是一個鍵值對的內(nèi)存數(shù)據(jù)庫,它將數(shù)據(jù)及狀態(tài)保存在內(nèi)存中,保證了對數(shù)據(jù)的最快訪問速...
    大傻_df4c閱讀 198評論 0 0

友情鏈接更多精彩內(nèi)容