原文地址:www.xilidou.com/2018/03/20/…java
以前的文章講解了 Redis 的數據結構,這回就能夠看看做爲內存數據庫,Redis 是怎麼存儲數據的。以及鍵是怎麼過時的。程序員
閱讀這篇文章你將會了解到:redis
咱們先看代碼 server.h/redisServer
數據庫
struct redisServer{
...
//保存 db 的數組
redisDb *db;
//db 的數量
int dbnum;
...
}
複製代碼
再看redisDb的代碼:數組
typedef struct redisDb {
dict *dict; /* The keyspace for this DB */
dict *expires; /* Timeout of keys with a timeout set */
dict *blocking_keys; /* Keys with clients waiting for data (BLPOP)*/
dict *ready_keys; /* Blocked keys that received a PUSH */
dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */
int id; /* Database ID */
long long avg_ttl; /* Average TTL, just for stats */
} redisDb;
複製代碼
整體來講redis的 server 包含若干個(默認16個) redisDb 數據庫。緩存
Redis 是一個 k-v 存儲的鍵值對數據庫。其中字典 dict 保存了數據庫中的全部鍵值對,這個地方叫作 keyspace
直譯過來就是「鍵空間」。bash
因此咱們就能夠這麼認爲,在 redisDb 中咱們使用 dict(字典)來維護鍵空間。服務器
keyspace 的 kay 是數據庫的 key,每個key 是一個字符串對象。注意不是字符串,而是字符串對象。微信
keyspace 的 value 是數據庫的 value,這個 value 能夠是 redis 的,字符串對象,列表對象,哈希表對象,集合對象或者有序對象中的一種。數據結構
因此對於數據的增刪改查,就是對 keyspace 這個大 map 的增刪改查。
當咱們執行:
>redis SET mobile "13800000000"
複製代碼
實際上就是爲 keyspace 增長了一個 key 是包含字符串「mobile」的字符串對象,value 爲包含字符「13800000000」的字符串對象。
看圖:
對於刪改查,沒啥好說的。相似java 的 map 操做,大多數程序員應該都能理解。
須要特別注意的是,再執行對鍵的讀寫操做的時候,Redis 還要作一些額外的維護動做:
Redis 做爲緩存使用最主要的一個特性就是能夠爲鍵值對設置過時時間。就看看 Redis 是若是實現這一個最重要的特性的?
在 Redis 中與過時時間有關的命令
其實這些命令,底層的命令都是由 REXPIREAT 實現的。
在 redisDb 中使用了 dict *expires,來存儲過時時間的。其中 key 指向了 keyspace 中的 key(c 語言中的指針), value 是一個 long long 類型的時間戳,標定這個 key 過時的時間點,單位是毫秒。
若是咱們爲上文的 mobile 增長一個過時時間。
>redis PEXPIREAT mobile 1521469812000
複製代碼
這個時候就會在過時的 字典中增長一個鍵值對。以下圖:
對於過時的判斷邏輯就很簡單:
接下來就須要討論一下過時的鍵的刪除策略。
key的刪除有三種策略:
這三種策略就是對時間和空間有不一樣的傾向。Redis爲了平衡時間和空間,採用了後兩種策略 惰性刪除和定時部分刪除。
惰性刪除比較簡單,不作過多介紹。主要討論一下定時部分刪除。
過時鍵的定時刪除的策略由 expire.c/activeExpireCycle() 函數實現,server.c/serverCron() 定時的調用 activieExpireCycle()
。
activeExpireCycle 的大的操做原則是,若是過時的key比較少,則刪除key的數量也比較保守,若是,過時的鍵多,刪除key的策略就會很激進。
static unsigned int current_db = 0; /* Last DB tested. */
static int timelimit_exit = 0; /* Time limit hit in previous call? */
static long long last_fast_cycle = 0; /* When last fast cycle ran. */
複製代碼
首先三個 static
全局參數分別記錄目前遍歷的 db下標,上一次刪除是不是超時退出的,上一次快速操做是何時進行的。
計算 timelimit = 1000000*ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC/server.hz/100;
能夠理解爲 25% 的 cpu 時間。
若是 db 中 expire 的大小爲0 不操做
expire 佔總 key 小於 1% 不操做
num = dictSize(db->expires);num 是 expire 使用的key的數量。
slots = dictSlots(db->expires); slots 是 expire 字典的尺寸大小。
已使用的key(num) 大於 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 則設置爲 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP。也就是說每次只檢查 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 個鍵。
隨機獲取帶過時的 key。計算是否過時,若是過時就刪除。
而後各類統計,包括刪除鍵的次數,平均過時時間。
每遍歷十六次,計算操做時間,若是超過 timelimit 結束返回。
若是刪除的過時鍵大於 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 的 1\4 就跳出循環,結束。
步驟比較複雜,總結一下:(這裏都是以默認配置描述)
這篇文章主要解釋了 Redis 的數據庫是怎麼實現的,同時介紹了 Redis 處理過時鍵的邏輯。看 Redis 的代碼越多愈加現,實際上 Redis 一直在作的一件事情就是平衡,一直在平衡程序的空間和時間。其實平時的業務設計,就是在宏觀上平衡,平衡系統的時間和空間。因此我想說的是,看源碼是讓咱們從微觀學習系統架構,是每一位架構師的必經之路。你們加油。
我以前的三篇關於 Redis 的基礎數據結構連接地址,歡迎你們閱讀。
Redis 的基礎數據結構(二) 整數集合、跳躍表、壓縮列表
歡迎關注個人微信公衆號: