Redis數據庫實現原理(劃重點)

Redis服務器將所有數據庫都保存在服務器狀態redis.h/redisServer結構的db數組中,db數組的每一項都是一個redis.h/redisDb結構,每個redisDb結構代表一個數據庫,服務器設置dbnum屬性爲初始數據庫的個數,這個屬性一般由數據庫服務器配置conf文件中的database節點來配置,默認情況下這個初始值是16。

struct redisServer{    //數據庫    redisDb *db;    //服務器數量    int dbnum;};

數據庫切換

前面我們說到默認的redis數據庫有16個,那麼我們如何來切換數據庫呢?

127.0.0.1:6379> get name"mango"127.0.0.1:6379> select 1OK127.0.0.1:6379[1]> get name(nil)127.0.0.1:6379[1]> select 0OK127.0.0.1:6379> get name"mango"

默認情況下我們操作的都是第一個數據庫,數據庫數組索引爲[0],我們通過select命令來切換數據庫,通過修改客戶端的指針,來指向第二個數據庫。

注意:這裏我們值得注意的是,我們在維護redis的時候執行一些敏感的指令時養成一個習慣就是先切換數據庫,然後執行。

數據庫鍵空間

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本身就是一個字典類型的數據庫,我們可以看到在RedisDb中,dict保存數據庫的所有鍵值對數據。

字典的鍵是一個字符串對象(StringObject)

字典的值則可以包括Redis的五種基礎結構的任意一種(字符串、列表、哈希表、集合或有序集),例如:列表就是一個ListObject,哈希就是HashObject等。

如圖所示,每個類型對應數據結構不同。

關於Redis數據庫的增、刪、改、查操作這裏就不過多解釋了,跟java或是c#中的實現是相似的,只不過對於字典擴容稍有不同,Redis的擴容是一種漸進式的方式,一點點的將舊錶遷入新表,不會在擴容的時候阻塞其他操作。

其他操作
除了增刪該查的鍵值操作之外,還有很多針對數據庫本身的命令,也是通過對鍵空間進行處理:
• FLUSHDB 命令:刪除鍵空間中的所有鍵值對。
• RANDOMKEY 命令:從鍵空間中隨機返回一個鍵。
• DBSIZE 命令:返回鍵空間中鍵值對的數量。
• EXISTS 命令:檢查給定鍵是否存在於鍵空間中。
• RENAME 命令:在鍵空間中,對給定鍵進行改名。

過期設置

數據過期操作命令通過EXPIRE、PEXPIRE、EXPIREAT和PEXPIREAT四個命令,客戶端可以給某個存在的鍵設置過期時間,當鍵的過期時間到達時,鍵就不再可用。當然SETEX也是可以的,只不過它是一個限定類型命令(可以說是一個複合命令),跟其他的過期時間設置原理一樣。

>set name mango(integer)1>expire name 5    #設置過期時間

過期時間是一個UNIX時間戳,當鍵的過期時間到了,這個鍵就會在數據庫中被刪除。那麼我們可以通過TTL和PTTL命令來返回距離過期時間還有多長時間

> SETEX key 10086 valueOK> TTL key(integer) 10082> PTTL key(integer) 10068998

過期時間保存

前面我們給出的RedisDb裏面的一個屬性dict *expires是保存過期時間的,我們可以看到它也是一個dict鍵值的指針類型,其中這個過期字典中的是一個long long類型的整數,這個整數保存的是過期時間。下面我們來畫一個圖加深印象。

注意:這裏的過期時間的字典是指向的鍵空間的,只不過爲了區分才這樣畫的。

如何檢測key是否過期?

  1. 檢查這個key是否存在expires中

  2. 檢查當前時間是否大於過期時間,如果大於則過期,反之未過期

過期key刪除策略

Redis設置好過期時間後,如果key的過期時間到期,我們的redis沒有及時回收資源可能會導致Redis內存溢出,所以我們需要及時清理過期的key來釋放資源。

定時清理:

此方法就是我設置一個定時器,到點了我就去清理已經過期的key。

優點:就是方便簡潔,保證key過期及時處理;

缺點:也很明顯,掃描整個字典是一個O(n)的時間複雜度,使用這種方式的時候會佔用大量的cpu可能會導致服務器卡頓等現象,我們說過Redis儘量避免使用這類的操作,這樣處理並不高效。

惰性清理:

這種方式就是客戶端請求這個key的時候去判斷時間是否過期,過期則清理。

優點:對cup很友好,使用的時候去清理

缺點:對內存不友好,無效key不及時清理內存得不到釋放,積壓的內存越來越多,如果過期key特別多而且永遠不訪問,可能會導致內存溢出。

定期清理:

定期刪除是結合定是清理和惰性清理的特點選擇一個折中處理,一段時間內處理一定量的key,這樣減少使用cpu帶來的阻塞,一定程度的減少內存積壓。

定期清理的難點在於清理算法,也就是說什麼時候清理多少key這個量度是很難把握的。

那麼我們介紹了這三種清理方式後,Redis的清理過期key的方式是通過惰性清理和定期清理兩種策略來實現的。通過這兩種方式使Redis在cpu和內存之間取得平衡,這兩種方式也是在不同的時期進行的。

過期鍵對AOF、RDB和複製的影響
前面的內容討論了過期鍵對cpu和內存的影響,那麼過期鍵在RDB文件、AOF 文件、AOF重寫以及複製中的影響。
RDB文件在創建新的RDB文件時,程序會對鍵進行檢查,過期的鍵不會被寫入到更新後的RDB文件中,過期鍵對更新後的RDB文件沒有影響。

AOF文件在鍵已經過期,但是還沒有被惰性刪除或者定期刪除之前,這個鍵不會產生任何影響,AOF文件也不會因爲這個鍵而被修改。當過期鍵被惰性刪除、或者定期刪除之後,程序會向AOF文件追加一條DEL命令,來顯式地記錄該鍵已被刪除。
AOF重寫時程序對鍵進行檢查,過期的鍵不會被保存到重寫後的AOF文件。過期鍵對重寫後的AOF文件沒有影響。
複製:當服務器帶有附屬節點時,過期鍵的刪除由主節點統一控制:

  • 如果服務器是主節點,那麼它在刪除一個過期鍵之後,會顯式地向所有附屬節點發送一個DEL命令。
  • 如果服務器是附屬節點,那麼當它碰到一個過期鍵的時候,它會向程序返回鍵已過期的回覆,但並不真正的刪除過期鍵。因爲程序只根據鍵是否已經過期、而不是鍵是否已經被刪除來決定執行流程,所以這種處理並不影響命令的正確執行結果。當接到從主節點發來的DEL命令之後,附屬節點纔會真正的將過期鍵刪除掉。附屬節點不自主對鍵進行刪除是爲了和主節點的數據保持絕對一致,因爲這個原因,當一個過期鍵還存在於主節點時,這個鍵在所有附屬節點的副本也不會被刪除。這種處理機制對那些使用大量附屬節點,並且帶有大量過期鍵的應用來說,可能會造成一部分內存不能立即被釋放,但是,因爲過期鍵通常很快會被主節點發現並刪除

 

總結一下

  1. Redis默認有16個數據庫,但如果沒有切換那麼使用的是第一個默認的數據庫

  2. 數據庫主要由dict和expires兩個字典構成,其中dict保存鍵值對,而expires則保存鍵的過期時間。
    數據庫的鍵總是一個字符串對象,而值可以是任意一種 Redis 數據類型,包括字符串、哈希、集合、列表和有序集。

  3. expires的某個鍵和dict的某個鍵共同指向同一個字符串對象,而expires 鍵的值則是該鍵以毫秒計算的 UNIX 過期時間戳。

  4. Redis使用惰性刪除和定期刪除兩種策略來刪除過期的鍵。

  5. 更新後的RDB文件和重寫後的AOF文件都不會保留已經過期的鍵。

  6. 當一個過期鍵被刪除之後,程序會追加一條新的DEL命令到現有AOF文件末尾。

  7. 當主節點刪除一個過期鍵之後,它會顯式地發送一條DEL命令到所有附屬節點,而從節點是不會主動刪除某個key,這樣保證主從一致性問題。

 

一名正在搶救的coder

筆名:mangolove

CSDN地址:https://blog.csdn.net/mango_love

GitHub地址:https://github.com/mangoloveYu

 

 

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