數據庫的結構
Redis 中的每個數據庫,都由一個 redis.h/redisDb 結構表示。
typedef struct redisDb {
// 保存着數據庫以整數表示的號碼
int id;
// 保存着數據庫中的所有鍵值對數據
// 這個屬性也被稱爲鍵空間(key space)
dict *dict;
// 保存着鍵的過期信息
dict *expires;
// 實現列表阻塞原語,如 BLPOP
// 在列表類型一章有詳細的討論
dict *blocking_keys;
dict *ready_keys;
// 用於實現 WATCH 命令
// 在事務章節有詳細的討論
dict *watched_keys;
} redisDb;
Redis有id 、dict 和 expires 三個重要屬性:
- id 保存數據庫的號碼。Redis 服務器初始化時, 它會創建出 redis.h/REDIS_DEFAULT_DBNUM 個數據庫, 並 將所有數據庫保存到 redis.h/redisServer.db 數組中, 每個數據庫的 id 爲從 0 到 REDIS_DEFAULT_DBNUM - 1 的值。當執行 SELECT number 命令時,程序直接使用 redisServer.db[number] 來切換數據庫。
- dict 保存着數據庫的所有鍵值對數據。Redis的結構可以看成字典的嵌套,類似json的數據結構。dict的內部依然是字典結構,dict的key是字符串對象,表示name,dict的值則是從string到sort-set中的任意一種對象。而刪除數據庫的健,實際上就是刪除dict中對應的健對象和值對象。
- expires也是一個字典,保存鍵的過期時間,注意只保存設置過的過期時間,如果沒設置,則默認爲永久。
字典的底層實現
hashtable
衝突解決:鏈表
擴容:漸進式hash,方法是複製出一個hash表,重算hash值(java8已不再重算)。重點是,擴容和收縮不是一次性完成,而是分多次完成。期間,字典的刪改查操作可以在兩個hashtable上進行,則增加操作只在新hashtable上進行。當字典中保存的數據很多事,可以避免擴容影響性能。
過期鍵的檢查和清除
根據不同的清楚策略,通過expires 字典來檢查鍵是否過期:
- 檢查鍵是否存在於 expires 字典:如果存在,那麼取出鍵的過期時間。
- 判斷當前 UNIX 時間是否大於鍵的過期時間,如果是,那麼鍵已經過期。
過期鍵的清除有三種方式:定時刪除、惰性刪除和定期刪除。
定時刪除
創建一個定時事件,由事件處理 器自動執行鍵的刪除操作。
優點:對內存友好
缺點:可能佔用大量cpu時間
惰性刪除
每次從dict字典取出鍵值時,檢查是否過期,如果過期則刪除,並返回空。
優點:對cpu友好
缺點:過期鍵佔用內存
核心是 db.c/expireIfNeeded 函數。在讀取或寫入數據庫之前,調用 expireIfNeeded 對輸入鍵進行檢查。如果輸入鍵已經過期的話,那麼將鍵、鍵的值、鍵保存在 expires 字典中的過期時間都刪除掉。
定期刪除
是上面兩個策略的結合。每隔一段時間,對 expires 字典進行檢查,並執行惰性刪除。
核心是redis.c/activeExpireCycle,每當 Redis 的例行處理程序 serverCron 執行時,activeExpireCycle 都會被調用。這個函數在規定的時間限制內,儘可能地遍歷各個數據庫的 expires 字典,隨機地檢查一部分鍵的過期時間,並刪除過期鍵。
對RDB、AOF和複製的影響
RDB:在創建新的 RDB 文件時,程序會對鍵進行檢查,過期的鍵不會被寫入到更新後的 RDB 文件 中。
AOF:當過期鍵被惰性刪除、或者定期刪除後,程序會向 AOF 文件追加一條 DEL 命令,來顯式地記錄該鍵已被刪除。
複製:當服務器帶有附屬節點時,過期鍵的刪除由主節點統一控制。主節點再刪除過期鍵後,會會顯式地向所有附屬節點發送一 個 DEL 命令。附屬節點只按DEL命令行動,當它自己碰到過期鍵時,只向主節點返回鍵已過期。
持久化機制
把數據由內存同步到磁盤,會Fork一個子進程來異步的完成。有三種方式,RDB、AOF和混合方式。
RDB方式
即快照,定期一次全量備份,將所有緩存進行序列化存到磁盤。
優勢:災難恢復、性能好
劣勢:1、歸檔前斷線,則這個歸檔週期的數據無法恢復。2、子進程工作,如果數據量大,可能影響性能。
配置:
1、修改redis.conf中的save時間:
第一個save的表示每900秒,至少一個key發生變化,則歸檔一次。第二個save則表示每300秒,至少10個key變化,則歸檔。第三個同理,是爲了應對短時間內的大量服務。
2、也可以修改rdb文件的命名和保存路徑:
AOF方式
以redis網絡協議的格式記錄對數據庫進行的寫命令。
優勢:append模式寫日誌,即使宕機,不會影響已記錄的日誌。
劣勢:同數量的數據集,AOF體量比RDB大,效率低。
配置:
在redis.conf中允許打開AOF模式,改爲yes:
配置AOF的同步方式,always表示每次修改都要追加日誌:
AOF的原理,兩個核心函數:
save():aof_buf -> aof文件
write():aof文件 -> 磁盤
共三種模式,第二種綜合性性能較好。
混合方式
增大定期歸檔的時間跨度,歸檔間隔期,用AOF記錄修改命令。
事件
文件事件和時間事件
1、文件事件
Redis使用socket進行client和server的通信,來完成實現高效的命令請求處理。採用非阻塞、多路複用IO模式。
在多個客戶端中實現多路複用,接受它們發來的命令請求,並將命令的執 行結果返回給客戶端。
Redis 將這類因爲對套接字進行多路複用而產生的事件稱爲文件事件。文件事件分爲讀事件和寫事件。
讀事件實現了命令請求的接收,生命週期與該客戶端和服務器的連接狀態相同。
寫事件實現了命令結果的返回。
2、時間事件
時間事件完成服務器的常規操作,分爲單次執行事件和循環執行事件,服務器常規操作 serverCron 就是循環事件。
其實現結構是無序鏈表,所以查詢的時間複雜度爲O(N)。
3、文件事件和時間事件之間是合作關係:一種事件會等待另一種事件完成之後再執行,不會出現搶佔情況。由於優先級的問題,時間事件的實際執行時間通常會比預定時間晚一些。
參考
《Redis設計和實現》黃健宏
《Redis實戰》