參考:[Redis設計與實作]
文章目錄
- 1、資料庫 -資料結構與實作
-
- 1、資料結構
- 2、用戶端通路與資料庫切換
- 3、單個資料庫結構實作
- 4、讀寫鍵空間觸發額外操作
- 2、鍵過期判定與實作
-
- 1、過期設定指令
- 2、過期時間的結構化存儲
- 3、删除政策
-
- 1、常見删除政策對比
-
- 1、定時删除
- 2、惰性删除
- 3、定期删除
- 2、Redis使用的删除政策
-
- 1、惰性删除的實作
- 2、定期删除的實作
- 3、RDB複制對過期鍵的處理
-
- 1、生成RDB檔案
- 2、載入RDB檔案
- 4、AOF指派對過期鍵的處理
-
- 1、AOF檔案寫入
- 2、AOF檔案重寫
- 3、複制
- 4、資料庫訂閱與通知
-
- 1、功能描述
- 2、功能實作
1、資料庫 -資料結構與實作
1、資料結構
Redis伺服器所有的資料庫都儲存在 redis.h/redisServer結構中的db數組中,也就是說對于Redis來說不同的資料庫其實是對應的程式中的不同的數組。
db數組的每個項都是 redis.h/redisDb結構,并且redis通過redisServer中的dbNum屬性來決定初始化的資料庫數量,這個屬性是通過database選項進行配置,預設是16.
redisServer結構如下
struct redisServer {
redisDb *db,//伺服器資料庫的數組
dbNum int, //伺服器上資料庫數量的配置參數
}
具體的資料庫結構關系如下:
2、用戶端通路與資料庫切換
預設情況下,Redis用戶端的目标資料庫是0号資料庫。用戶端可以通過SELECT指令來實作目标資料庫切換
在Redis記憶體,通過redisClient的資料結構來記錄用戶端目前的目标資料庫等資訊。主要是通過redisDb的指針來存取目前目标資料庫。
redisClient結構如下:
typedef struct redisClient{
redisDb *db //記錄用戶端目前的目标資料庫
//...
}
通過SELECT指令改變目标資料庫其實就是修改了,redisDb的指針指向,具體的結構關系如下:
3、單個資料庫結構實作
伺服器中的每個資料庫都是由 redis.h/redisDb的資料結構表示和實作的,而redisDb又是通過dict資料字典來儲存了資料庫中的所有鍵值對,這個字典就是所謂的鍵空間。
redisDb資料結構:
typedef struct redisDb{
//...
dict *dict,//資料庫鍵空間,儲存資料庫中的所有鍵值對資訊
}
鍵空間的特性:
- 鍵空間的鍵就是資料庫的鍵,每一個鍵都是一個字元串對象(不可變和唯一性)
- 鍵空間的值就是資料庫的值,值可以是字元串對象、清單對象、哈希表對象、集合對象和有序集合對象中的一種
具體看一下資料庫鍵空間的例子,對鍵空間的增删改查與正常資料庫類似,略過不表:
4、讀寫鍵空間觸發額外操作
redis對鍵空間進行讀取,會觸發額外的維護操作,包括如下:
- 讀寫之後,伺服器會根據鍵是否存在來更新伺服器的鍵空間命中次數或者鍵空間的不命中次數。
- 讀寫之後,更新鍵的LRU時間,這個屬性可用于計算鍵的空閑時間。
- 如果資料庫讀取鍵的時候發現鍵已經過期,會先删除這個鍵,然後才會執行餘下操作。
- 如果使用WATCH指令監視某個鍵,伺服器會标記被修改後的鍵為dirty。
- 伺服器每次修改一個鍵都會對髒鍵計數器的值加一,這個計數器會觸發伺服器的持久化以及複制操作。
- 如果開啟了資料庫通知功能,則在修改之後會觸發相應的資料庫通知。
2、鍵過期判定與實作
1、過期設定指令
- EXPIRE :将key的生存時間設定為ttl 秒
- PEXPIRE :将key的生存時間設定為ttl 毫秒
- EXPIREAT :将過期時間設定為timestamp所指定的秒數時間戳
- PEXPIREAT :将過期時間設定為timestamp所指定的毫秒數時間戳
- TTL :計算key剩餘的生存時間秒數
- PTTL :計算key剩餘的生存時間毫秒數
- PERSIST :移除key對應的過期時間
2、過期時間的結構化存儲
redis通過redisDb結構的expires字典儲存資料庫所有鍵的過期時間
- 過期字典的鍵是一個指針(指向對應的某個鍵對象)
- 過期字典的值是一個long long 類型的正數,儲存了對應資料庫鍵的過期時間(毫秒精度的UNIX時間戳)
expire字典結構:
typedef struct redisDb{
dict *expires;//過期資料字典,儲存鍵的過期時間
}
結構化關系:
3、删除政策
1、常見删除政策對比
1、定時删除
優點:
- 這種政策對記憶體是最友好的,通過定時器實作,定時删除可以保證過期鍵盡快删除,并釋放過期鍵所占用的記憶體空間
缺點:
- 對于CPU時間(CPU輪轉執行)是最不友好的,在過期鍵比較多的情況下,删除過期鍵會占用相當一部分CPU時間,在CPU時間緊張的情況下,CPU時間被用在删除與目前任務無關的過期鍵上,對伺服器的響應時間和吞吐量會造成影響
- 建立一個定時任務需要用到redis伺服器中的時間事件,目前的時間事件的實作方式是無序連結清單,查找的複雜度為O(N),并不能高效的處理大量的時間事件。
2、惰性删除
優點:
- 對CPU時間是最友好的,程式隻會對需要被取出的鍵進行是否過期的判斷,沒有在無關的過期鍵上面花費任何CPU時間
缺點:
- 對記憶體最不友好,如果一個鍵過期了,但是沒有被調用,那麼它會一直停留在記憶體,占用的記憶體空間不會被釋放。伺服器不會自動去釋放它們,對于Redis這種運作非常依賴記憶體的伺服器來說,非常不友好。
3、定期删除
定期删除是前兩種方式的整合和折中。采用定期删除必須根據情況設定删除操作的執行時長和頻率,不然就可能退化成上面兩種其中任意一種删除政策,缺點暴露
2、Redis使用的删除政策
Redis的實作采用 惰性删除和定期删除兩種删除政策。配合這兩種删除政策,伺服器可以很好的在合理使用CPU時間和避免記憶體浪費之間取得平衡
1、惰性删除的實作
惰性删除政策是通過 db.c/expireIfNeeded函數實作,所有讀寫資料庫的指令在執行之前都會先調用這個方法進行檢查。如果鍵過期則進行删除,否則不做具體操作。
具體的實作分為兩種情況:1、鍵不存在(前一次操作被删除)2、鍵存在。具體的操作過程如下(以GET舉例):
2、定期删除的實作
定期删除通過redi.c/activeExpireCycle函數實作,當redis伺服器周期性操作redis.h/serverCron函數執行的時候會調用定期删除函數,它會在規定時間内,分多次周遊伺服器的各個資料庫,從expire字典中随機檢查一部分數量鍵的過期時間,删除其中的過期鍵。
對應的函數屬性資訊:
{
DEFAULT_DB_NUMBERS = 16 //預設每次需要檢查的資料庫數量
DEFAULT_KEY_NUMBERS = 20 //預設每次檢查的key的數量
current_db = 1 //全局變量,檢查進度,因為是分多次進行檢查,是以記錄的是目前執行的資料庫,下一次操作就是從下一個資料庫開始,檢查結束,變量直接置為0
}
操作過程:
- 根據要檢查資料庫數量,進行周遊,取出檢查的key數量個鍵,進行判斷expire字典過期時間,如果過期則進行删除
- 本次檢查儲存檢查進度資訊,等待本次的下一波檢查開始,從檢查進度對應的下一個資料開始檢查,執行上一步相應操作
- 一次檢查分為幾輪,本次檢查結束之後,把檢查進度資訊置為0,等待下一次被觸發。
3、RDB複制對過期鍵的處理
1、生成RDB檔案
在執行SAVE或者BGSAVE指令建立一個新的RDB檔案時,程式會對資料庫中的鍵進行檢查,已過期的鍵bu8hui儲存到新建立的RDB檔案中。
是以,資料庫中包含過期鍵不會對生成新的RDB檔案造成影響。
2、載入RDB檔案
在啟動Redis伺服器時,如果開啟了RDB功能,伺服器對RDB檔案進行載入有兩種情況:
- 如果以主伺服器模式運作,載入RDB檔案時,程式會對鍵進行檢查,未過期的鍵會被載入到資料庫,過期的會被忽略
- 如果是以從伺服器模式運作,載入RDB檔案時,程式會把所有鍵都進行載入,當主伺服器進行資料同步的時候,從伺服器的資料庫會被清空,是以過期鍵對載入RDB檔案不會造成影響。
4、AOF指派對過期鍵的處理
1、AOF檔案寫入
當程式以AOF持久化模式運作時,如果某個鍵已經過期,但是還沒有被删除政策删除,對AOF檔案不會造成任何影響。因為AOF檔案寫入對于過期的key是這麼操作的:
- 從資料庫中删除過期鍵
- 追加一條 DEL 指令到AOF檔案
- 向執行讀取的指令傳回空回複
2、AOF檔案重寫
和AOF檔案寫入類型,不會對過期的key進行寫入,是以過期的key對AOF檔案重寫不會造成任何影響。
3、複制
當伺服器運作在複制模式下,從伺服器的過期删除動作主要由主伺服器控制
- 主伺服器删除一個過期鍵之後,會顯示的想所有從伺服器發送一個DEL指令,告訴伺服器删除這個過期鍵
- 從服務在執行用戶端的指令,即使碰到過期鍵也不會對其進行删除,而是想處理未過期鍵一樣(通過主伺服器統一删除過期鍵,可以保證主從伺服器的一緻性)
- 從伺服器收到DEL指令之後,才會删除過期鍵
4、資料庫訂閱與通知
1、功能描述
該功能可以讓用戶端通過訂閱給定的頻道或者模式,來獲知資料庫中鍵的變化,以及資料庫中指令的執行情況。
Redis用戶端訂閱指令:
SUBSCRIBE _ _keyspace@【資料庫編号】_ _ :【指令或者key名】
,比如說訂閱0号資料庫的message這個key的指令就是:
SUBSCRIBE _ [email protected]_ _:message
伺服器通過配置**
notify-keyspace-events**
選項決定發送通知的類型:
2、功能實作
發送通知功能由notify.c/notifyKeysSpaceEvent函數實作:
/**
* type:目前想要發送的通知類型
* event:時間的名稱
* keys:産生事件的鍵
* dbid:資料庫号碼
*/
void notifyKeysSpaceEvent(int type,char *Event,robj *key,int dbid)
當一個redis指令需要發送資料庫通知的時候會調用該函數,實際實作步驟:
- 判斷操作類型是否是notify-keyspace-events配置的允許發送的内容,如果不是則直接傳回
- 判斷伺服器是否允許發送鍵空間通知,如果允許則會發送事件通知
訂閱事件其實本質就是通過PUBLISH指令來執行的