本文主要介紹Redis服務器數據庫的設計與實現,說明保存數據庫的方法,保存鍵值對的方法,以及針對數據庫添加、刪除、查找、更新操作的實現方法,并且還會說明對過期鍵的處理方式。
I、服務器中的數據庫
Redis服務器將所有的數據庫都保持在redisServer結構的db數組中,每個db數組都是一個redisDb結構,每個redisDb都表示一個數據庫:
II、切換數據庫
客戶端可以通過SELECT
命令來切換目標數據庫。
在服務器內部,客戶端狀態redisClient結構的db屬性記錄了客戶端當前的目標數據庫,這個屬性是一個指向redisDb的指針:
typedef struct redisClient {
//記錄客戶端當前正在使用的數據庫
redisDb *db;
} redisClient;
通過修改redisClient.db指針,讓它指向服務器中的不同數據庫,從而實現切換數據庫的功能,這就是SELECT
命令的實現原理。
III、數據庫鍵空間
redisDb結構中的dict字典保存了數據庫中的所有鍵值對,我們將這個字典稱為鍵空間:
typedef struct redisDb {
//數據庫鍵空間,保存所有鍵值對
dict *dict;
} redisDb;
數據庫的鍵空間是一個字典,所有對數據庫的操作,都是以字典操作來實現的,如數據庫的添加、刪除、更新、取值等。
IV、設置鍵的生存時間或過期時間
4.1 設置過期鍵命令
通過EXPIRE
命令或PEXPIRE
命令,客戶端可以以秒或毫秒為單位為數據庫中的某個鍵設置生存時間(TTL,Time To Live)。
客戶端還可以通過EXPIREAT
命令或PEXPIREAT
命令,以秒或毫秒為單位設置過期時間,這個過期時間是一個UNIX時間戳。
TTL
命令和PTTL
命令接受一個帶有生存時間或過期時間的鍵,返回這個鍵的剩余生存時間。
4.2 保存過期時間
redisDb結構的expires字典保存了數據庫中所有鍵的過期時間,稱這個字典為過期字典:
typedef struct redisDb {
//過期字典,保存著鍵的過期時間
dict *expires;
} redisDb;
下圖展示了這個結構:
4.3 移除過期時間
PERSIST
命令可以移除一個鍵的過期時間,其是PEXPIREAT
命令的反操作。
V、過期鍵刪除策略
5.1 三種基本刪除策略
1、定時刪除:在設置鍵的過期時間的同時,創建一個定時器,讓定時器在鍵過期時間到達時,執行對鍵的刪除操作。
定時刪除策略對內存是最友好的,因為其可以保證過期鍵盡可能快的被刪除,并釋放過期鍵所占內存。
但其對CPU時間是不友好的, 在過期鍵比較多的情況下,刪除過期鍵這一行為可能會占用相當一部分CPU時間。如果有當量的命令請求等待服務器處理,那么服務器應該優先將CPU時間處理客戶端請求,而不是在刪除過期鍵上面。
2、惰性刪除:放任過期鍵不管,但是每次從鍵空間中獲取鍵時,都檢查取得的鍵是否過期,如果過期則刪除,如果不過期,則返回該鍵。
與上面的分析類似,惰性刪除策略對CPU時間來說是最友好的,但對內存是不友好的。
3、定期刪除:每隔一段時間,程序就對數據庫進行一次檢查,刪除里面的過期鍵。至于要刪除多少過期鍵,以及要檢查多少個數據庫,都是由算法決定的。
定期刪除策略是前面兩種刪除策略的整合和折中:
每隔一段時間執行一次刪除操作,并限制刪除操作的時長和頻率,以減少刪除操作對CPU時間的影響。
通過定期刪除有效減少了因為過期鍵而帶來的內存浪費。
5.2 Redis的過期鍵刪除策略
Redis服務器采用惰性刪除和定期刪除兩種策略,通過配合使用這兩種刪除策略,可以很好的在合理使用CPU時間和避免內存浪費之間取得平衡。
VI、AOF,RDB和復制功能對過期鍵的處理
RDB持久化、AOF持久化以及復制功能是如何處理數據庫中的過期鍵的?
6.1 生成RDB文件
在執行SAVE
命令或BGSAVE
命令創建一個RDB文件時,程序對數據庫中的鍵進行檢查,已過期的鍵不會被保存到新創建的RDB文件中。
所以,數據庫中包含過期鍵不會對生成新的RDB文件造成影響。
6.2 載入RDB文件
如果服務器開啟了RDB功能,則服務器將對RDB文件進行載入:
· 如果服務器以主服務器模式運行,那么在載入RDB文件時,程序會對文件中保存的鍵進行檢查,未過期的鍵會被載入到數據庫中,而過期鍵會被忽略。
所以過期鍵對載入RDB文件的主服務器不會造成影響。
· 如果服務器以從服務器模式運行,那么在載入RDB文件時,文件中保存的所有鍵不論是否過期,都會被載入到數據庫中,但是因為主從服務器在進行數據同步的時候,從服務器就會重新更新為與主服務器一致,而這時也會刪除過期鍵。
因此,過期鍵對載入RDB文件的從服務器也不會造成影響。
6.3 AOF文件寫入
當服務器以AOF持久化模式運行時,如果數據庫中的某個鍵已經過期,還沒有被惰性刪除或定期刪除,則AOF文件不會因為這個過期鍵而產生任何影響,這是因為當過期鍵被惰性刪除或定期刪除之后,程序會向AOF文件追加一條DEL
命令。
6.4 AOF重寫
在執行AOF重寫的過程中,程序會對數據庫中的鍵進行檢查,已過期的鍵不會被重寫到AOF文件中。
6.5 復制
當服務器運行在復制模式下,從服務器的過期鍵刪除動作由主服務器控制:
· 主服務器在刪除一個過期鍵之后,會顯式的向所有從服務器發送一個DEL
命令;
· 從服務器在執行客戶端發送的讀命令時,即使碰到過期鍵也不會將過期鍵刪除,而是正常返回;
· 從服務器只有在接到主服務器發來的DEL
命令之后,才會刪除過期鍵;
通過由主服務器來控制從服務器統一的刪除過期鍵,可以保證主從服務器數據的一致性。
例如,有下圖的主從服務器結構:
當客戶端向從服務器發送GET message
時,從服務器正常返回過期鍵:
當有客戶端向主服務器發送GET message
時,主服務器發現message過期,會刪除message鍵,向客戶端返回nil,并向從服務器發送DEL message
:
最后的主從服務器結構為:
【參考】
[1] 《Redis設計與實現》
歡迎轉載,轉載請注明出處wenmingxing Redis之數據庫實現