Redis的結構和運作機制

數據庫的結構

1488206-20200316185701397-777082398.png

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時間:

1488206-20200316184943158-748370441.png

第一個save的表示每900秒,至少一個key發生變化,則歸檔一次。第二個save則表示每300秒,至少10個key變化,則歸檔。第三個同理,是爲了應對短時間內的大量服務。

2、也可以修改rdb文件的命名和保存路徑:

1488206-20200316185228419-1623296457.png

AOF方式

以redis網絡協議的格式記錄對數據庫進行的寫命令。

優勢:append模式寫日誌,即使宕機,不會影響已記錄的日誌。

劣勢:同數量的數據集,AOF體量比RDB大,效率低。

配置:
在redis.conf中允許打開AOF模式,改爲yes:

1488206-20200316185318581-330082725.png

配置AOF的同步方式,always表示每次修改都要追加日誌:

1488206-20200316185511124-944896032.png

AOF的原理,兩個核心函數:

save():aof_buf -> aof文件

write():aof文件 -> 磁盤

共三種模式,第二種綜合性性能較好。

混合方式

增大定期歸檔的時間跨度,歸檔間隔期,用AOF記錄修改命令。

事件

文件事件和時間事件

1、文件事件

Redis使用socket進行client和server的通信,來完成實現高效的命令請求處理。採用非阻塞、多路複用IO模式。

在多個客戶端中實現多路複用,接受它們發來的命令請求,並將命令的執 行結果返回給客戶端。

Redis 將這類因爲對套接字進行多路複用而產生的事件稱爲文件事件。文件事件分爲讀事件和寫事件。

讀事件實現了命令請求的接收,生命週期與該客戶端和服務器的連接狀態相同。

寫事件實現了命令結果的返回。

2、時間事件

時間事件完成服務器的常規操作,分爲單次執行事件和循環執行事件,服務器常規操作 serverCron 就是循環事件。

其實現結構是無序鏈表,所以查詢的時間複雜度爲O(N)。

3、文件事件和時間事件之間是合作關係:一種事件會等待另一種事件完成之後再執行,不會出現搶佔情況。由於優先級的問題,時間事件的實際執行時間通常會比預定時間晚一些。

參考

《Redis設計和實現》黃健宏
《Redis實戰》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章