[Redis]-----第二部分 單機(jī)數(shù)據(jù)庫的實(shí)現(xiàn)

第二部分 單機(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è)鍵是否過期:

  1. 檢查這個(gè)鍵是否存在于過期字典,如果存在那么取得過期時(shí)間戳,如果不存在那么一定沒過期
  2. 將過期時(shí)間戳與當(dāng)前的UNIX時(shí)間戳進(jìn)行比較,如果是過去的時(shí)間戳,那么這個(gè)鍵過期了.

(5). 過期鍵刪除策略

三種不同的刪除策略:

  1. 定時(shí)刪除:設(shè)置一個(gè)計(jì)時(shí)器,計(jì)時(shí)器通知在鍵的過期時(shí)間來立即執(zhí)行刪除操作
  2. 惰性刪除:獲取鍵時(shí),判斷是否過期,如果過期,刪除
  3. 定期刪除:每隔一段時(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í)間,刪除掉其中過期的.

工作模式:

  1. 每次函數(shù)被調(diào)用時(shí),都從一部分?jǐn)?shù)據(jù)庫的過期字典中再找出一部分鍵進(jìn)行判斷過期,并進(jìn)行刪除操作
  2. 全局變量current_db記錄當(dāng)前函數(shù)檢查的進(jìn)度,下一次被調(diào)用時(shí),接著這個(gè)進(jìn)度進(jìn)行檢查
  3. 隨著這個(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ù)器:

  1. 如果服務(wù)器是==主服務(wù)器==,程序會對文件中保存的鍵再進(jìn)行一次檢查,過期鍵會被忽略
  2. 如果服務(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ù)器控制:

  1. 主服務(wù)器刪除一個(gè)鍵后,會向所有從服務(wù)器發(fā)送一個(gè)DEL命令,通知從服務(wù)器刪除這個(gè)鍵保持同步.
  2. 從服務(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)

  1. 如果給定的通知是服務(wù)器不允許發(fā)送的,那么直接返回
  2. 檢測服務(wù)器是否允許發(fā)送==鍵空間==通知,如果允許,程序會構(gòu)件并發(fā)送時(shí)間通知
  3. 函數(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屬性:

  1. dirty計(jì)數(shù)器記錄上次(BG)SAVE之后服務(wù)器對數(shù)據(jù)庫狀態(tài)的修改次數(shù)
  2. 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ì)步驟如下:

  1. 創(chuàng)建一個(gè)==不帶網(wǎng)絡(luò)連接的偽客戶端==(其實(shí)是在服務(wù)端內(nèi)部的,所以叫偽客戶端,用于處理命令,其效果和普通的Redis客戶端使用網(wǎng)絡(luò)傳遞命令是一樣的)
  2. 從AOF文件中分析并讀取出一條命令
  3. 使用偽客戶端執(zhí)行這條命令
  4. 重復(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ù)器需要處理以下兩類事件:

  1. 文件事件:Redis服務(wù)器通過套接字(socket)與客戶端進(jìn)行連接,文件事件就是服務(wù)器對套接字操作的抽象.服務(wù)器和客戶端或者其他服務(wù)器的通信就會產(chǎn)生文件事件.服務(wù)器通過監(jiān)聽并處理這些文件事件來完成一些列網(wǎng)絡(luò)通信操作.
  2. 時(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). 客戶端屬性

? 客戶端屬性一般分為兩類:

  1. 一類是比較通用的屬性,這種屬性很少與特定的功能相關(guān),比如名字,創(chuàng)建時(shí)間等等
  2. 另外一類是和特定功能相關(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)閉:

  1. 客戶端進(jìn)程退出或者被殺死,網(wǎng)絡(luò)連接關(guān)閉,從而客戶端關(guān)閉
  2. 發(fā)送了不符合REST協(xié)議的請求
  3. 成為了CLIENT KILL命令的目標(biāo)
  4. 空轉(zhuǎn)時(shí)間超出設(shè)定值
  5. 命令請求大小超過輸入緩沖區(qū)大小1 GB
  6. 發(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í)行過程

  1. 客戶端向服務(wù)端發(fā)送命令請求
  2. 服務(wù)器接受并處理客戶端發(fā)來的命令請求,在數(shù)據(jù)庫中進(jìn)行相應(yīng)的操作,并產(chǎn)生回復(fù)信息
  3. 服務(wù)端將命令回復(fù)發(fā)送給客戶端
  4. 客戶端接受服務(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). 讀取命令請求

  1. 服務(wù)器讀取套接字中REST協(xié)議格式的命令請求,保存到對應(yīng)客戶端結(jié)構(gòu)體的輸入緩沖中去
  2. 對輸入緩沖中的數(shù)據(jù)進(jìn)行分析,提取出命令中包含的參數(shù),以及參數(shù)個(gè)數(shù).保存到客戶端結(jié)構(gòu)體的參數(shù)字段
  3. 調(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)備工作

  1. 檢查客戶端狀態(tài)的cmd指針是否為null,為null說明未找到相應(yīng)的函數(shù),會返回一個(gè)錯(cuò)誤
  2. 檢查函數(shù)的參數(shù)數(shù)量是否和命令中的參數(shù)數(shù)量一樣,如果不一樣返回一個(gè)錯(cuò)誤
  3. 檢查客戶端是否通過了身份驗(yàn)證
  4. 如果正在執(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ù)工作

  1. 如果服務(wù)器開啟了慢查詢?nèi)罩竟δ?那么慢查詢?nèi)罩灸K會檢查是否需要為剛剛執(zhí)行的命令添加一條慢查詢?nèi)罩?/li>
  2. 更新命令執(zhí)行函數(shù)的總調(diào)用時(shí)長和調(diào)用次數(shù)
  3. 如果開啟了AOF功能,把這條命令寫入AOF緩沖區(qū)
  4. 如果有其他的服務(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;
    
    // .....
};

其他的管理:

  1. 管理客戶端資源:調(diào)用clientsCron函數(shù),這個(gè)函數(shù)會對客戶端進(jìn)行連接超時(shí)檢查和緩沖區(qū)大小檢查(如果過大,重建)
  2. 管理數(shù)據(jù)庫資源:也就是刪除其中的過期鍵,并在有需要時(shí)對字典進(jìn)行收縮
  3. AOF寫入:每次serverCron都會對AOF寫入的條件進(jìn)行判斷,如果滿足進(jìn)行響應(yīng)的操作(寫入和同步)
  4. 關(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)

  1. 如果服務(wù)器啟用了,那么使用AOF文件來還原數(shù)據(jù)庫狀態(tài).
  2. 如果沒有啟用,那么使用RDB文件來進(jìn)行還原

5). 執(zhí)行事件循環(huán)

? 打印出日志,來時(shí)執(zhí)行服務(wù)器的事件循環(huán).

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末赡矢,一起剝皮案震驚了整個(gè)濱河市礁阁,隨后出現(xiàn)的幾起案子笙瑟,更是在濱河造成了極大的恐慌游添,老刑警劉巖牲平,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件皂贩,死亡現(xiàn)場離奇詭異,居然都是意外死亡自沧,警方通過查閱死者的電腦和手機(jī)坟奥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人爱谁,你說我怎么就攤上這事晒喷。” “怎么了管行?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵厨埋,是天一觀的道長邪媳。 經(jīng)常有香客問我捐顷,道長,這世上最難降的妖魔是什么雨效? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任迅涮,我火速辦了婚禮,結(jié)果婚禮上徽龟,老公的妹妹穿的比我還像新娘叮姑。我一直安慰自己,他們只是感情好据悔,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布传透。 她就那樣靜靜地躺著,像睡著了一般极颓。 火紅的嫁衣襯著肌膚如雪朱盐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天菠隆,我揣著相機(jī)與錄音兵琳,去河邊找鬼。 笑死骇径,一個(gè)胖子當(dāng)著我的面吹牛躯肌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播破衔,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼清女,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了晰筛?” 一聲冷哼從身側(cè)響起校仑,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎传惠,沒想到半個(gè)月后迄沫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡卦方,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年羊瘩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡尘吗,死狀恐怖逝她,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情睬捶,我是刑警寧澤黔宛,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站擒贸,受9級特大地震影響臀晃,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜介劫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一徽惋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧座韵,春花似錦险绘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至黔帕,卻和暖如春代咸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蹬屹。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工侣背, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人慨默。 一個(gè)月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓贩耐,卻偏偏與公主長得像,于是被迫代替她去往敵國和親厦取。 傳聞我的和親對象是個(gè)殘疾皇子潮太,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355