Redis的内部运作机制——Redis详解
本文將分五個部分來分析和總結(jié)Redis的內(nèi)部機制,分別是:Redis數(shù)據(jù)庫、Redis客戶端、Redis事件、Redis服務(wù)器的初始化步驟、Redis命令的執(zhí)行過程。
首先介紹一下Redis服務(wù)器的狀態(tài)結(jié)構(gòu)。Redis使用一個類型為“redisServer”的數(shù)據(jù)結(jié)構(gòu)來保存整個Redis服務(wù)器的狀態(tài)(每個屬性按照即將講解的順序進行排序):
struct redisServer { int dbnum;//服務(wù)器的數(shù)據(jù)庫數(shù)量,值由服務(wù)器配置的“databases”選項決定,默認為16 redisDb *db;//數(shù)組,保存著服務(wù)器中的所有數(shù)據(jù)庫list *clients;//一個鏈表,保存了所有客戶端狀態(tài),每個鏈表元素都是“redisClient”結(jié)構(gòu)time_t unixtime;//保存秒級精度的系統(tǒng)當前UNIX時間戳,減少獲取系統(tǒng)當前時間的系統(tǒng)調(diào)用次數(shù),100毫秒更新一次 long long mstime;//保存毫秒級精度的系統(tǒng)當前UNIX時間戳 unsigned lruclock;//默認每10秒更新一次,用于計算數(shù)據(jù)庫鍵的空轉(zhuǎn)時長,數(shù)據(jù)庫鍵的空轉(zhuǎn)時長 = 服務(wù)器的“l(fā)ruclock”屬性值 - 數(shù)據(jù)庫鍵值對象的“l(fā)ru”屬性值long long ops_sec_last_sample_time;//上一次進行服務(wù)器每秒執(zhí)行命令數(shù)量抽樣的時間 long long ops_sec_last_sample_ops;//上一次進行服務(wù)器每秒執(zhí)行命令數(shù)量抽樣時,服務(wù)器已執(zhí)行命令的數(shù)量 long long ops_sec_samples[REDIS_OPS_SEC_SAMPLE];//環(huán)形數(shù)組,每個元素記錄一次服務(wù)器每秒執(zhí)行命令數(shù)量抽樣結(jié)果,估算服務(wù)器在最近一秒鐘處理的命令請求數(shù)量(數(shù)組長度默認為16,100毫秒更新一次) int ops_sec_idx;//ops_sec_samples數(shù)組的索引值,每次抽樣后值增1,等于16時重置為0size_t stat_peak_memory;//已使用內(nèi)存峰值int shutdown_asap;//關(guān)閉服務(wù)器的標識,1表示關(guān)閉,0不關(guān)閉pid_t rdb_child_pid;//記錄執(zhí)行BGSAVE命令的子進程的ID,-1表示服務(wù)器沒有正在執(zhí)行BGSAVE pid_t aof_child_pid;//記錄執(zhí)行BGREWRITEAOF命令的子進程的ID,-1表示服務(wù)器沒有正在執(zhí)行BGREWRITEAOF int aof_rewrite_scheduled;//1表示有BGREWRITEAOF命令被延遲了(服務(wù)器執(zhí)行BGSAVE期間收到的BGREWRITEAOF會被延遲到BGSAVE執(zhí)行完成之后執(zhí)行) struct saveparam *saveparams;//記錄了自動保存條件的數(shù)組(執(zhí)行BGSAVE的條件) long long dirty;//修改計數(shù)器(上一次執(zhí)行BGSAVE之后已經(jīng)產(chǎn)生了多少修改) time_t lastsave;//上一次執(zhí)行自動保存操作(BGSAVE)的時間 sds aof_buf;//AOF緩沖區(qū)int cronloops;//serverCron函數(shù)的運行次數(shù)計數(shù)器lua;//用于執(zhí)行Lua腳本的Lua環(huán)境 redisClient *lua_client;//Lua腳本的偽客戶端,在服務(wù)器運行的整個生命周期一直存在,直至服務(wù)器關(guān)閉才會關(guān)閉 dict *lua_scripts;//字典,記錄所有載入的Lua腳本,鍵為某個Lua腳本的SHA1校驗和,值為對應(yīng)的Lua腳本 dict *repl_scriptcache_dict;//字典,記錄已經(jīng)傳播給所有從服務(wù)器的所有Lua腳本,鍵為腳本的SHA1校驗和,值為NULL,用于EVALSHA1命令的復(fù)制long long slowlog_entry_id;//下一條慢查詢?nèi)罩镜腎D list *slowlog;//保存了所有慢查詢?nèi)罩镜逆湵?/span> long long slowlog_log_slower_than;//服務(wù)器配置“slowlog-log-slower-than”選項的值,表示查詢慢于多少微秒便記錄慢查詢?nèi)罩?/span> unsigned long slowlog_max_len;//服務(wù)器配置“slowlog-max-len”選項的值,表示服務(wù)器最多保存多少條慢查詢?nèi)罩居涗?#xff0c;若超出,最久的記錄會被覆蓋monitors;//鏈表,監(jiān)視器客戶端列表dict *pubsub_channels;//字典,保存所有頻道的訂閱關(guān)系,鍵為某個被訂閱的頻道,值為鏈表,記錄了所有訂閱這個頻道的客戶端 list *pubsub_patterns;//鏈表,保存所有模式的訂閱關(guān)系,每個鏈表節(jié)點都包含了訂閱的客戶端和被訂閱的模式 };Redis數(shù)據(jù)庫
在Redis服務(wù)器狀態(tài)結(jié)構(gòu)中,“dbnum”屬性記錄了服務(wù)器的數(shù)據(jù)庫數(shù)量,它的值可以通過服務(wù)器配置的“databases”選項決定,默認為16。“db”屬性是一個數(shù)組,保存保存著服務(wù)器中的所有數(shù)據(jù)庫,其中每一個數(shù)據(jù)庫都對應(yīng)一個“redisDb”數(shù)據(jù)結(jié)構(gòu):
struct redisDb { dict *dict;//數(shù)據(jù)庫鍵空間字典,保存數(shù)據(jù)庫中所有的鍵值對 dict *expires;//過期字典,保存數(shù)據(jù)庫中所有鍵的過期時間 dict *watched_keys;//字典,正在被WATCH命令監(jiān)視的鍵 };數(shù)據(jù)庫鍵空間
Redis數(shù)據(jù)庫結(jié)構(gòu)的“dict”屬性是Redis數(shù)據(jù)庫的鍵空間,底層由字典實現(xiàn)。所有在數(shù)據(jù)庫上的增刪改查,實際上都是通過對鍵空間字典進行相應(yīng)操作來實現(xiàn)的。除此之外,還需要進行一些額外的維護操作,主要有如下操作內(nèi)容:
(1)在讀取一個鍵之后,服務(wù)器會根據(jù)鍵是否存在來更新服務(wù)器的鍵空間命中次數(shù)或不命中次數(shù)(這兩個值可以在’info stats’命令返回中的’keyspace_hits’屬性和’keyspace_misses’屬性中查看)。
(2)在讀取一個鍵之后,服務(wù)器會更新鍵的LRU屬性值(最后一次使用時間,使用’object idletime’命令可以查看鍵的閑置時間)。
(3)在讀取一個鍵時,若發(fā)現(xiàn)該鍵已過期,則刪除這個鍵。
(4)如果有客戶端使用’watch’命令監(jiān)視了某個鍵,服務(wù)器在對被監(jiān)視的鍵進行修改之后,會將這個鍵標記為臟,從而讓事務(wù)程序注意到這個鍵已經(jīng)被修改過了。
(5)服務(wù)器每次修改一個鍵之后,都會對臟鍵計數(shù)器(即Redis服務(wù)器狀態(tài)的’dirty’屬性)的值加一,這個計數(shù)器會觸發(fā)服務(wù)器的持久化以及復(fù)制操作。
(6)如果服務(wù)器開啟了數(shù)據(jù)庫通知功能,在對鍵進行修改之后,服務(wù)器將按配置發(fā)送相應(yīng)的數(shù)據(jù)庫通知。
過期字典
Redis數(shù)據(jù)庫結(jié)構(gòu)的“expires”屬性保存了Redis數(shù)據(jù)庫所有擁有過期時間的鍵以及它們對應(yīng)的過期時間,底層同樣由字典實現(xiàn)。數(shù)據(jù)庫鍵過期時間的設(shè)置和刪除,實際上都是對過期字典的操作。其中,字典的鍵是一個個指針,分別指向鍵空間字典中的一個個鍵對象(共享對象,節(jié)省內(nèi)存空間);字典的值則是一個個long long類型的整數(shù)表示的毫秒精度的UNIX時間戳,保存數(shù)據(jù)庫鍵的過期時間。
1. 鍵過期時間設(shè)置
expire:以秒為單位,設(shè)置Redis鍵的生存時間。
pexpire:以毫秒為單位,設(shè)置Redis鍵的生存時間。
expireat:以秒為單位,設(shè)置Redis鍵的過期時間。
pexpireat:以毫秒為單位,設(shè)置Redis鍵的過期時間。
注:實際上’expire’、‘pexpire’、'expireat’命令最后都會轉(zhuǎn)換為’pexpireat’命令來執(zhí)行。
2. 鍵過期時間查看
ttl:以秒為單位,返回鍵的剩余生存時間。
pttl:以毫秒為單位,返回鍵的剩余生存時間。
3. 鍵過期判定
檢查當前Unix時間戳是否大于鍵的過期時間,是則過期,否則不過期。
4. 過期鍵的理論刪除策略
(1)定時刪除:
設(shè)置一個鍵過期時間的同時,創(chuàng)建一個定時器。每個帶有過期時間的鍵都對應(yīng)著一個定時器。
這種策略對內(nèi)存是最友好的,但對CPU時間是最不友好的。創(chuàng)建一個定時器需要用到Redis服務(wù)器中的時間事件,而當前時間事件的實現(xiàn)方式為無序鏈表,查找一個事件的時間復(fù)雜度為O(N),并不能高效地處理大量時間事件。
(2)惰性刪除:
訪問一個鍵的時候再檢測該鍵是否過期,是則刪除之。
這種策略對CPU時間是最友好的,但對內(nèi)存是最不友好的。沒被訪問到的過期鍵永遠不會被刪除,可以看做內(nèi)存泄露。對于運行狀態(tài)非常依賴于內(nèi)存的Redis來說,這種策略顯然會影響到Redis的性能。
(3)定期刪除:
這種策略是對前兩種策略的整合與折中方案。使用這種策略需要控制好刪除操作每次執(zhí)行的時長和執(zhí)行的頻率,否則會退化為前兩種策略的其中一種。
5. Redis采用的過期鍵刪除策略
Redis服務(wù)器實際使用的是惰性刪除和定期刪除兩種策略配合使用的方案。
(1)惰性刪除策略的實現(xiàn):
所有讀寫數(shù)據(jù)庫的Redis命令在執(zhí)行之前都會先檢查輸入鍵是否已過期,過期則刪除之。
(2)定期刪除策略的實現(xiàn):
在規(guī)定時間內(nèi),分多次遍歷服務(wù)器中的各個數(shù)據(jù)庫,從數(shù)據(jù)庫的過期字典中隨機檢查一部分鍵的過期時間,并刪除其中的過期鍵。
<1>定期刪除程序每次運行時,都會從一定數(shù)量的數(shù)據(jù)庫中取出一定數(shù)量的隨機鍵進行檢查,并刪除其中的過期鍵。
<2>使用一個全局變量記錄當前刪除程序檢查的是第幾個數(shù)據(jù)庫,下一次運行都會接著上一次的進度進行處理。
<3>隨著刪除程序的不斷執(zhí)行,服務(wù)器中所有的數(shù)據(jù)庫都會被檢查一遍,然后這個全局變量被重置為0,開始新一輪的檢查工作。
6. AOF、RDB和復(fù)制功能對過期鍵的處理
(1)生成RDB文件:
在執(zhí)行save或bgsave命令創(chuàng)建一個新的RDB文件時,程序會對數(shù)據(jù)庫中的鍵進行檢查,已過期的鍵不會被保存到新創(chuàng)建的RDB文件中。
(2)載入RDB文件:
- <1>主服務(wù)器模式:載入RDB文件時,程序會對文件中保存的鍵進行檢查,只有未過期的鍵會被 載入到數(shù)據(jù)庫中。
- <2>從服務(wù)器模式:文件中保存的所有鍵都會被載入到數(shù)據(jù)庫中。不過因為主從服務(wù)器在進行數(shù)據(jù)同步的時候,從服務(wù)器的數(shù)據(jù)庫會被清空,所以過期鍵對載入RDB文件的從服務(wù)器也不會造成影響。
(3)AOF文件寫入:
當過期鍵被惰性刪除或定期刪除之后,程序會向AOF文件追加一條del命令,來顯式地記錄該鍵已被刪除。
(4)AOF重寫:
程序會對數(shù)據(jù)庫中的鍵進行檢查,已過期的鍵不會被保存到重寫后的AOF文件中。
(5)復(fù)制:
當服務(wù)器運行在復(fù)制模式下時,從服務(wù)器的過期鍵刪除動作由主服務(wù)器控制:
- <1>主服務(wù)器在刪除一個過期鍵之后,會顯式地向所有從服務(wù)器發(fā)送一個del命令,告知從服務(wù)器刪除這個過期鍵。
- <2>從服務(wù)器在執(zhí)行客戶端發(fā)送的讀命令時,即使碰到過期鍵也不會將其刪除,而是將過期鍵的值繼續(xù)返回給客戶端。
- <3>從服務(wù)器只有在接到主服務(wù)器發(fā)送來的del命令之后,才會刪除過期鍵。
Redis客戶端
Redis服務(wù)器狀態(tài)結(jié)構(gòu)中的“clients”屬性是一個鏈表,保存了所有連接到當前服務(wù)器的客戶端狀態(tài),每個客戶端狀態(tài)使用類型為“redisClient”的數(shù)據(jù)結(jié)構(gòu)進行表示(每個屬性按照即將講解的順序進行排序):
//Redis客戶端的狀態(tài)結(jié)構(gòu) struct redisClient { redisDb *db;//記錄客戶端當前正在使用的數(shù)據(jù)庫 int fd;//客戶端正在使用的套接字描述符,-1表示偽客戶端(AOF文件或者Lua腳本),大于-1表示普通客戶端 robj *name;//客戶端名字 int flags;//客戶端標志,記錄了客戶端的角色,以及客戶端目前所處的狀態(tài) sds querybuf;//輸入緩沖區(qū),根據(jù)輸入內(nèi)容動態(tài)地縮小或擴大,但不能超過1GB,否則服務(wù)器將關(guān)閉這個客戶端 robj **argv;//命令與命令參數(shù),數(shù)組,每個元素都是一個字符串對象,argv[0]為命令,其余元素為參數(shù) int argc;//argv數(shù)組的長度 struct redisCommand *cmd;//當前執(zhí)行的命令的實現(xiàn)函數(shù),指向命令表中的命令結(jié)構(gòu) char buf[REDIS_REPLY_CHUNK_BYTES];//固定大小輸出緩沖區(qū),數(shù)組,默認大小為16KB int bufpos;//buf數(shù)組目前已使用的字節(jié)數(shù)量 list *reply;//可變大小輸出緩沖區(qū),鏈表 obuf_soft_limit_reached_time:記錄了“reply”輸出緩沖區(qū)第一次到達軟性限制的時間,用于計算持續(xù)超出軟性限制的時長,以此決定是否關(guān)閉客戶端 int authenticated;//0表示未通過身份驗證,1表示已通過身份驗證 time_t ctime:創(chuàng)建客戶端的時間,可用于計算客戶端與服務(wù)器連接的時間長度 time_t lastinteraction:客戶端與服務(wù)器最后一次進行互動的時間,可用于客戶端的空轉(zhuǎn)時長 multiState mstate;//事務(wù)狀態(tài),包含一個事務(wù)隊列,以及一個已入列命令計數(shù)器 };“db”:
是一個指針,指向Redis服務(wù)器狀態(tài)結(jié)構(gòu)中的“db”數(shù)組其中一個元素,表示當前客戶端正在使用的數(shù)據(jù)庫。
默認情況下,Redis客戶端的目標數(shù)據(jù)庫為0號數(shù)據(jù)庫,可以通過select命令切換,所以select命令的實現(xiàn)原理為:修改redisClient.db指針,讓它指向服務(wù)器中指定的數(shù)據(jù)庫。
示例圖:
“fd”:
連接當前客戶端與Redis服務(wù)器的套接字描述符。值為-1表示偽客戶端(AOF文件或者Lua腳本),值大于-1則表示普通客戶端。
Redis客戶端分為普通客戶端與偽客戶端兩種類型,其中通過網(wǎng)絡(luò)連接與Redis服務(wù)器進行連接的就是普通客戶端,反之則是偽客戶端了。偽客戶端也有兩種類型,分別是Lua腳本的偽客戶端和AOF文件的偽客戶端。Redis服務(wù)器狀態(tài)結(jié)構(gòu)的“l(fā)ua_client”屬性就保存了Lua腳本的偽客戶端,它會在Redis服務(wù)器初始化時就被創(chuàng)建,負責執(zhí)行Lua腳本中包含的Redis命令,在服務(wù)器運行的整個生命周期一直存在,直至服務(wù)器關(guān)閉才會關(guān)閉。而AOF偽客戶端則是在載入AOF文件時被創(chuàng)建,用于執(zhí)行AOF文件中的Redis命令,在AOF文件載入完成之后被關(guān)閉。
client list:列出目前所有連接到服務(wù)器的普通客戶端。
“name”:
當前客戶端名字。
client setname:為客戶端設(shè)置一個名字。
“flags”:
客戶端標志,記錄了客戶端的角色,以及客戶端目前所處的狀態(tài)。例如:REDIS_MASTER表示當前客戶端是一個主服務(wù)器;REDIS_BLOCKED表示當前客戶端正在被列表命令阻塞。它的值可以是單個標志,也可以是多個標志的二進制或。
“querybuf”:
輸入緩沖區(qū),存儲客戶端輸入的內(nèi)容,可以根據(jù)輸入內(nèi)容動態(tài)地縮小或擴大,但不能超過1GB,否則服務(wù)器將關(guān)閉這個客戶端。
“argv” & “argc”:
這兩個屬性的值都是由輸入緩沖區(qū)的內(nèi)容分析得來的。其中“argv”屬性是一個數(shù)組,數(shù)組的每個元素都是一個字符串對象,argv[0]為客戶端當前執(zhí)行的命令,其余元素為傳給該命令的參數(shù)。而“argc”屬性則記錄了“argv”數(shù)組的長度。
“cmd”:
當前執(zhí)行的命令的實現(xiàn)函數(shù),指向命令表中的命令結(jié)構(gòu)。
Redis服務(wù)器中保存著一個由字典實現(xiàn)的命令表,服務(wù)器會根據(jù)agrv[0]的值(不區(qū)分字母大小寫),在命令表中查找命令對應(yīng)的命令實現(xiàn)函數(shù),然后將“cmd”指針指向這個函數(shù)。
命令表示例圖:
redisCommand結(jié)構(gòu):保存了命令的實現(xiàn)函數(shù)、命令的標志、命令應(yīng)該給定的參數(shù)個數(shù),命令的總執(zhí)行次數(shù)和總消耗時長等統(tǒng)計信息。
“buf” & “bufpos”:
“buf”屬性是一個數(shù)組,作為固定大小的輸出緩沖區(qū),默認大小為16KB,用于保存長度比較小的回復(fù)(服務(wù)器給客戶端的回復(fù))。
“bufpos”屬性則記錄了“buf”目前已經(jīng)使用的字節(jié)數(shù)。
“reply”:
鏈表,可變大小的輸出緩沖區(qū),用于保存長度比較大的回復(fù)。
當“buf”數(shù)組的空間已用完,或者因為回復(fù)太大而沒辦法放進“buf”數(shù)組時,服務(wù)器就會開始使用可變大小緩沖區(qū)。
但可變大小的緩沖區(qū)也是有限制的,分為硬性限制與軟性限制兩種模式,一旦超過硬性限制服務(wù)器會立刻關(guān)閉客戶端,若是超過軟性限制,客戶端不會立刻被關(guān)閉,但若是持續(xù)一段時間一直超過軟性限制,服務(wù)器也是會關(guān)閉客戶端的。這兩種限制可以使用Redis配置的“client-output-buffer-limit”選項來進行配置:
client-output-buffer-limit
配置示例(以下分別為普通客戶端、從服務(wù)器客戶端、執(zhí)行發(fā)布與訂閱功能的客戶端設(shè)置不同的軟性限制與硬性限制):
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
“obuf_soft_limit_reached_time”:
記錄了“reply”輸出緩沖區(qū)第一次到達軟性限制的時間,用于計算持續(xù)超出軟性限制的時長,以此決定是否關(guān)閉客戶端。
“authenticated”:
身份驗證的標識,值為0表示未通過身份驗證,1則表示已通過身份驗證。
“ctime”:
創(chuàng)建客戶端的時間,可用于計算客戶端與服務(wù)器連接的時間長度。
client list:“age”域記錄了客戶端與服務(wù)器連接的時間長度。
“l(fā)astinteraction”:
客戶端與服務(wù)器最后一次進行互動的時間,可用于客戶端的空轉(zhuǎn)時長。
client list:“idle”域記錄了客戶端的空轉(zhuǎn)時長。
Redis事件
Redis服務(wù)器是一個事件驅(qū)動程序,需要處理兩類事件:文件事件與時間事件。
文件事件
Redis服務(wù)器通過套接字與客戶端或其他Redis服務(wù)器進行連接,而文件事件就是服務(wù)器對套接字操作的抽象。服務(wù)器與客戶端或其他服務(wù)器的通信會產(chǎn)生相應(yīng)的文件事件,而服務(wù)器通過監(jiān)聽并處理這些事件來完成一系列網(wǎng)絡(luò)通信操作。
Redis基于Reactor模式開發(fā)了自己的網(wǎng)絡(luò)事件處理器——文件事件處理器,文件事件處理器使用I/O多路復(fù)用程序來同時監(jiān)聽多個套接字,并根據(jù)套接字目前執(zhí)行的任務(wù)來為套接字關(guān)聯(lián)不同的事件處理器。當被監(jiān)聽的套接字準備好執(zhí)行連接應(yīng)答、讀取、寫入、關(guān)閉等操作時,與操作相對應(yīng)的文件事件就會產(chǎn)生,這時文件事件處理器就會調(diào)用套接字之前已關(guān)聯(lián)好的事件處理器來處理這些事件。
文件事件處理器的構(gòu)成:
(其中I/O多路復(fù)用程序通過隊列向文件事件分派器傳送套接字)
時間事件
Redis服務(wù)器中的一些操作需要在給定時間點執(zhí)行,時間事件就是服務(wù)器對這類定時操作的抽象。
1. 時間事件的分類
(1)定時事件:讓一段程序在指定的時間之后執(zhí)行一次。
(2)周期性事件:讓一段程序每隔指定的時間就執(zhí)行一次。
2. 時間事件的屬性
(1)id:Redis服務(wù)器為每個時間事件創(chuàng)建的全局唯一ID,從小到大遞增。
(2)when:毫秒精度的UNIX時間戳,記錄了時間事件的到達時間。
(3)timeProc:時間事件處理器,一個時間事件到達時就會被服務(wù)器調(diào)用的函數(shù)。
3. 時間事件的實現(xiàn)
服務(wù)器將所有時間事件都放在一個無序鏈表中,新加入的時間事件總是插入到鏈表的表頭中。每當時間事件執(zhí)行器運行時,它就遍歷整個鏈表,查找所有已經(jīng)到達的時間事件,并調(diào)用相應(yīng)的事件處理器。
正常模式下Redis服務(wù)器只使用serverCron一個時間事件,而在benchmark模式下,服務(wù)器也只使用兩個時間事件。所以時間事件無序鏈表幾乎退化成一個指針,使用它來保存時間事件并不影響事件執(zhí)行的性能。
4. 周期性事件serverCron
持續(xù)運行的Redis服務(wù)器需要定期對自身的資源和狀態(tài)進行檢查和調(diào)整,從而確保服務(wù)器可以長期、穩(wěn)定地運行,這些定期操作就由周期性事件serverCron來負責執(zhí)行。周期性事件serverCron會每隔一段時間執(zhí)行一次,直到服務(wù)器關(guān)閉為止。serverCron默認每隔100毫秒執(zhí)行一次,可以通過Redis配置中的“hz”選項來設(shè)置serverCron的每秒回執(zhí)行次數(shù)。
serverCron函數(shù)主要負責執(zhí)行的有以下操作:
(1)更新服務(wù)器時間緩存
為了減少獲取系統(tǒng)當前時間需要執(zhí)行的系統(tǒng)調(diào)用次數(shù),Redis服務(wù)器使用狀態(tài)結(jié)構(gòu)中的“unixtime”和“mstime”兩個屬性分別緩存秒級精度和毫秒級精度的系統(tǒng)當前UNIX時間戳,但是只能用于對時間精確度要求不高的功能,對時間精確度高的功能還是會執(zhí)行系統(tǒng)調(diào)用來獲取系統(tǒng)當前時間。
(2)更新LRU時鐘
使用服務(wù)器狀態(tài)的“l(fā)ruclock”屬性來保存,默認每10秒更新一次,用于計算數(shù)據(jù)庫鍵的空轉(zhuǎn)時長:數(shù)據(jù)庫鍵的空轉(zhuǎn)時長 = 服務(wù)器的“l(fā)ruclock”屬性值 - 數(shù)據(jù)庫鍵值對象的“l(fā)ru”屬性值。
INFO SERVER:“l(fā)ru_clock”域的值就是服務(wù)器狀態(tài)的“l(fā)ruclock”屬性值。
(3)更新服務(wù)器每秒執(zhí)行命令次數(shù)
INFO STATUS:“instantaneous_ops_per_sec”域的值就是Redis服務(wù)器在最近一秒鐘執(zhí)行的命令數(shù)量。
這個值是根據(jù)抽樣計算得到的所有結(jié)果的平均值。serverCron每100毫秒就進行一次抽樣計算,其中,Redis服務(wù)器狀態(tài)的“ops_sec_last_sample_time”屬性記錄上一次進行抽樣的時間,“ops_sec_last_sample_ops”屬性記錄上一次抽樣時服務(wù)器已執(zhí)行命令的數(shù)量,“ops_sec_samples”數(shù)組則用于存放所有抽樣計算的結(jié)果,默認長度為16,“ops_sec_idx”屬性指定本次抽樣計算結(jié)果應(yīng)放入“ops_sec_samples”數(shù)組的哪個索引位置,它的值在每次抽樣后自增1,等于16時重置為0。所以每次抽樣計算的過程大概如下:
ops_sec_samples[ops_sec_idx] = (服務(wù)器當前已執(zhí)行命令數(shù)量 - ops_sec_last_sample_ops屬性值) / (服務(wù)器當前時間 - ops_sec_last_sample_time屬性值) * 1000;
ops_sec_last_sample_ops屬性值 = 服務(wù)器當前已執(zhí)行命令數(shù)量;
ops_sec_last_sample_time屬性值 = 服務(wù)器當前時間;
ops_sec_idx ++;
if(ops_sec_idx == 16) ops_sec_idx = 0;
最后,服務(wù)器每秒執(zhí)行命令次數(shù) = ops_sec_samples數(shù)組元素總和 / ops_sec_samples數(shù)組長度,所以它只是一個估算值。
(4)更新服務(wù)器內(nèi)存峰值記錄
服務(wù)器狀態(tài)的“stat_peak_memory”屬性記錄了服務(wù)器已使用的內(nèi)存峰值。serverCron每次執(zhí)行都會比較“stat_peak_memory”屬性值與Redis服務(wù)器當前使用的內(nèi)存數(shù)量,若當前使用的內(nèi)存數(shù)量大于“stat_peak_memory”屬性值,則使用當前使用的內(nèi)存數(shù)量更新“stat_peak_memory”屬性的值。
INFO MEMORY:“used_memory_peak”域和“used_memory_peak_human”域分別以兩種格式記錄了服務(wù)器的內(nèi)存峰值。
(5)處理SIGTERM信號
在Redis服務(wù)器啟動時,Redis會為服務(wù)器進程的SIGTERM信號關(guān)聯(lián)一個信號處理器,這個信號處理器負責載服務(wù)器收到SIGTERM信號時,將Redis服務(wù)器狀態(tài)的“shutdown_asap”屬性值置為1。
“shutdown_asap”屬性是Redis服務(wù)器的關(guān)機標識,serverCron每次運行都會對它的值進行檢查,若其值為1則關(guān)閉Redis服務(wù)器,關(guān)閉之前會先進行RDB持久化。
(6)管理客戶端資源
serverCron每次運行都會對一定數(shù)量的客戶端進行以下兩個檢查:
<1>如果客戶端與服務(wù)器之間的連接已經(jīng)超時,即客戶端已經(jīng)在很長一段時間內(nèi)沒有與服務(wù)器互動,則關(guān)閉這個客戶端。
<2>若客戶端的輸入緩沖區(qū)大小超過了一定長度,則釋放當前輸入緩沖區(qū),重新創(chuàng)建一個默認大小的輸入緩沖區(qū),防止耗費過多內(nèi)存。
<3>若客戶端輸出緩沖區(qū)大小超出限制,則關(guān)閉客戶端。
(7)管理數(shù)據(jù)庫資源
serverCron每次運行都會對一部分數(shù)據(jù)庫進行檢查,刪除其中的過期鍵,并在有需要時對字典進行收縮操作。
(8)檢查持久化操作運行狀態(tài)、標識與運行條件
Redis服務(wù)器狀態(tài)中與持久化操作的運行狀態(tài)、標識以及運行條件相關(guān)的屬性有以下六個:
<1>rdb_child_pid:記錄執(zhí)行BGSAVE命令的子進程的ID,值為-1表示服務(wù)器沒有正在執(zhí)行BGSAVE命令。
<2>aof_child_pid:記錄執(zhí)行BGREWRITEAOF命令的子進程的ID,值為-1表示服務(wù)器沒有正在執(zhí)行BGREWRITEAOF命令。
<3>aof_rewrite_scheduled:BGREWRITEAOF命令延遲執(zhí)行的標識,值為1表示有BGREWRITEAOF命令被延遲了(服務(wù)器執(zhí)行BGSAVE命令期間收到的BGREWRITEAOF命令請求會被延遲到BGSAVE執(zhí)行完成之后執(zhí)行)。
<4>saveparams:記錄了自動保存條件的數(shù)組,即執(zhí)行BGSAVE的條件。可由“save”選項進行配置,例如:save 900 1,表示在900秒之內(nèi),對數(shù)據(jù)庫至少進行了1次修改,則執(zhí)行BGSAVE命令。可以配置個條件。
<5>dirty:修改計數(shù)器,記錄上一次執(zhí)行BGSAVE之后已經(jīng)產(chǎn)生了多少修改。
<6>lastsave:上一次執(zhí)行自動保存操作(BGSAVE)的時間。
serverCron每次運行都會檢查“rdb_child_pid”和“aof_child_pid”兩個屬性的值,只要其中一個屬性的值不為-1,就檢查子進程是否有信號發(fā)送給服務(wù)器進程:
<1>若有信號,表示新的RDB文件已經(jīng)生成完畢,或者AOF文件已經(jīng)重寫完成,服務(wù)器需要進行相應(yīng)命令的后續(xù)操作,比如用新的RDB文件替換現(xiàn)有的RDB文件,或者用重寫后的AOF文件替換現(xiàn)有的AOF文件。
<2>若沒信號,則表示持久化操作尚未完成,程序不做任何操作。
如果檢查之后發(fā)現(xiàn)“rdb_child_pid”和“aof_child_pid”兩個屬性的值都為-1,表示服務(wù)器沒有正在進行持久化操作,這時會按以下三個步驟進行相應(yīng)檢查:
<1>檢查“aof_rewrite_scheduled”屬性的值,若為1,表示有BGREWRITEAOF操作被延遲了,則開始一次新的BGREWRITEAOF操作。
<2>檢查服務(wù)器的自動保存條件是否滿足:循環(huán)取出“saveparams”數(shù)組中的所有條件配置,逐個與“dirty”和“l(fā)astsave”屬性的值進行對比,只要其中一個條件配置的時間間隔大于“dirty”屬性值,并且修改數(shù)量大于“l(fā)astsave”屬性值,則表示自動保存的條件已經(jīng)滿足,若此時服務(wù)器沒有正在執(zhí)行其他持久化操作,則開始一次新的BGSAVE操作。
<3>檢查服務(wù)器設(shè)置的AOF重寫條件是否滿足,如果滿足,并且服務(wù)器沒有正在執(zhí)行其他持久化操作,自動開始一次新的BGREWRITEAOF操作。
整個檢查過程的流程圖:
(9)將AOF緩沖區(qū)的內(nèi)容寫入AOF文件
如果服務(wù)器開啟了AOF持久化功能,serverCron運行時會檢查AOF緩沖區(qū)“aof_buf”中有沒有內(nèi)容,若有,則將AOF緩沖區(qū)中的內(nèi)容寫入AOF文件中。
(10)增加cronloops計數(shù)器的值
Redis服務(wù)器狀態(tài)的“cronloops”屬性記錄了serverCron函數(shù)執(zhí)行的次數(shù),serverCron會在每次執(zhí)行之后將“cronloops”屬性的值加一。
事件的調(diào)度與執(zhí)行
(1)獲取到達時間與當前時間最接近的時間事件。
(2)阻塞并等待文件事件產(chǎn)生。(避免頻繁輪詢時間事件)
(3)若有文件事件產(chǎn)生,則處理文件事件。
(4)若獲取的時間事件的到達時間已到,則執(zhí)行時間事件,完成之后重新從步驟一開始新一輪的事件循環(huán)。
Redis服務(wù)器對文件事件和時間事件的處理都是同步、有序、原子地執(zhí)行的。因為時間事件在文件事件之后執(zhí)行,并且事件之間不會出現(xiàn)搶占,所以時間事件的實際處理時間,通常會比時間事件設(shè)定的到達時間稍晚一些。
Redis服務(wù)器初始化步驟
(1)初始化服務(wù)器狀態(tài)結(jié)構(gòu)
創(chuàng)建一個struct redisServer類型的實例變量作為服務(wù)器的狀態(tài),并為結(jié)構(gòu)中的各個屬性設(shè)置默認值,例如:服務(wù)器的運行ID、默認配置文件路徑、默認端口等等,同時創(chuàng)建Redis命令表。
(2)載入配置選項
載入用戶指定的配置參數(shù)和配置文件,并根據(jù)用戶設(shè)定的配置,對服務(wù)器狀態(tài)變量的相關(guān)屬性進行修改。
(3)初始化服務(wù)器數(shù)據(jù)結(jié)構(gòu)
這一步主要是為服務(wù)器狀態(tài)中的一些數(shù)據(jù)結(jié)構(gòu)分配內(nèi)存,例如:
<1>“clients“:鏈表,保存所有與服務(wù)器連接的客戶端的狀態(tài)結(jié)構(gòu)。
<2>”db“:字典保存服務(wù)器的所有數(shù)據(jù)庫。
<3>”pubsub_channels“:字典,保存頻道訂閱信息。
<4>“pubsub_patterns”:鏈表,保存模式訂閱信息。
<5>”lua“:用于執(zhí)行Lua腳本的Lua環(huán)境。
<6>”slowlog“:用于保存慢查詢?nèi)罩尽?br /> 除此之外,還會進行一些非常重要的設(shè)置操作,例如:
<1>為服務(wù)器設(shè)置進程信號處理器。
<2>創(chuàng)建共享對象,例如經(jīng)常經(jīng)常用到的“OK”回復(fù)字符串對象,1到10000的字符串對象等等。
<3>為serverCron函數(shù)創(chuàng)建時間事件。
<4>如果AOF持久化功能已經(jīng)打開,則打開現(xiàn)有的AOF文件,若AOF文件不存在,則創(chuàng)建并打開一個新的AOF文件,為AOF寫入做好準備。
<5>初始化服務(wù)器的后臺I/O模塊,為將來的I/O操作做好準備。
(4)還原數(shù)據(jù)庫狀態(tài)
若服務(wù)器啟用了AOF持久化功能,則載入AOF文件,否則載入RDB文件,根據(jù)AOF文件或RDB文件記錄的內(nèi)容還原數(shù)據(jù)庫狀態(tài),同時在日志文件中打印出載入文件并還原數(shù)據(jù)庫狀態(tài)所耗費的時長。
(5)執(zhí)行事件循環(huán)
一切準備就緒,開始執(zhí)行服務(wù)器的事件循環(huán),開始接受客戶端的連接請求,處理客戶端發(fā)送的命令請求。
Redis命令請求的執(zhí)行過程
(1)發(fā)送命令請求
當用戶在客戶端中鍵入一個命令請求時,客戶端會將這個命令請求轉(zhuǎn)換成協(xié)議格式,然后通過連接到服務(wù)器的套接字,將協(xié)議格式的命令請求發(fā)送給服務(wù)器。
(2)讀取命令請求
當客戶端與服務(wù)器之間的連接套接字因為客戶端的寫入而變得可讀時,服務(wù)器將調(diào)用命令請求處理器來執(zhí)行以下操作:
<1>讀取套接字中協(xié)議格式的命令請求,并將其保存到客戶端狀態(tài)的輸入緩沖區(qū)“querybuf”中。
<2>對輸入緩沖區(qū)中的命令請求進行分析,提取出命令請求中包含的命令參數(shù),計算命令參數(shù)的個數(shù),然后分別將它們保存到客戶端狀態(tài)的“argv”屬性和“argc”屬性中。
<3>調(diào)用命令執(zhí)行器,執(zhí)行客戶端指定的命令。
(3)命令執(zhí)行器——查找命令
根據(jù)客戶端狀態(tài)的argv[0]參數(shù),在命令表中查找參數(shù)所指定的命令(查找結(jié)果不受命令名字大小寫影響),并將其保存到客戶端狀態(tài)的“cmd”屬性中。(命令表是一個字典,字典的鍵是命令的名字,字典的值是一個“redisCommand”結(jié)構(gòu),記錄著Redis命令的實現(xiàn)函數(shù)與一些統(tǒng)計信息)
設(shè)置客戶端狀態(tài)的“cmd”屬性的示例圖:
(4)命令執(zhí)行器——執(zhí)行預(yù)備操作
<1>檢查客戶端狀態(tài)的“cmd”指針是否指向NULL,以此判斷用戶輸入的命令是否存在。
<2>根據(jù)客戶端狀態(tài)的“cmd”屬性指向的“redisCommand”結(jié)構(gòu)中的“arity”屬性值和客戶端狀態(tài)的“argc”屬性值,判斷用戶輸入的命令參數(shù)個數(shù)是否正確。
<3>通過客戶端狀態(tài)的“authenticated”屬性值判斷客戶端是否已經(jīng)通過了身份驗證,未通過只能執(zhí)行AUTH命令。
<4>如果服務(wù)器打開了“maxmemory”功能,在執(zhí)行命令之前,需要先檢查服務(wù)器的內(nèi)存占用情況,并在有需要時進行內(nèi)存回收,若內(nèi)存回收失敗則不再執(zhí)行后續(xù)步驟,向客戶端返回一個錯誤。
<5>如果服務(wù)器上一次執(zhí)行BGSAVE命令時出錯,并且服務(wù)器打開了"stop-writes-on-bgsave-error"功能, 而且服務(wù)器即將要執(zhí)行的命令是一個寫命令,那么服務(wù)器將拒絕執(zhí)行這個命令,并向客戶端返回一個錯誤。
<6>如果客戶端當前正在用SUBSCRIBE命令訂閱頻道,或者正在用PSUBSCRIBE命令訂閱模式, 那么服務(wù)器只會執(zhí)行客戶端發(fā)來的SUBSCRIBE 、PSUBSCRIBE 、UNSUBSCRIBE 、PUNSUBSCRIBE四個命令,其他別的命令都會被服務(wù)器拒絕。
<7>如果服務(wù)器正在進行數(shù)據(jù)載入,那么客戶端發(fā)送的命令必須帶有“l(fā)”標識(比如INFO 、SHUTDOWN 、PUBLISH,等等)才會被服務(wù)器執(zhí)行,其他別的命令都會被服務(wù)器拒絕。
<8>如果服務(wù)器因為執(zhí)行Lua腳本而超時并進入阻塞狀態(tài),那么服務(wù)器只會執(zhí)行客戶端發(fā)來的SHUTDOWN nosave命令和SCRIPT KILL命令,其他別的命令都會被服務(wù)器拒絕。
<9>如果客戶端正在執(zhí)行事務(wù),那么服務(wù)器只會執(zhí)行客戶端發(fā)來的EXEC 、DISCARD 、MULTI 、WATCH四個命令,其他命令都會被放進事務(wù)隊列中。
<10>如果服務(wù)器打開了監(jiān)視器功能,那么服務(wù)器會將要執(zhí)行的命令和參數(shù)等信息發(fā)送給監(jiān)視器。
(5)命令執(zhí)行器——調(diào)用命令的實現(xiàn)函數(shù)
client->cmd->proc(client);//client是指向客戶端狀態(tài)的指針
調(diào)用實現(xiàn)函數(shù)執(zhí)行指定操作,產(chǎn)生的相應(yīng)的命令回復(fù),將其保存到客戶端狀態(tài)的輸出緩沖區(qū)中,并為客戶端的套接字關(guān)聯(lián)命令回復(fù)處理器,這個處理器負責將命令回復(fù)返回給客戶端。
(6)命令執(zhí)行器——執(zhí)行后續(xù)工作
<1>如果服務(wù)器開啟了慢查詢?nèi)罩竟δ?#xff0c;那么慢查詢?nèi)罩灸K會檢查是否需要為剛剛執(zhí)行完的命令請求添加一條新的慢查詢?nèi)罩尽?br /> <2>根據(jù)剛剛執(zhí)行命令所耗費的時長,更新被執(zhí)行命令的“redisCommand”結(jié)構(gòu)的“milliseconds”屬性, 并將命令的“redisCommand”結(jié)構(gòu)的“calls”計數(shù)器的值增一。
<3>如果服務(wù)器開啟了AOF持久化功能, 那么AOF持久化模塊會將剛剛執(zhí)行的命令請求寫入到AOF緩沖區(qū)里面。
<4>如果有其他從服務(wù)器正在復(fù)制當前這個服務(wù)器,那么服務(wù)器會將剛剛執(zhí)行的命令傳播給所有從服務(wù)器。
(7)將命令回復(fù)發(fā)送給客戶端
當客戶端套接字變?yōu)榭蓪憰r,服務(wù)器就會執(zhí)行命令回復(fù)處理器,將保存在客戶端輸出緩沖區(qū)的命令回復(fù)發(fā)送給客戶端。發(fā)送完畢之后,清空客戶端輸出緩沖區(qū)。
(8)客戶端接受并打印命令回復(fù)
客戶端接收到協(xié)議格式的命令回復(fù)之后,將其轉(zhuǎn)換成人類可讀的格式,并打印在客戶端屏幕上。
總結(jié)
以上是生活随笔為你收集整理的Redis的内部运作机制——Redis详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: AI理论知识基础(23)-齐次坐标
- 下一篇: linux cmake编译源码,linu