Redis源碼解析數據庫redisDb

[Redis 源碼解析 1:數據庫 redisDb]

服務器中的數據庫

Redis 服務器將絕大部分的信息都保存在 server.h/redisServer。redis 的數據是保存在 redisServer 中的 redisDb 結構中。

struct redisServer {
    // ...
    redisDb *db; // 數據庫列表
    // ...
    int dbnum;   // 數據庫數量
    // ...
}
  • db 中每個redisDb結構代表一個數據庫。
  • 在初始化服務器時,程序會根據服務器狀態的 dbnum 屬性來決定應該創建多少個數據庫。
  • dbnum 屬性的值由服務器配置的 database 選項決定,默認情況下,該選項的值爲16,所以Redis服務器默認會創建16個數據庫。

![redisServer數據結構](/Users/mjduan/Documents/markdown/圖片/篇章1 redisDB/redisServer數據結構.png)

數據庫鍵空間

Redis 是一個鍵值對數據庫服務器,服務器中的每個數據庫都由一個 server.h/redisDb 結構表示. 其中,redisDbdict 字典屬性保存了數據庫中的所有鍵值對,我們將這個字典稱爲鍵空間(key space):

/* Redis database representation. There are multiple databases identified
 * by integers from 0 (the default database) up to the max configured
 * database. The database number is the 'id' field in the structure. */
//Redis數據庫結構,通過ID來識別多個數據庫
typedef struct redisDb {
    // 當前數據口的所有鍵值對空間
    dict *dict;                 /* The keyspace for this DB */
    // 存放已設置exptime的key的集合
    dict *expires;              /* Timeout of keys with a timeout set */
    ....
    ....
    // 數據庫id
    int id;                     /* Database ID */
    // 數據庫的平均TTL
    long long avg_ttl;          /* Average TTL, just for stats */
    unsigned long expires_cursor; /* Cursor of the active expire cycle. */
    list *defrag_later;         /* List of key names to attempt to defrag one by one, gradually. */
    clusterSlotToKeyMapping *slots_to_keys; /* Array of slots to keys. Only used in cluster mode (db 0). */
} redisDb;

dict 中的數據跟我們平常操作的鍵值對是一一對應的:

  • dict 的 key 就是數據庫中的 key,字符串類型
  • dict 的 值 就是數據庫中的 值,這個值可以是 stringhashzsetsetlist 中的任何一種

示例

如果我們在數據庫中,執行以下命令:

redis > SET str_key str_value
OK
redis > RPUSH list_key a b c
(integer) 3

新添加的兩個 key 的結構如下圖所示:

![redisDB數據結構](/Users/mjduan/Documents/markdown/圖片/篇章1 redisDB/redisDB數據結構.png)

從上面的示例圖可以很清晰地知道 Redis 數據是如何組織的,增刪改查也就是對 dict 的操作而已

Key 的過期時間

1. 數據結構

redisDb 中的 expires 屬性保存了存放已設置exptime的key的集合的過期時間,我們姑且就稱它爲過期字典吧。

  • 過期字典中的鍵,是一個指針,指向了真實數據的 key,不會浪費空間多保存一次
  • 過期字典中的值,存的是具體的過期時間點,精確到毫秒的時間戳
/* Redis database representation. There are multiple databases identified
 * by integers from 0 (the default database) up to the max configured
 * database. The database number is the 'id' field in the structure. */
//Redis數據庫結構,通過ID來識別多個數據庫
typedef struct redisDb {
    // 當前數據口的所有鍵值對空間
    dict *dict;                 /* The keyspace for this DB */
    // 存放已設置exptime的key的集合
    dict *expires;              /* Timeout of keys with a timeout set */
    ....
    ....
    // 數據庫id
    int id;                     /* Database ID */
    // 數據庫的平均TTL
    long long avg_ttl;          /* Average TTL, just for stats */
    unsigned long expires_cursor; /* Cursor of the active expire cycle. */
    list *defrag_later;         /* List of key names to attempt to defrag one by one, gradually. */
    clusterSlotToKeyMapping *slots_to_keys; /* Array of slots to keys. Only used in cluster mode (db 0). */
} redisDb;

命令TTLPTTL 都是去查這個過期字典的過期時間,然後減去當前時間,得到的就是剩餘的時間啦。

那麼expires中entry的key和value代表什麼?

  • key:某個鍵對象
  • value:long long類型的整數,表示key的過期時間

![redisDB的key過期時間](/Users/mjduan/Documents/markdown/圖片/篇章1 redisDB/redisDB的key過期時間.png)

2. 過期 key 的刪除策略

一個 key 過期時間到了之後,是如何進行刪除的呢?Redis 使用了一下兩種策略:惰性刪除、定期刪除

惰性刪除

惰性刪除策略指的是:key 在過期之後,沒有立即刪除,而是在讀寫 key 的時候,纔對過期的 key 進行刪除。 代碼實現在 db.c/expireIfNeeded 方法中。所有 key 的讀寫之前,都會先調用 expireIfNeeded 對 key 進行檢查,如果已過期,則刪除。

`

int expireIfNeeded(redisDb *db, robj *key, int flags) {
    if (server.lazy_expire_disabled) return 0;
    //看key是否過期了
    if (!keyIsExpired(db,key)) return 0;
    ...
    deleteExpiredKeyAndPropagate(db,key);
    ...
    return 1;
}

int keyIsExpired(redisDb *db, robj *key) {
    /* Don't expire anything while loading. It will be done later. */
    if (server.loading) return 0;
    
    //獲取key對應的過期時間,存儲的是時間戳
    mstime_t when = getExpire(db,key);
    mstime_t now;

    if (when < 0) return 0; /* No expire for this key */

    //獲取系統的當前時間,時間戳,是毫秒級的long類型數據
    now = commandTimeSnapshot();

    /* The key expired if the current (virtual or real) time is greater
     * than the expire time of the key. */
    //如果過了過期時間點
    return now > when;
}

`

定期刪除

定期刪除策略指的是:Redis 每隔一段時間,隨機從數據庫中取出一定量的 key 進行檢查,如果已過期,則進行刪除。 代碼實現在 expire.c/activeExpireCycle 方法中。

刪除的步驟:

  1. 從過期字典中隨機 20 個 key
  2. 刪除這 20 個 key 中已經過期的 key
  3. 如果過期的 key 比率超過 1/4,那就重複步驟 1

爲什麼只是隨機挑 一些 key 呢?因爲如果把所有 key 都遍歷一遍,那這個性能肯定是不能接受的!

總結

redis中所有key都存儲在redisDB的dict字典中,增刪改查也就是對 dict 的操作而已,而對於設置了expire過期時間的key來說,在expire字段(字典類型)中也存儲了該字段,value是過期時間(時間戳類型)。

過期key的刪除時惰性刪除結合定期刪除,在操作redis key時,會先判斷該key是否已過期,如果過期了則進行刪除。

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