Redis設計與實現 第 9 章 資料庫
第 9 章 資料庫
9.1 伺服器中的資料庫
redis.h/redisServer 結構的 db 陣列中,每個元素都是 redis.h/redisDb 結構,代表一個數據庫
初始化伺服器時會根據伺服器狀態的 dbnum 屬性來決定建立多少個數據庫
dbnum 由伺服器配置的 database 選項決定,預設為 16,所以會預設建立 16 個數據庫
9.2 切換資料庫
Redis 客戶端的目標資料庫為 0 號資料庫,可以通過 SELECT 命令來切換目標資料庫
127.0.0.1:6379> set 32 "hello you" OK 127.0.0.1:6379> get 32 "hello you" 127.0.0.1:6379> select 2 OK 127.0.0.1:6379[2]> get 32 (nil) 127.0.0.1:6379[2]> set 32 "you hello" OK 127.0.0.1:6379[2]> get 32 "you hello"
在伺服器內部,客戶端狀態的 redisClient 結構的 db 屬性記錄了客戶端當前的目標資料,是一個指向 redisDb 結構的指標
redisClint.db 指標指向 redisServer.db 陣列的一個元素,被指向的元素則是客戶端的目標資料庫
9.3 資料庫鍵空間
redis 是鍵值對資料庫伺服器,每個資料庫都是 redis.h/redisDb 結構表示,dict 字典儲存了資料庫中的鍵值對,這個字典為鍵空間
鍵空間與資料庫是直接對應的
- 鍵空間的鍵是資料庫的鍵,也是一個字串物件
- 鍵空間的值是資料庫的值,每個值可以是任意一種 Redis 物件
所有針對資料庫的操作,實際上都是通過對鍵空間字典進行操作實現的
新增新鍵
新增新鍵值對到資料庫,實際上就是將新鍵值對新增到鍵空間中,鍵為字串物件,值為任意一種 Redis 物件
刪除鍵、更新鍵、對鍵取值
同理新增新鍵
其他命令
FLUSHDB、RANDOMKEY、DESIZE 同樣也是通過對鍵空間進行操作的
9.3.6
不僅只有指定的讀寫操作,還有一些額外的維護操作,包括
-
伺服器會根據鍵是否存在來更新鍵空間命中次數和不命中次數,使用 INFO status 命令的 keyspace_hits 和 keyspace_misses 屬性檢視
-
伺服器會更新鍵的 lru (最後一次使用)時間,可以計算鍵的閒置時間, OBJECT idletime key 檢視
-
讀取一個鍵時發現已經過期了,則會先刪除再進行餘下的操作
-
如果客戶端使用 watch 命令監視了某個鍵,則伺服器修改後,會將其標記為髒,從而讓事務程式注意到鍵已經修改
-
每修改一個鍵後,髒鍵計數器的值 + 1,會觸發伺服器的持久化以及複製操作
-
如果伺服器開啟資料庫通知功能,在對鍵進行修改之後,伺服器會按配置傳送相應的資料庫通知
9.4 設定鍵的生存時間或過期時間
9.4.1 設定過期時間
所有命令底層還是由 PEXPIRE 命令轉換而來的
9.4.2 儲存過期時間
redisDb 結構的 expires 字典儲存了資料庫中所有鍵的過期時間,即過期字典:
-
鍵為指標,指向鍵空間的某個鍵物件
-
值為 long long 型別,儲存了鍵指向的鍵空間鍵物件的過期時間,毫秒精度的 UNIX 時間戳
鍵空間和過期字典的鍵都指向同一個,此處為明顯展示而列舉兩個
9.4.3 移除過期命令
PERSTST 命令移除鍵的過期時間:
在過期字典中查詢給定的鍵,並解除鍵和值在過期字典中的關聯
9.4.4 計算並返回剩餘生存時間
TTL 秒為單位,PTTL 毫秒為單位,通過計算鍵的過期時間與當前時間之差
9.4.5 過期鍵的判定
- 檢查給定鍵是否存在於過期字典;存在則取得過期時間
- 檢查當前 UNIX 時間戳是否大於鍵的過期時間;是則鍵已經過期,否則未過期
或者使用 TTL 或 PTTL 命令,但實際中是使用 is_expired 函式
9.5 過期鍵刪除策略
- 定時刪除:主動
- 設定過期時間的同時建立定時器,過期時間到達立即刪除
- 惰性刪除:被動
- 當從鍵空間獲取鍵時,檢查是否過期,過期則刪除,沒有則返回
- 定期刪除:主動
- 每隔一段時間,對資料庫檢查,刪除多少,檢查多少資料庫則由演算法決定
9.5.1 定時刪除
-
記憶體友好
-
CPU 最不友好
-
需要使用 Redis 的時間事件,時間事件實現方式為無序連結串列,則查詢一個事件的時間複雜度為 O(N)
9.5.2 惰性刪除
-
對 CPU 最友好
-
對記憶體最不友好
-
浪費大量記憶體
9.5.3 定期刪除
每隔一段時間執行一次刪除過期鍵操作,並通過限制刪除操作執行的時長和頻率來減少刪除操作對 CPU 時間的影響,有效減少了過期鍵的記憶體浪費
時長和頻率難以確定:
-
執行頻繁、時間長,會退化成定時刪除
-
執行少、時間短,退化為惰性刪除
9.6 Redis 的過期鍵刪除策略
9.6.1 惰性刪除策略的實現
由 db.c/expireIfNeeded 函式實現,所有讀寫資料庫的 Redis 命令在執行之前都會呼叫此函式對輸入鍵進行檢查
- 過期,此函式將鍵從資料庫中刪除
- 未過期,此函式不做動作
命令執行時也需要按照兩種情況執行
9.6.2 定期刪除策略的實現
由 redis.c/activeExpireCycle 函式實現,Redis 週期性操作 redis.c/serverCron 函式執行時,activeExpireCycle 函式會呼叫,在規定時間內,分多次遍歷各個資料庫,從 expires 字典中隨機檢查一部分鍵的過期時間,並刪除過期鍵
activeExpireCycle:
- 執行時,從一定數量的資料庫取出一定數量的隨機鍵進行檢查,刪除過期鍵
- current_db 會記錄當前函式檢查的進度,並在下一次呼叫此函式時按上一次進度進行處理
- 函式不斷執行,所有資料庫都會被檢查一遍,current_db 置為 0 ,開始新一輪的檢查
9.7 AOF、ROB 和複製功能對過期鍵的處理
9.7.1 生成 RDB 檔案
在執行 save 命令或者 bgsave 命令建立新的 RDB 檔案時,對鍵會進行檢查,已經過期的鍵不會被儲存到新建立的 RDB 檔案中
9.7.2 載入 RDB 檔案
對 RDB 檔案進行載入:
-
以主伺服器模式執行,在載入時會對檔案中儲存的鍵進行檢查,未過期的鍵會被載入到資料庫中,已過期的會被忽略
-
以從伺服器模式執行,檔案中所有鍵都會被載入資料庫,但是主從伺服器進行資料同步時,從伺服器的資料庫就會被清空
9.7.3 AOF 檔案寫入
伺服器以 AOF 持久化模式執行時,如果某個鍵過期,但沒有被惰性刪除或者定期刪除,AOF 檔案不會因這個過期鍵產生任何影響
當過期鍵被刪除後,會向 AOF 檔案追加一條 DEL 命令顯示記錄該鍵已被刪除
9.7.4 AOF 重寫
類似生成 RDB 檔案,對鍵檢查,已過期的不會被儲存
9.7.5 複製
當伺服器在複製模式下,從伺服器的過期刪除動作由主伺服器控制:
- 主伺服器刪除一個過期鍵,顯示向所有從伺服器傳送一個 DEL 命令,告知從伺服器刪除這個過期鍵
- 從伺服器在執行客戶端傳送的讀命令時,遇到過期鍵也不會將過期鍵刪除,當成未過期鍵處理
- 從伺服器只有在接到豬伺服器發來的 DEL 命令之後才會刪除過期鍵
通過主伺服器控制從伺服器統一刪除過期鍵,來保證主從伺服器的一致性
9.8 資料庫通知
讓客戶端通過訂閱給定的頻道或者模式,來獲知資料庫中鍵的變化,以及命令的執行情況
- 鍵空間通知:
- 某個鍵執行了什麼命令
- 鍵事件通知
- 某個命令被什麼鍵執行了
notify-keyspace-events 決定傳送通知的型別
-
傳送所有型別的鍵空間通知和鍵事件通知:AKE
-
傳送所有型別的鍵空間通知:AK
-
傳送所有型別的鍵事件通知:AE
-
傳送和字串鍵相關的鍵空間通知:K$
-
傳送和列表鍵相關的鍵事件通知:EL
9.8.1 傳送通知
傳送資料庫通知功能由 notify.c/notifyKeyspaceEvent 實現:
void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid);
type 為當前想要傳送的通知的型別,根據這個值來判斷通知是否就是伺服器配置 notify-keyspace-event 選項所選定的通知型別來決定是否傳送通知
event 事件名稱
keys 產生事件的鍵
dbid 產生事件的資料庫號
根據這些引數來構建事件通知的內容、以及接受通知的頻道名
REDIS_NOTIFY_SET:集合鍵通知
REDIS_NOTIFY_GENERIC:通用型別通知
notifyKeyspaceEvent:
- server.notify_keyspace_events 為 notify-keyspace-events 選項設定的值,如果給定的通知型別 type 不是伺服器允許傳送的通知型別,函式直接返回
- 是伺服器允許傳送的通知型別,再檢測是否允許傳送鍵空間通知,允許則構建併發送事件通知
- 最後再檢測是否允許傳送鍵事件通知,允許中午構建併發送事件通知
pubsubPublishMessage 是PUBLISH 命令的實現函式