第二部分 單機(jī)數(shù)據(jù)庫的實(shí)現(xiàn)
[toc]
1. 數(shù)據(jù)庫
? 本章說明服務(wù)器保存數(shù)據(jù)庫的方法,客戶端切換數(shù)據(jù)庫的方法,數(shù)據(jù)庫保存鍵值對的方法,還有針對數(shù)據(jù)庫的增刪改查等操作的實(shí)現(xiàn)方法等等.
(1). 服務(wù)器中的數(shù)據(jù)庫
這里說的數(shù)據(jù)庫是指一個(gè)Redis程序中的多個(gè)命名空間,它們相互無關(guān),所以認(rèn)為是不同的庫.
? Redis服務(wù)器將所有數(shù)據(jù)庫都保存在redis.h/redisServer結(jié)構(gòu)的db數(shù)組中,db數(shù)組中的每一個(gè)元素都是一個(gè)redis.h/redisDb結(jié)構(gòu).每一個(gè)redisDb結(jié)構(gòu)代表一個(gè)數(shù)據(jù)庫.
struct redisServer{
// ......
// db數(shù)組
redisDb *db;
// 服務(wù)器的數(shù)據(jù)庫數(shù)量
int dbnum;
// .....
};
? dbnum的值默認(rèn)為16,可以通過配置更改.
(2). 切換數(shù)據(jù)庫
? 初始化完成后,默認(rèn)使用的數(shù)據(jù)庫是0號數(shù)據(jù)庫,可以使用SELECT命令切換數(shù)據(jù)庫.
redis> SET msg "hello"
OK
redis> GET msg
"hello"
redis> SELECT 1
OK
redis[1]> GET msg
(nil)
? 在服務(wù)器內(nèi)部,客戶端狀態(tài)redisClient結(jié)構(gòu)(每一個(gè)Redis客戶端連接服務(wù)端,就有一個(gè)該結(jié)構(gòu)的實(shí)例)的db屬性記錄了客戶端當(dāng)前的目標(biāo)數(shù)據(jù)庫.也就是說這個(gè)結(jié)構(gòu)用于服務(wù)端在對客戶端發(fā)出的命令進(jìn)行響應(yīng)時(shí),選擇哪一個(gè)數(shù)據(jù)庫進(jìn)行響應(yīng).
typedef struct redisClient{
// .....
// 記錄客戶端正在使用的數(shù)據(jù)庫,指向之前db數(shù)組中的一個(gè)元素
redis *db;
// .....
} redisClient;
Redis沒有返回?cái)?shù)據(jù)庫的命令,也就是說,除了Redis命令行中會有號碼外,不能得知當(dāng)前使用的是哪一個(gè)數(shù)據(jù)庫,因此在其他語言中通過接口使用Redis數(shù)據(jù)庫,要謹(jǐn)防多次切換數(shù)據(jù)庫后忘記當(dāng)前使用的是哪一個(gè)數(shù)據(jù)庫.
安全起見,最好先執(zhí)行一個(gè)SELECT進(jìn)行切換.
(3). 數(shù)據(jù)庫鍵空間
? Redis是一個(gè)鍵值對數(shù)據(jù)庫服務(wù)器,服務(wù)器中的每一個(gè)數(shù)據(jù)庫都由一個(gè)redis.h/redisDb表示.其中的dict是一個(gè)字典,存儲了這個(gè)數(shù)據(jù)庫所有的鍵值對,被稱為鍵空間.
typedef struct redisDb{
// .....
// 數(shù)據(jù)庫鍵空間,是一個(gè)字典,保存了數(shù)據(jù)庫中所有的鍵值對
dict *dict;
// .....
} redisDb;
? 同字典一樣,鍵空間的鍵都是string對象,值可以使任意的redisObject.
? 對數(shù)據(jù)庫的操作,比如新建一個(gè)字符串鍵,都是通過對鍵空間這個(gè)字典的操作來實(shí)現(xiàn)的.增刪改查都是如此.
(4). 設(shè)置鍵的生存時(shí)間
? 通過EXPIRE命令或者PEXPIRE命令,可以以秒或者毫秒的精度為數(shù)據(jù)庫中的一個(gè)鍵設(shè)置生存時(shí)間.經(jīng)過指定的一段時(shí)間后,服務(wù)器就會自動刪除生存時(shí)間為0的鍵.
? SETEX命令可以在聲明字符串的時(shí)候就設(shè)置生存時(shí)間,后面分布式鎖會用到.只能用于字符串.
? 和EXPIRE命令或者PEXPIRE命令類似,還可以使用EXPIREAT命令或者PEXPIREAT命令設(shè)置鍵的死亡時(shí)刻,傳入一個(gè)UNIX時(shí)間戳.到達(dá)這個(gè)時(shí)間戳,這個(gè)對象就被清理.
? TTl和PTTL命令接受一個(gè)帶有生存時(shí)間或者死亡時(shí)刻的鍵,返回不同時(shí)間精度的剩余存活時(shí)長.
? 實(shí)際上底層都是通過PEXPIREAT命令實(shí)現(xiàn)的
1). 保存過期時(shí)間
? redisDB結(jié)構(gòu)的expires字典保存了數(shù)據(jù)庫中所有鍵的過期時(shí)間,我們也成這個(gè)鍵為過期字典.
typedef struct redisDb{
// .....
// 數(shù)據(jù)庫鍵空間,是一個(gè)字典,保存了數(shù)據(jù)庫中所有的鍵值對
dict *dict;
// 過期字典
dict expires;
// .....
} redisDb;
? 設(shè)置一個(gè)鍵的過期時(shí)間實(shí)際上就是在過期字典中添加一個(gè)string和string的鍵值對,只不過值存儲的是long long類型的時(shí)間.
? 那么很明顯,移除過期時(shí)間就是將過期字典中的一個(gè)鍵值對移除.返回過期時(shí)間就是相應(yīng)的查詢操作.
2). 過期鍵的判定
? 使用過期字典,可以通過以下方式檢查一個(gè)鍵是否過期:
- 檢查這個(gè)鍵是否存在于過期字典,如果存在那么取得過期時(shí)間戳,如果不存在那么一定沒過期
- 將過期時(shí)間戳與當(dāng)前的UNIX時(shí)間戳進(jìn)行比較,如果是過去的時(shí)間戳,那么這個(gè)鍵過期了.
(5). 過期鍵刪除策略
三種不同的刪除策略:
- 定時(shí)刪除:設(shè)置一個(gè)計(jì)時(shí)器,計(jì)時(shí)器通知在鍵的過期時(shí)間來立即執(zhí)行刪除操作
- 惰性刪除:獲取鍵時(shí),判斷是否過期,如果過期,刪除
- 定期刪除:每隔一段時(shí)間檢查過期鍵,并刪除
1). 定時(shí)刪除
? 定時(shí)刪除策略對內(nèi)存是最友好的,因?yàn)槟茏罴皶r(shí)的刪除過期鍵,但對CPU時(shí)間非常不友好,因?yàn)橛行r(shí)間CPU非常緊張二內(nèi)存還很充裕.此時(shí)如果進(jìn)行了刪除鍵,會對CPU壓力非常大,影響響應(yīng)時(shí)間和吞吐量.
? 此外,定時(shí)器需要用到Redis服務(wù)器中的時(shí)間事件,實(shí)現(xiàn)方式為無序鏈表,查找時(shí)間事件的時(shí)間復(fù)雜度為O(n),并不高效.
? 所以定時(shí)刪除策略并不是很現(xiàn)實(shí).
2). 惰性刪除策略
? 惰性刪除對CPU時(shí)間是非常友好的,保證過期鍵的操作只在非做不可的情況下執(zhí)行,并且僅僅刪除當(dāng)前處理的鍵.不會在其他鍵上花費(fèi)CPU時(shí)間.
? 但是這種策略對內(nèi)存是十分不友好的,這個(gè)鍵若是不再被使用就會一直待在內(nèi)存中.甚至可以看做是一種內(nèi)存泄露.對于運(yùn)行狀態(tài)十分依賴內(nèi)存的Redis服務(wù)器來說,并不是一個(gè)好消息.
3). 定期刪除
? 定期刪除策略是前兩種策略的一個(gè)折中.既能考慮到內(nèi)存,又對CPU一定程度上的友好.
? 難點(diǎn)在于確定刪除操作執(zhí)行的時(shí)長和頻率,如果太過頻繁,或者執(zhí)行時(shí)間太長,會退化成定時(shí)刪除策略.如果刪除操作執(zhí)行的太少,或者執(zhí)行的時(shí)間太短,就會出現(xiàn)和惰性刪除一樣的問題,浪費(fèi)內(nèi)存.
(6). Redis的過期鍵刪除策略
? Redis實(shí)際上使用的是惰性刪除和定期刪除兩種策略.兩種策略配合使用,取得平衡.
1). 惰性刪除策略的實(shí)現(xiàn)
? 所有讀寫數(shù)據(jù)庫的Redis命令在執(zhí)行前都會調(diào)用expireIfNeeded函數(shù)對輸入鍵進(jìn)行檢查,
- 如果輸入鍵過期,那么將輸入鍵刪除
- 如果輸入鍵未過期,那么不做動作
? expireIfNeeded函數(shù)像一個(gè)過濾器,能過過濾掉所有過期的鍵.相應(yīng)的所有的命令也都必須能夠同時(shí)處理鍵存在和鍵不存在兩種情況.
2). 定期刪除則略的實(shí)現(xiàn)
? 過期鍵的定期刪除策略由activeExpireCycle函數(shù)實(shí)現(xiàn).
? 每當(dāng)Redis的服務(wù)器周期性操作serverCron函數(shù)執(zhí)行時(shí),activeExpireCycle函數(shù)就會被調(diào)用,它在規(guī)定時(shí)間內(nèi),分多次掃描服務(wù)器中的各個(gè)數(shù)據(jù)庫,從數(shù)據(jù)庫的expires字典(過期字典)中隨機(jī)檢查一部分鍵的過期時(shí)間,刪除掉其中過期的.
工作模式:
- 每次函數(shù)被調(diào)用時(shí),都從一部分?jǐn)?shù)據(jù)庫的過期字典中再找出一部分鍵進(jìn)行判斷過期,并進(jìn)行刪除操作
- 全局變量current_db記錄當(dāng)前函數(shù)檢查的進(jìn)度,下一次被調(diào)用時(shí),接著這個(gè)進(jìn)度進(jìn)行檢查
- 隨著這個(gè)函數(shù)的多次調(diào)用,服務(wù)器中的所有數(shù)據(jù)庫都會被檢查一遍,然后記錄清零,從頭開始
(7). AOF,RDB和復(fù)制功能對過期鍵的處理
? AOF和RDB是Redis的兩種持久化方案
1). 生成RDB文件
? SAVE或者BGSAVE命令會創(chuàng)建一個(gè)新的RDB文件,程序會拋棄數(shù)據(jù)庫中過期的鍵,過期的鍵不會被保存到新的RDB文件中.
2). 載入RDB文件
? 啟動Redis服務(wù)器時(shí),如果開啟了RDB功能,那么服務(wù)器將對RDB文件進(jìn)行載入,初始化服務(wù)器:
- 如果服務(wù)器是==主服務(wù)器==,程序會對文件中保存的鍵再進(jìn)行一次檢查,過期鍵會被忽略
- 如果服務(wù)器是==從服務(wù)器==,不論鍵是否過期,都會被載入到數(shù)據(jù)庫中.不過主服務(wù)器在進(jìn)行數(shù)據(jù)同步時(shí),從服務(wù)器會被清空,所以也不影響.
3). AOF文件寫入
? 當(dāng)服務(wù)器以AOF持久化運(yùn)行是,如果某個(gè)鍵已經(jīng)過期但是還沒被刪除,AOF文件不會有任何處理.但==當(dāng)這個(gè)鍵被刪除時(shí),程序會在AOF文件追加一條DEL命令顯式的記錄這個(gè)鍵被刪除了==.
4). AOF重寫
? 在執(zhí)行AOF重寫過程中,程序會對鍵進(jìn)行檢查,所有過期的鍵(而不是被刪除)不會被保存到新的AOF文件中.
5). 復(fù)制
? 當(dāng)服務(wù)器子在復(fù)制模式下運(yùn)行時(shí),服務(wù)器的過期鍵刪除動作由主服務(wù)器控制:
- 主服務(wù)器刪除一個(gè)鍵后,會向所有從服務(wù)器發(fā)送一個(gè)DEL命令,通知從服務(wù)器刪除這個(gè)鍵保持同步.
- 從服務(wù)器不會考慮鍵的過期情況,只有主服務(wù)器發(fā)來DEL命令時(shí),才進(jìn)行相應(yīng)的操作
? 當(dāng)一個(gè)過期鍵在主服務(wù)器中存在時(shí),也應(yīng)該同時(shí)存在于從服務(wù)器.
(8). 數(shù)據(jù)庫通知
? 數(shù)據(jù)庫通知功能可以讓客戶端通過訂閱某個(gè)給定的頻道,來獲取數(shù)據(jù)庫中鍵的變化,以及數(shù)據(jù)庫中命令的執(zhí)行情況.(大致意思就是,客戶端可以監(jiān)聽服務(wù)器對某個(gè)鍵的所有操作和變化,服務(wù)器發(fā)回一些列消息說明這個(gè)鍵執(zhí)行了什么命令,也就是通知).
? "這個(gè)鍵執(zhí)行了什么命令"這種通知被稱為鍵空間通知,除此之外,還有鍵空間時(shí)間通知,關(guān)注的是"某個(gè)命令被什么鍵執(zhí)行了"
1). 發(fā)送通知
? 發(fā)送通知的功能由以下函數(shù)實(shí)現(xiàn):
/**
* type參數(shù)是當(dāng)前要發(fā)送通知的類型,服務(wù)器根據(jù)這個(gè)參數(shù)確是否發(fā)送這條通知
* event是事件名稱
* key是產(chǎn)生事件的鍵
* dbid是產(chǎn)生事件的數(shù)據(jù)庫號碼
* 下面的三個(gè)參數(shù)構(gòu)建了通知的內(nèi)容
*/
void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid);
? 當(dāng)一個(gè)Redis命令需要發(fā)送數(shù)據(jù)庫通知的時(shí)候,就會調(diào)用這個(gè)函數(shù),并通過參數(shù)傳遞相應(yīng)的信息.
2). 發(fā)送通知的實(shí)現(xiàn)
- 如果給定的通知是服務(wù)器不允許發(fā)送的,那么直接返回
- 檢測服務(wù)器是否允許發(fā)送==鍵空間==通知,如果允許,程序會構(gòu)件并發(fā)送時(shí)間通知
- 函數(shù)檢測服務(wù)器是否云溪發(fā)送==鍵事件==通知,如果允許,構(gòu)件并發(fā)送
2. RDB持久化
? 全稱Redis Database,實(shí)際指磁盤上的Redis數(shù)據(jù)庫.
? 我們將服務(wù)器中的非空數(shù)據(jù)庫以及它們的鍵值對統(tǒng)稱為數(shù)據(jù)庫狀態(tài).
? 因?yàn)镽edis是內(nèi)存數(shù)據(jù)庫,如果不想辦法將存儲在內(nèi)存中的數(shù)據(jù)保存到磁盤中,那么一旦服務(wù)器進(jìn)程退出,數(shù)據(jù)就消失了.為了解決這個(gè)問題,Redis提供了RDB持久化功能,這個(gè)功能可以==將Redis在內(nèi)存中的數(shù)據(jù)庫狀態(tài)保存到磁盤中==,避免數(shù)據(jù)意外丟失.
? RDB持久化功能生成的RDB文件是一個(gè)經(jīng)過壓縮的二進(jìn)制文件.
(1). RDB文件的創(chuàng)建與載入
? 有兩個(gè)命令可以生成RDB文件,分別是SAVE命令和BGSAVE命令.
? SAVE命令會阻塞Redis服務(wù)器進(jìn)程,直到RDB文件創(chuàng)建完成為止.,期間服務(wù)器不能處理任何請求.
? BGSAVE命令會派生出一個(gè)子進(jìn)程,由子進(jìn)程負(fù)責(zé)創(chuàng)建RDB文件,服務(wù)器進(jìn)程繼續(xù)處理命令請求.
? 這兩個(gè)命令都是通過調(diào)用rdbSave函數(shù)完成生成RDB文件的功能的.
偽代碼如下:
def SAVE():
# 生成RDB文件
rdbSave()
def BGSAVE():
# 創(chuàng)建子進(jìn)程
# 這個(gè)方法會將當(dāng)前執(zhí)行的方法復(fù)制一份
# 在子進(jìn)程中返回的pid為0,父進(jìn)程中為正數(shù)
# 返回負(fù)數(shù)則說明創(chuàng)建失敗
pid = fork()
if pid == 0:
# 子進(jìn)程中生成RDB文件
rdbSave()
# 完成之后向父進(jìn)程發(fā)送信號
signal_parent()
elif pid > 0:
# 父進(jìn)程中繼續(xù)處理命令請求,并輪詢等待子進(jìn)程的完成信號
handle_request_and_wait_signal()
else:
# 如果創(chuàng)建失敗,進(jìn)行相應(yīng)的處理
handle_fork_error()
? 和生成RDB文件不同,Redis只在服務(wù)器啟動時(shí)進(jìn)行,所以并沒有專門用于載入RDB文件的命令,只要Redis檢測到RDB文件,那么在啟動服務(wù)器時(shí)就會進(jìn)行載入.
? 因?yàn)锳OF文件的更新頻率通常都會比RDB文件高,所以服務(wù)器優(yōu)先使用AOF來進(jìn)行持久化,也就是說如果服務(wù)器開啟了AOF功能,那么服務(wù)器有限使用AOF文件進(jìn)行數(shù)據(jù)的恢復(fù),只有當(dāng)沒有開啟AOF功能時(shí),才會使用RDB文件恢復(fù)數(shù)據(jù).
1). SAVE命令執(zhí)行是的服務(wù)器狀態(tài)
? 阻塞,可以看做的串行化的.
2). BGSAVE命令執(zhí)行是的服務(wù)器狀態(tài)
? 服器可以在執(zhí)行BGSAVE的通知接受請求并執(zhí)行命令,但是在這期間處理SAVE,BGSAVE,BGREWRITEAOF這些命令會和平時(shí)不同.
- SAVE和BGSAVE命令會被拒絕
- BGREWRITEAOF命令會被延遲到BGSAVE命令執(zhí)行完再進(jìn)行(如果BGREWRITEAOF正在執(zhí)行,BGSAVE會被拒絕),這是處于性能上的考慮
3). RDB文件載入是的服務(wù)器狀態(tài)
? 載入期間,會阻塞服務(wù)器進(jìn)程,直到載入完成.
(2). 自動間隔性保存
? 因?yàn)锽GSAVE命令可以在子線程中執(zhí)行,不阻塞服務(wù)器,所以Redis允許用戶設(shè)置save配置,讓服務(wù)器每隔一段時(shí)間自動執(zhí)行一次BGSAVE命令.
? 用戶可以設(shè)置多個(gè)保存條件,當(dāng)其中之一滿足時(shí),就會執(zhí)行BGSAVE命令.
? 樣例如下:
// 服務(wù)器在900秒之內(nèi)對數(shù)據(jù)庫至少進(jìn)行了1次修改
save 900 1
save 300 10
save 60 10000
1). 設(shè)置保存條件
? 上面顯示的就是默認(rèn)的自動間隔性保存配置.
? 啟動服務(wù)器時(shí),Redis會根據(jù)這個(gè)配置,設(shè)置服務(wù)器狀態(tài)redisServer結(jié)構(gòu)的saveparams屬性;
struct redisServer{
// .....
// 記錄了保存條件的數(shù)組
struct saveparam *saveparams;
// .....
};
? saveparams是一個(gè)數(shù)組,每一個(gè)元素都是saveparam結(jié)構(gòu),其內(nèi)部保存了一個(gè)saveparams選項(xiàng)設(shè)置的保存條件:
struct saveparam{
// 秒數(shù)
time_t seconds;
// 修改數(shù)
int changes;
};
2). dirty計(jì)數(shù)器和lastsave屬性
? 除了saveparams數(shù)組之外,服務(wù)器還維護(hù)者一個(gè)dirty計(jì)數(shù)器和lastsave屬性:
- dirty計(jì)數(shù)器記錄上次(BG)SAVE之后服務(wù)器對數(shù)據(jù)庫狀態(tài)的修改次數(shù)
- lastsave屬性記錄了上次(BG)SAVE執(zhí)行的UNIX時(shí)間戳
struct redisServer{
// .....
// 修改計(jì)數(shù)器
long long dirty;
// 上次執(zhí)行保存的時(shí)間
time_t lastsave;
// .....
};
3). 檢查保存條件是否滿足
? Redis的服務(wù)器周期性操作函數(shù)serverCron默認(rèn)每100毫秒就執(zhí)行一次,它的其中一項(xiàng)工作就是檢查save選項(xiàng)所保存的條件是否被滿足.
? 和saveparams數(shù)組中的條件進(jìn)行比較.計(jì)算出距上次save的時(shí)間,如果dirty數(shù)大于cahnges,==并且==時(shí)間大于所設(shè)置的時(shí)間,就進(jìn)行BGSAVE操作.
(3). RDB文件結(jié)構(gòu)
? 一個(gè)完整的RDB文件所包含的各個(gè)部分:
-
REDIS:
- RDB文件的最開頭部分摩擦和你共度為5字節(jié),就記錄了REDIS五個(gè)字符,相當(dāng)于Java中class文件中的魔數(shù),用于文件類型的確認(rèn).
-
db_version:
- 長度為4字節(jié),它記錄了一個(gè)字符串表示的整數(shù),用于表示RDB文件的版本號,從而確定解析方式的不同.相當(dāng)于Java中的class文件的版本號.
-
databases:
- 包含0個(gè)或者多個(gè)數(shù)據(jù)庫,以及各個(gè)數(shù)據(jù)庫中的鍵值對數(shù)據(jù)
-
EOF:
- 長度為1字節(jié),用來標(biāo)記databases部分的結(jié)束
-
check_sum:
- 是一個(gè)8字節(jié)長的無符號整數(shù),是Redis程序根據(jù)前面四個(gè)部分的內(nèi)容進(jìn)行計(jì)算得出的一個(gè)校驗(yàn)和.通過對比這個(gè)數(shù)字和當(dāng)前程序計(jì)算得出的結(jié)構(gòu),可以得知這個(gè)RDB文件是否出錯(cuò)或者被損壞.
1). databases部分
? 一個(gè)RDB文件的databases部分可以保存任意多個(gè)數(shù)據(jù)庫,如果其中一部分?jǐn)?shù)據(jù)庫為空,那么會跳過這個(gè)庫,直接保存下一個(gè)庫的內(nèi)容.
? 每一個(gè)非空數(shù)據(jù)庫在RDB文件中都可以保存為==SELECTDB, db_number, key_value_pairs==三部分
SELECTDB | db_number | key_value_pairs |
---|---|---|
常量,長度為1字節(jié),讓讀入程序知道接下來讀入的將是一個(gè)數(shù)據(jù)庫號碼. | 保存所記錄的數(shù)據(jù)庫的號碼.根據(jù)號碼的不同,長度可以是1字節(jié),2字節(jié)或者5字節(jié).讀入這個(gè)值之后,服務(wù)器也會進(jìn)行相應(yīng)的切換庫操作,保證后面的數(shù)據(jù)進(jìn)入正確的庫. | 這部分保存了數(shù)據(jù)庫中的所有鍵值對數(shù)據(jù),包括過期時(shí)間 |
2). key_value_pairs部分
? 每一個(gè)key_value_pairs部分都保存了一個(gè)或以上數(shù)量的鍵值對.
? ==帶過期時(shí)間的==key_value_pairs前面才會有這兩個(gè)屬性,不帶過期時(shí)間的之后后三個(gè)屬性
EXPIRETIME_MS | ms | TYPE | key | value |
---|---|---|---|---|
常量,1字節(jié),告訴程序接下來讀取一個(gè)毫秒為單位的過期時(shí)間 | 8字節(jié)長的帶符號整數(shù),記錄了一個(gè)UNIX時(shí)間戳 | 是眾多常量中的一個(gè),表示這個(gè)鍵值對的對象類型或者底層編碼,服務(wù)器根據(jù)這個(gè)字段決定如何讀入和解釋value的數(shù)據(jù) | 一個(gè)字符串對象,表示鍵 | 根據(jù)不同的類型或者編碼,有不同的格式和長度 |
3). value的編碼
? value屬性中每種編碼的結(jié)構(gòu)都不同
1>. string對象
? 如果是整數(shù),那么直接存儲
len | string |
---|---|
字符串長度 | 字符串內(nèi)容 |
? 如果是raw編碼,說明是一個(gè)字符串,長度大于20字節(jié)會被壓縮.
? 不被壓縮的會保存長度和內(nèi)容
? 被壓縮之后會保存標(biāo)記,壓縮后長度,壓縮前長度和壓縮后的內(nèi)容
REDIS_RDB_ENC_LZF | compressed_len | origin_len | compressed+string |
---|---|---|---|
標(biāo)記為,表示是否經(jīng)過LZF算法壓縮 | 壓縮后長度 | 壓縮前長度 | 壓縮后內(nèi)容 |
2>. list對象
雙端鏈表編碼:
list_length | item 1 | item 2 | ... | item n |
---|---|---|---|---|
列表長度 | 列表項(xiàng),是字符串對象 |
3>. set對象
字典編碼:
set_size | elem 1 | elem 2 | ... | elem n |
---|---|---|---|---|
集合中元素?cái)?shù) | 元素,是字符串對象 |
4>. hash對象
字典編碼:
hash_size | key_value_pair 1 | key_value_pair 2 | ... | key_value_pair n |
---|---|---|---|---|
哈希表的鍵值對數(shù)量 | 一個(gè)鍵值對 |
? key_value_pair的結(jié)構(gòu):
key 1 | value 1 | key 2 | value 2 | ... | key n | value n |
---|---|---|---|---|---|---|
第一個(gè)key,是string對象 | 第一個(gè)value,是字符串對象 |
5>. zset對象
跳表編碼:
sorted+set_size | element 1 | element 2 | ... | element n |
---|---|---|---|---|
有序集和的元素?cái)?shù)量 | 一個(gè)鍵值對 |
? element的結(jié)構(gòu):
member 1 | score 1 | member 2 | score 2 | ... | member n | score n |
---|---|---|---|---|---|---|
第一個(gè)成員,是string對象 | 第一個(gè)分值,是double類型的浮點(diǎn)數(shù) |
6>. 其他編碼實(shí)現(xiàn)
? 其他編碼方式(整數(shù)編碼的集合,壓縮列表編碼的列表,哈希表或者有序集合),都是將數(shù)據(jù)部分轉(zhuǎn)化為字符串進(jìn)行存儲,因?yàn)檫@些數(shù)據(jù)底層都是字節(jié)數(shù)組.
3. AOF持久化
? 全稱是Append Only File,也就是可追加的一個(gè)文件.
? 通過保存Redis服務(wù)器所執(zhí)行的==寫命令==來記錄數(shù)據(jù)庫狀態(tài).
? 被寫入AOF文件的所有命令都是以==Redis的命令請求協(xié)議REST==格式保存的.
(1). AOF持久化的體現(xiàn)
? AOF持久化功能的實(shí)現(xiàn)可以分為命令追加,文件寫入和文件同步三個(gè)步驟.
1). 命令追加
? 當(dāng)開啟AOF功能的Redis服務(wù)器執(zhí)行完一個(gè)寫命令之后,會以REST的格式將這個(gè)命令寫入服務(wù)器狀態(tài)的aof_buf緩沖區(qū).
struct redisServer{
// .....
// AOF緩沖區(qū)
sds aof_buf;
// .....
};
2). AOF文件的寫入和同步
? 這里寫入是指將緩沖中的數(shù)據(jù)寫入到內(nèi)存中的AOF文件中,同步則相當(dāng)于保存到磁盤中.
? Redis的服務(wù)器進(jìn)程是一個(gè)事件循環(huán),其中的文件事件負(fù)責(zé)接受命令請求,而時(shí)間時(shí)間則執(zhí)行serverCron這樣的定時(shí)執(zhí)行的函數(shù).
? 因?yàn)槊恳粋€(gè)文件事件都有可能執(zhí)行寫命令,所以服務(wù)器每次結(jié)束一個(gè)時(shí)間循環(huán)之前,都會調(diào)用flushAppendOnlyFile()函數(shù)考慮是否將緩沖中的數(shù)據(jù)追加到AOF文件中.
? 事件循環(huán)如下偽代碼:
def eventLoop():
while True:
# 進(jìn)行文件事件
processFileEvents()
# 時(shí)間事件
processTimeEvents()
# 判斷是否進(jìn)行寫入和同步
flushAppendOnlyFile()
? flushAppendOnlyFile()函數(shù)的行為有服務(wù)器配置的appendfsync選項(xiàng)的值決定.
appendfsync的值 | flushAppendOnlyFile函數(shù)的行為 |
---|---|
always(每命令) | 將緩沖區(qū)中的內(nèi)容寫入并同步到AOF文件中 |
everysec(每秒,==默認(rèn)配置==) | 先進(jìn)行寫入,如果距離上次同步超過一秒,那么接著在一個(gè)專門的線程中進(jìn)行同步操作 |
no(從不) | 進(jìn)行寫入操作,不進(jìn)行同步,由操作系統(tǒng)來決定同步的時(shí)機(jī) |
- always配置是三個(gè)配置中最慢的,但是最安全,因?yàn)樽疃鄷G失一個(gè)命令數(shù)據(jù)
- everysec最多丟失一秒的數(shù)據(jù)
- no配置會丟失上次操作系統(tǒng)自動同步到宕機(jī)為止所有的數(shù)據(jù),并且單次同步時(shí)間最長(數(shù)據(jù)量大),但是效率最高
(2). AOF文件的載入與數(shù)據(jù)還原
? Redis服務(wù)器在啟動時(shí)如果開啟了AOF功能,并且檢測到AOF文件,那么就會進(jìn)行AOF文件的載入和數(shù)據(jù)還原.
詳細(xì)步驟如下:
- 創(chuàng)建一個(gè)==不帶網(wǎng)絡(luò)連接的偽客戶端==(其實(shí)是在服務(wù)端內(nèi)部的,所以叫偽客戶端,用于處理命令,其效果和普通的Redis客戶端使用網(wǎng)絡(luò)傳遞命令是一樣的)
- 從AOF文件中分析并讀取出一條命令
- 使用偽客戶端執(zhí)行這條命令
- 重復(fù)2和3,直到AOF文件被全部讀取完畢
(3). AOF重寫
? 因?yàn)锳OF文件是追加寫入的,所以隨著服務(wù)器運(yùn)行時(shí)間的流逝,這個(gè)文件會越來越大,不加以控制會造成越來越多的影響.
? 為了解決AOF文件體積膨脹的問題,Redis提供了AOF文件重寫功能.Redis將通過這個(gè)功能創(chuàng)建出一個(gè)新的AOF文件代替舊的,膨脹了的AOF文件.新舊==兩個(gè)AOF文件功能上完全一致,但是新的AOF文件不會包含任何的冗余命令==,所以體積會小很多.
1). AOF文件重寫的實(shí)現(xiàn)
? 這個(gè)功能通過去讀現(xiàn)有的數(shù)據(jù)庫狀態(tài)來實(shí)現(xiàn).==Redis會將每一個(gè)鍵值對用一條寫命令代替==(例如:一個(gè)命令插入一整個(gè)list,而不是一個(gè)列表項(xiàng)一個(gè)列表項(xiàng)插入).
2). AOF后臺重寫
? 上面介紹的AOF文件重寫功能雖然很好,但是這個(gè)函數(shù)會進(jìn)行大量的寫入操作,所以調(diào)用這個(gè)函數(shù)的線程將被長時(shí)間阻塞.由于Redis使用單線程來處理請求,所以就可能導(dǎo)致Redis服務(wù)器的停滯.所以==Redis決定將AOF重寫程序放在子進(jìn)程中進(jìn)行,從而不影響服務(wù)器進(jìn)程處理命令,另一方面使用子進(jìn)程可以在不使用鎖的情況下保證數(shù)據(jù)的安全==.
? 在AOF重寫進(jìn)行中,服務(wù)器進(jìn)程還要繼續(xù)處理新的命令請求,就會改變數(shù)據(jù)庫狀態(tài),就可能在AOF重寫完成后,和現(xiàn)有的數(shù)據(jù)庫狀態(tài)不一致.為了解決這個(gè)問題,==Redis設(shè)置了AOF重寫緩沖區(qū),在AOF重寫的過程中所有的AOF追加部分將同時(shí)被寫入AOF緩沖和AOF重寫緩沖==.這樣就保證了現(xiàn)有的AOF文件是正常更新的,并且從創(chuàng)建子進(jìn)程開始,所有的命令都會被加以記錄.
? 當(dāng)AOF重寫完成后,子進(jìn)程會向父進(jìn)程發(fā)送一個(gè)信號.父進(jìn)程接收到之后(這里因?yàn)閳?zhí)行了信號處理函數(shù),是會==阻塞==父進(jìn)程的)會==將AOF重寫緩沖中的內(nèi)容寫入新的AOF文件==中,追加完成之后,這個(gè)新的AOF文件就和當(dāng)前的數(shù)據(jù)庫狀態(tài)一致了.然后==使用新的AOF文件原子性覆蓋舊的文件==.然后就完成了整個(gè)AOF重寫的全過程
4. 事件
? Redis服務(wù)器是一個(gè)事件驅(qū)動程序,服務(wù)器需要處理以下兩類事件:
- 文件事件:Redis服務(wù)器通過套接字(socket)與客戶端進(jìn)行連接,文件事件就是服務(wù)器對套接字操作的抽象.服務(wù)器和客戶端或者其他服務(wù)器的通信就會產(chǎn)生文件事件.服務(wù)器通過監(jiān)聽并處理這些文件事件來完成一些列網(wǎng)絡(luò)通信操作.
- 時(shí)間事件:服務(wù)器對定時(shí)操作的抽象.
(1). 文件事件
? Redis基于Reactor模式開發(fā)了自己的網(wǎng)絡(luò)時(shí)間處理器:被稱為==文件事件處理器==:
- 使用I/O多路復(fù)用程序來同時(shí)監(jiān)聽多個(gè)套接字,并根據(jù)每個(gè)套接字執(zhí)行的任務(wù)來關(guān)聯(lián)不同的事件處理器.
- 被監(jiān)聽的套接字準(zhǔn)備好執(zhí)行一系列操作時(shí),與操作隊(duì)形的文件事件就會產(chǎn)生,這是文件事件處理器就會調(diào)用與套接字相關(guān)聯(lián)的事件處理器去處理這些事件.
1). 文件事件處理器的構(gòu)成
? 文件事件處理器由四個(gè)部分組成,分別是:套接字,I/O多路復(fù)用程序,文件事件分派器,多個(gè)事件處理器.
[圖片上傳失敗...(image-d2434c-1590395205133)]
? I/O多路復(fù)用程序負(fù)責(zé)監(jiān)聽多個(gè)套接字,并向文件事件分派器傳送那些產(chǎn)生了事件的套接字.==它總會把所有產(chǎn)生事件的套接字都放到一個(gè)隊(duì)列里面,然后通過這個(gè)隊(duì)列,以有序,同步,每次一個(gè)套接字的方式向文件事件分派器傳送套接字==.當(dāng)一個(gè)套接字產(chǎn)生的事件被處理完之后,I/O多路復(fù)用程序才會繼續(xù)床送下一個(gè)套接字.
? 文件事件分派器接收I/O多路復(fù)用程序傳來的套接字,根據(jù)套接字產(chǎn)生的事件類型,調(diào)用相應(yīng)的事件處理器.這些處理器就是一個(gè)個(gè)不同的函數(shù),它們定義了事件發(fā)生時(shí),服務(wù)器應(yīng)該進(jìn)行的動作.
2). I/O多路復(fù)用程序的實(shí)現(xiàn)
? 程序會在編譯時(shí)通過宏定義自動選擇系統(tǒng)中性能最高的I/O多路復(fù)用函數(shù)庫來作為Redis的I/O多路復(fù)用程序的底層實(shí)現(xiàn).
3). 事件類型
- 當(dāng)套接字可讀時(shí),產(chǎn)生AE_READABLE事件
- 當(dāng)套接字可寫時(shí),產(chǎn)生AE_WRITABLE事件
如果一個(gè)套架子同事產(chǎn)生這兩種事件,也就是說可讀又可寫,服務(wù)器優(yōu)先讀套接字,然后才寫.
4). 文件事件的處理器
? Redis為文件事件編寫了多個(gè)處理器,分別用于實(shí)現(xiàn)不用的網(wǎng)絡(luò)通信需求:
1>. 連接應(yīng)答處理器
? 用于對 連接服務(wù)器監(jiān)聽套接字的客戶端 進(jìn)行應(yīng)答.也就是用于處理客戶端連接服務(wù)端的請求
2>. 命令請求處理器
? 負(fù)責(zé)從套接字中讀入客戶端發(fā)送的命令請求內(nèi)容.
3>. 命令回復(fù)處理器
? 負(fù)責(zé)將服務(wù)器執(zhí)行命令后得到的命令通過套接字回復(fù)給客戶端.
4>. 一次完整的客戶端與服務(wù)端連接時(shí)間的實(shí)例
[圖片上傳失敗...(image-ef7412-1590395205133)]
(2). 時(shí)間事件
Redis的時(shí)間事件分為以下兩類:
- 定時(shí)時(shí)間:讓一段程序在==指定的時(shí)間之后==執(zhí)行一次
- 周期性時(shí)間:讓一段程序==每隔一段時(shí)間==就執(zhí)行一次
一個(gè)時(shí)間事件由一下三個(gè)屬性組成:
- id:服務(wù)器會為時(shí)間事件創(chuàng)建全局唯一的ID,新的事件的ID比舊的事件的ID大
- when:毫秒精度的一個(gè)UNIX時(shí)間戳,記錄了時(shí)間事件應(yīng)該發(fā)生的時(shí)間戳
- timeProc:時(shí)間事件處理器,一個(gè)函數(shù).當(dāng)時(shí)間事件到達(dá)時(shí),服務(wù)器會調(diào)用相應(yīng)的處理器來處理時(shí)間
==一個(gè)時(shí)間事件是不是周期性的取決于時(shí)間事件處理器的返回值==:
- 返回AE_NOMORE,則表明以后不再重復(fù)這個(gè)時(shí)間事件
- 否則會根據(jù)時(shí)間事件處理器返回的值對when屬性進(jìn)行更改,從而得到下一次執(zhí)行的時(shí)間
1). 實(shí)現(xiàn)
? 服務(wù)器將所有時(shí)間事件都放在一個(gè)==無序鏈表==(這里的無序是指不按when屬性排序)中.==每當(dāng)時(shí)間事件執(zhí)行器運(yùn)行時(shí),它就遍歷這個(gè)鏈表,調(diào)用其中所有到達(dá)時(shí)間的時(shí)間事件的處理器.==
? 一般情況下,服務(wù)器只會使用一個(gè)時(shí)間事件(serverCron),benchmark模式下也只有兩個(gè),所以不影響性能.
(3). 事件的調(diào)度與執(zhí)行
? 服務(wù)器中同事存在著文件事件和時(shí)間事件,所以服務(wù)器西部隊(duì)這兩種事件進(jìn)行調(diào)度,決定優(yōu)先級和分配的時(shí)間等等.
? 大致的調(diào)度方式如下面的偽代碼:
def main():
# 初始化服務(wù)器
init_server()
# 在服務(wù)器未被關(guān)閉的情況下一直循環(huán)
while server_is_not_shutdown():
# 處理事件
# 其中要先等待文件事件產(chǎn)生,然后在進(jìn)行處理
# 然后在輪詢時(shí)間事件是否到達(dá),到達(dá)進(jìn)行處理
aeProcessEvents()
clean_server()
? 因?yàn)闀r(shí)間事件的處理是在文件事件處理之后進(jìn)行的,所以時(shí)間事件的實(shí)際處理時(shí)間通常會比設(shè)定的時(shí)間晚一些到達(dá).
5. 客戶端
? Redis服務(wù)器是定性的一對多服務(wù)器程序:一個(gè)服務(wù)器可以與多個(gè)客戶端建立網(wǎng)絡(luò)連接,每個(gè)客戶端可以向服務(wù)器發(fā)送命令請求,二服務(wù)器接受并處理客戶端發(fā)送的命令請求,并向各個(gè)客戶端返回命令回復(fù).
? 對于每個(gè)與服務(wù)器進(jìn)行連接的客戶端,服務(wù)器都為這些客戶端建立了相應(yīng)的redisClient結(jié)構(gòu),其中的內(nèi)容包括:客戶端的套接字描述符, 名字, 標(biāo)志值, 正在使用的數(shù)據(jù)庫指針和號碼, 當(dāng)期那要執(zhí)行的命令,參數(shù)以及需要調(diào)用的函數(shù)指針, 輸入緩沖區(qū)和輸出緩沖區(qū), 復(fù)制狀態(tài)信息, 事務(wù)狀態(tài), 發(fā)布與訂閱相關(guān)數(shù)據(jù)結(jié)構(gòu), 身份驗(yàn)證標(biāo)志, 創(chuàng)建時(shí)間, 最后一次通信時(shí)間等等....
? Redis服務(wù)器狀態(tài)中有一個(gè)鏈表存儲了鏈接到這臺服務(wù)器的所有客戶端的信息
struct redisServer{
// .....
// 一個(gè)鏈表,存儲了所連接的所有客戶端
list *clients;
// .....
};
(1). 客戶端屬性
? 客戶端屬性一般分為兩類:
- 一類是比較通用的屬性,這種屬性很少與特定的功能相關(guān),比如名字,創(chuàng)建時(shí)間等等
- 另外一類是和特定功能相關(guān)的屬性,如db屬性和操作數(shù)據(jù)庫相關(guān)
typedef struct redisClient{
// .....
// 套接字描述符
// 若為-1,則是偽客戶端,處理的命令請求來自于AOF文件或者Lua腳本,不需要套接字連接
// 若為大于-1的整數(shù),則是普通客戶端,客戶端通過這個(gè)整數(shù)代表的套接字與服務(wù)端進(jìn)行通信
int fd;
// 客戶端名字
// 默認(rèn)情況下沒有名字,為NULL
// 如果客戶端通過CLIENT setname命令可以為當(dāng)前正在使用的客戶端設(shè)置名字,這時(shí)會指向一個(gè)string對象
robj *name;
// 標(biāo)志
// 記錄了客戶端的角色以及目前所處的狀態(tài)
// 標(biāo)記可以使單個(gè)標(biāo)記也可以是多個(gè)標(biāo)記的二進(jìn)制或,即不同的位負(fù)責(zé)不同的狀態(tài)
// 可表示的信息有:主從復(fù)制中連接的是主或者從服務(wù)器,是否低于2.8版本,是否是處理Luau腳本的偽客戶端等等
int falgs;
// 輸入緩沖區(qū)
// 用于保存客戶端發(fā)送的命令請求
// 格式就是REST協(xié)議定義的,Redis中所有的網(wǎng)絡(luò)傳輸內(nèi)容都是REST格式的
// 這個(gè)緩沖區(qū)的大小會根據(jù)輸入內(nèi)容動態(tài)的縮小或者擴(kuò)大,但不超過1GB
sds querybuf;
// 命令和命令參數(shù)
// argc是argv數(shù)組的長度
// argv數(shù)組是string對象數(shù)組,每一個(gè)桶存儲了命令中的一個(gè)單詞
// 類似: | set | name | Benjamin |
int argc;
robj **argv;
// 命令的實(shí)現(xiàn)函數(shù)
// 這個(gè)指針是從命令表中找到的,命令表示一個(gè)字典,key為命令的名稱,value為命令所對應(yīng)的函數(shù)的封裝結(jié)構(gòu)體
// redisCommand結(jié)構(gòu)體保存了命令的實(shí)現(xiàn)函數(shù),標(biāo)志,參數(shù)個(gè)數(shù),總執(zhí)行次數(shù)和總消耗時(shí)長等統(tǒng)計(jì)信息
// cmd指針會在找到這個(gè)結(jié)構(gòu)體之后指向這里
// 然后就可以通過cmd指向的結(jié)構(gòu)體和argv參數(shù)列表執(zhí)行命令
struct redisCommand *cmd;
// 輸出緩沖區(qū)
// 有三個(gè)輸出緩沖
// 一個(gè)用于保存ok,數(shù)值等簡短的字符串值
// 另一個(gè)用于返回信息,大小默認(rèn)為16K
// 當(dāng)16K不夠用時(shí),使用雙端鏈表實(shí)現(xiàn)的可變大小緩沖
int bufpos;
char buf[16*1024];
list *reply;
// 身份驗(yàn)證
// 用于記錄客戶端是否通過了身份驗(yàn)證
// 0為未通過,1為通過
// 未通過驗(yàn)證的客戶端,除了AUTH命令(身份驗(yàn)證命令)之外的所有命令都會被服務(wù)器拒絕執(zhí)行
// 這個(gè)屬性僅僅在服務(wù)器開啟了身份驗(yàn)證功能時(shí)使用,如果沒有啟用,默認(rèn)為0,服務(wù)端不拒絕命令
int authenticated;
// 時(shí)間
// 創(chuàng)建客戶端時(shí)間
time_t ctime;
// 最后一次互動時(shí)間,可以用來計(jì)算空轉(zhuǎn)時(shí)間
time_t lastinteration;
// 輸出緩沖區(qū)第一次達(dá)到軟性限制的時(shí)間
time_t obuf_soft_limit_reached_time;
// .....
};
(2). 客戶端的創(chuàng)建與關(guān)閉
? 服務(wù)器通過不同的方式創(chuàng)建和關(guān)閉不同類型的客戶端
1). 創(chuàng)建普通客戶端
? 如果客戶端是==通過網(wǎng)絡(luò)連接與服務(wù)器進(jìn)行連接==的普通客戶端,那么客戶端使用connect函數(shù)連接到服務(wù)器時(shí),服務(wù)器調(diào)用連接事件處理器,為客戶端創(chuàng)建相應(yīng)的客戶端狀態(tài),并添加至clients鏈表的末尾.
2). 關(guān)閉普通客戶端
? 一個(gè)普通客戶端可以因?yàn)槎喾N原因被關(guān)閉:
- 客戶端進(jìn)程退出或者被殺死,網(wǎng)絡(luò)連接關(guān)閉,從而客戶端關(guān)閉
- 發(fā)送了不符合REST協(xié)議的請求
- 成為了CLIENT KILL命令的目標(biāo)
- 空轉(zhuǎn)時(shí)間超出設(shè)定值
- 命令請求大小超過輸入緩沖區(qū)大小1 GB
- 發(fā)給客戶端的回復(fù)命令請求超過了輸出緩沖區(qū)的限制大小(硬件限制的大小和軟性限制大小,有屬性會記錄第一次達(dá)到軟性限制大小的時(shí)間,如果一致超過軟性限制并且超出了服務(wù)器這頂?shù)臅r(shí)長,就會關(guān)閉)
3). Lua腳本的偽客戶端
? 服務(wù)器會在初始化時(shí)創(chuàng)建 負(fù)責(zé)執(zhí)行Lua腳本中的Redis命令 的偽客戶端.
? 會被關(guān)聯(lián)在redisServer的lua_client屬性中
struct redisServer{
// .....
redisClient *lua_client;
// .....
}
? 這個(gè)偽客戶端會一直保存到服務(wù)器關(guān)閉
4). AOF文件的偽客戶端
? 服務(wù)器載入AOF文件時(shí),會創(chuàng)建 用于執(zhí)行AOF文件包含的Redis命令 的偽客戶端,載入完成之后關(guān)閉這個(gè)偽客戶端.
6. 服務(wù)器
? Redis服務(wù)器負(fù)責(zé)與多個(gè)客戶端建立網(wǎng)絡(luò)連接,處理多個(gè)客戶端發(fā)送的命令請求,在數(shù)據(jù)庫中保存客戶端執(zhí)行命令所產(chǎn)生的數(shù)據(jù),并通過資源管理來維持服務(wù)器自身的運(yùn)轉(zhuǎn).
(1). 命令請求的執(zhí)行過程
- 客戶端向服務(wù)端發(fā)送命令請求
- 服務(wù)器接受并處理客戶端發(fā)來的命令請求,在數(shù)據(jù)庫中進(jìn)行相應(yīng)的操作,并產(chǎn)生回復(fù)信息
- 服務(wù)端將命令回復(fù)發(fā)送給客戶端
- 客戶端接受服務(wù)端返回的命令回復(fù),并將這個(gè)回復(fù)打印給用戶看
1). 發(fā)送命令請求
? Redis服務(wù)器的命令請求來自于Redis客戶端,當(dāng)用戶在客戶端鍵入一個(gè)命令請求時(shí),客戶端會將這個(gè)命令轉(zhuǎn)化為REST協(xié)議格式,然后通過與服務(wù)器連接的套接字將轉(zhuǎn)化出來的數(shù)據(jù)發(fā)送給服務(wù)器.
2). 讀取命令請求
- 服務(wù)器讀取套接字中REST協(xié)議格式的命令請求,保存到對應(yīng)客戶端結(jié)構(gòu)體的輸入緩沖中去
- 對輸入緩沖中的數(shù)據(jù)進(jìn)行分析,提取出命令中包含的參數(shù),以及參數(shù)個(gè)數(shù).保存到客戶端結(jié)構(gòu)體的參數(shù)字段
- 調(diào)用命令執(zhí)行器執(zhí)行指定的命令
3). 命令執(zhí)行器(1):查找命令實(shí)現(xiàn)
? 命令執(zhí)行器要做的第一件事是從命令表中查找參數(shù)數(shù)組0下標(biāo)處,也就是命令名對應(yīng)的函數(shù),并將其保存到cmd字段
4). 命令執(zhí)行器(2):執(zhí)行預(yù)備操作
? 此時(shí),服務(wù)器已經(jīng)擁有了命令實(shí)現(xiàn)函數(shù),命令的參數(shù),命令的個(gè)數(shù).但是還需要執(zhí)行一些準(zhǔn)備工作
- 檢查客戶端狀態(tài)的cmd指針是否為null,為null說明未找到相應(yīng)的函數(shù),會返回一個(gè)錯(cuò)誤
- 檢查函數(shù)的參數(shù)數(shù)量是否和命令中的參數(shù)數(shù)量一樣,如果不一樣返回一個(gè)錯(cuò)誤
- 檢查客戶端是否通過了身份驗(yàn)證
- 如果正在執(zhí)行事務(wù),那么只會執(zhí)行控制事務(wù)的命令,其他命令會被放在事務(wù)隊(duì)列中
5). 命令執(zhí)行器(3):調(diào)用命令的實(shí)現(xiàn)函數(shù)
? 執(zhí)行以下語句:
// client是指向客戶端狀態(tài)的指針,其中就包含了參數(shù)
client->cmd->proc(client)
? 被調(diào)用的命令實(shí)現(xiàn)函數(shù)會執(zhí)行制定的操作,并產(chǎn)生相應(yīng)的命令回復(fù),保存在客戶端狀態(tài)的輸出緩存中.
? 然后為這個(gè)客戶端的套接字關(guān)聯(lián)命令回復(fù)處理器,這個(gè)處理器負(fù)責(zé)將命令回復(fù)返回給客戶端
6). 命令執(zhí)行器(4):執(zhí)行后續(xù)工作
- 如果服務(wù)器開啟了慢查詢?nèi)罩竟δ?那么慢查詢?nèi)罩灸K會檢查是否需要為剛剛執(zhí)行的命令添加一條慢查詢?nèi)罩?/li>
- 更新命令執(zhí)行函數(shù)的總調(diào)用時(shí)長和調(diào)用次數(shù)
- 如果開啟了AOF功能,把這條命令寫入AOF緩沖區(qū)
- 如果有其他的服務(wù)器正在復(fù)制這個(gè)服務(wù)器,那么會把這個(gè)命令傳播給所有從服務(wù)器
7). 將命令回復(fù)給客戶端
? 在實(shí)現(xiàn)函數(shù)中已經(jīng)將命令回復(fù)處理器和客戶端套接字關(guān)聯(lián),這時(shí)==當(dāng)客戶端套接字狀態(tài)變?yōu)榭蓪憰r(shí)==,命令回復(fù)處理器就會==將保存在輸出緩沖區(qū)的命令回復(fù)發(fā)送給客戶端==.
? 發(fā)送完畢之后回復(fù)處理器就會==清空輸出緩沖區(qū)==.
8). 客戶端接受并打印命令回復(fù)
? 客戶端收到的是REST協(xié)議格式的命令回復(fù),將它轉(zhuǎn)化為人可讀的格式后,打印到屏幕上給用戶看.
(2). serverCron函數(shù)
? Redis服務(wù)中的serverCron函數(shù)默認(rèn)每100毫秒執(zhí)行一次,這個(gè)函數(shù)負(fù)責(zé)==管理服務(wù)器的資源,保存服務(wù)器的良好運(yùn)轉(zhuǎn)==.
下面展示了對redisServer中屬性的維護(hù):
struct redisServer{
// .....
// 系統(tǒng)時(shí)鐘緩存
// serverCron函數(shù)中每次會更新這兩個(gè)緩存,也就是說,Redis服務(wù)器中的時(shí)間是100毫秒更新一次的.
// 一般都是用這個(gè)時(shí)間緩存作為系統(tǒng)時(shí)間
// 但是對于為鍵設(shè)置過期時(shí)間,添加慢查詢?nèi)罩具@種需要高精度時(shí)間的功能
// Redis還是會使用系統(tǒng)調(diào)用去獲取最新的系統(tǒng)時(shí)間的.
// 保存了秒級精度的系統(tǒng)當(dāng)前UNIX時(shí)間戳
time_t unixtime;
// 保存了毫秒級別的時(shí)間戳
time_t mstime;
// LRU時(shí)鐘緩存
// 服務(wù)器狀態(tài)中的lruclock和之前的兩個(gè)屬性一樣,都是服務(wù)器時(shí)間緩存的一種
// 這個(gè)屬性用于計(jì)算一個(gè)數(shù)據(jù)庫鍵的空轉(zhuǎn)時(shí)間.
// 服務(wù)器會使用lruclock減去robj的lru屬性(最后一次被命令訪問的時(shí)間).
// 該屬性在serverCron函數(shù)中每10秒更新一次.
unsigned lruclock:22;
// 每秒命令執(zhí)行次數(shù)
// 這個(gè)屬性會被serverCron函數(shù)中的trackOperationsPerSecond函數(shù)每100毫秒更新一次
// 會以抽樣計(jì)算的方式,估算并記錄服務(wù)器在最近一秒鐘處理的命令請求數(shù)量
long long ops_sec_last_sample_time;
// 記錄服務(wù)器的內(nèi)存峰值大小
// 每次serverCron執(zhí)行時(shí),程序都會查看當(dāng)前使用的內(nèi)存數(shù)量,判斷并更新這個(gè)峰值屬性
size_t stat_peak_memory;
// 關(guān)閉服務(wù)器的標(biāo)識,1為關(guān)閉服務(wù)器,0不做動作
// 啟動服務(wù)器時(shí),Redis會為服務(wù)器進(jìn)程的SIGNTERM信號關(guān)聯(lián)處理器
// 每當(dāng)服務(wù)器接到這個(gè)信號時(shí),就會打印日志并設(shè)置服務(wù)器的shurdown_asap標(biāo)識為1
// 然后serverCron函數(shù)運(yùn)行時(shí)就會檢查這個(gè)標(biāo)識
// 如果為1,就做關(guān)閉服務(wù)器的準(zhǔn)備(RDB持久化),然后關(guān)閉服務(wù)器
int shutdown_asap;
// BGREWRITEAOF延遲標(biāo)記
// 之前提到過執(zhí)行BGSAVE時(shí)遇到BGREWRITEAOF是會被延遲的
// 延遲就會將這個(gè)標(biāo)記更新為1
// 在每一次serverCron中都會對這個(gè)標(biāo)記進(jìn)行檢查
// 如果為1,那么進(jìn)行BGREWRITEAOF(還是會檢查BGSAVE和BGREWRITEAOF是都在執(zhí)行,如果是,再次延遲)
int aof_rewrite_scheduled;
// 記錄BGSAVE和BGREWRITEAOF的運(yùn)行情況
// 在serverCron執(zhí)行過程中,會檢查這兩個(gè)值,只要有一個(gè)不為-1
// 就會執(zhí)行wait3函數(shù),檢查子進(jìn)程是否有發(fā)信號到服務(wù)器進(jìn)程
// 如果有,那么執(zhí)行RDB或者AOF的后續(xù)操作(文件的替換)
// 如果都為-1,那么會檢查是否有被延遲的BGREWRITEAOF
// 檢查服務(wù)器的自動保存條件是否滿足,如果滿足,進(jìn)行BGSAVE
// 檢查AOF重寫條件是否滿足,如果滿足并且沒有其他的持久化操作,進(jìn)行BGREWRITEAOF
// 執(zhí)行BGSAVE的子進(jìn)程的id,如果沒在執(zhí)行,為-1
pid_t rdb_chile_pid;
// 記錄執(zhí)行BGREWRITEAOF的子進(jìn)程的id,如果沒在執(zhí)行,為-1
pid_t aof_chile_pid;
// serverCron函數(shù)的計(jì)數(shù)器
// 每執(zhí)行一次,計(jì)數(shù)器加一
int cronloops;
// .....
};
其他的管理:
- 管理客戶端資源:調(diào)用clientsCron函數(shù),這個(gè)函數(shù)會對客戶端進(jìn)行連接超時(shí)檢查和緩沖區(qū)大小檢查(如果過大,重建)
- 管理數(shù)據(jù)庫資源:也就是刪除其中的過期鍵,并在有需要時(shí)對字典進(jìn)行收縮
- AOF寫入:每次serverCron都會對AOF寫入的條件進(jìn)行判斷,如果滿足進(jìn)行響應(yīng)的操作(寫入和同步)
- 關(guān)閉異步客戶端:客戶端的輸出緩存超出范圍,會被關(guān)閉
(3). 初始化服務(wù)器
? 一個(gè)Redis服務(wù)器從啟動到準(zhǔn)備好接收客戶端的命令請求,要經(jīng)歷一系列的初始化的設(shè)置
1). 初始化服務(wù)器狀態(tài)結(jié)構(gòu)
? 創(chuàng)建一個(gè)struct redisServer類型的實(shí)例變量,為結(jié)構(gòu)中的各個(gè)屬性設(shè)置初始值(服務(wù)器的運(yùn)行id, 默認(rèn)配置文件路徑,默認(rèn)服務(wù)器頻率,服務(wù)器運(yùn)行架構(gòu)(機(jī)器的字長),默認(rèn)端口號).
2). 載入配置選項(xiàng)
? 載入配置文件,根據(jù)其中的內(nèi)容來修改服務(wù)器的默認(rèn)配置
3). 初始化服務(wù)器數(shù)據(jù)結(jié)構(gòu)
? 服務(wù)器狀態(tài)還有其他數(shù)據(jù)結(jié)構(gòu)的屬性,如果server.clients鏈表,server.db數(shù)組等等.
? 在這里才配置的原因是,這些屬性都需要根據(jù)配置來正確的初始化(避免重復(fù)調(diào)整).
? 除此之外,還會進(jìn)行:為服務(wù)器設(shè)置進(jìn)程信號處理器,創(chuàng)建共享對象,打開監(jiān)聽窗口,為serverCron函數(shù)創(chuàng)建時(shí)間事件,如果AOF功能打開,那么打開現(xiàn)有的AOF文件(不存在則新建),打印出Redis的開始問候語.
4). 還原數(shù)據(jù)庫狀態(tài)
- 如果服務(wù)器啟用了,那么使用AOF文件來還原數(shù)據(jù)庫狀態(tài).
- 如果沒有啟用,那么使用RDB文件來進(jìn)行還原
5). 執(zhí)行事件循環(huán)
? 打印出日志,來時(shí)執(zhí)行服務(wù)器的事件循環(huán).