Redis設計與實現:第九章:數據庫

參考:[Redis設計與實現]


1、數據庫 -數據結構與實現

1、數據結構

       Redis服務器所有的數據庫都保存在 redis.h/redisServer結構中的db數組中,也就是說對於Redis來說不同的數據庫其實是對應的程序中的不同的數組。
       db數組的每個項都是 redis.h/redisDb結構,並且redis通過redisServer中的dbNum屬性來決定初始化的數據庫數量,這個屬性是通過database選項進行配置,默認是16.

redisServer結構如下

	struct redisServer {
		redisDb *db,//服務器數據庫的數組
		dbNum int,	//服務器上數據庫數量的配置參數
	}

具體的數據庫結構關係如下:
在這裏插入圖片描述

2、客戶端訪問與數據庫切換

       默認情況下,Redis客戶端的目標數據庫是0號數據庫。客戶端可以通過SELECT命令來實現目標數據庫切換
       在Redis內存,通過redisClient的數據結構來記錄客戶端當前的目標數據庫等信息。主要是通過redisDb的指針來存取當前目標數據庫。

redisClient結構如下:

	typedef struct redisClient{
		redisDb *db //記錄客戶端當前的目標數據庫
		//...
	}

       通過SELECT命令改變目標數據庫其實就是修改了,redisDb的指針指向,具體的結構關係如下:
在這裏插入圖片描述

3、單個數據庫結構實現

       服務器中的每個數據庫都是由 redis.h/redisDb的數據結構表示和實現的,而redisDb又是通過dict數據字典來保存了數據庫中的所有鍵值對,這個字典就是所謂的鍵空間

redisDb數據結構:

	typedef struct redisDb{
		//...
		dict *dict,//數據庫鍵空間,保存數據庫中的所有鍵值對信息	
	}

鍵空間的特性:

  • 鍵空間的鍵就是數據庫的鍵,每一個鍵都是一個字符串對象(不可變和唯一性)
  • 鍵空間的值就是數據庫的值,值可以是字符串對象、列表對象、哈希表對象、集合對象和有序集合對象中的一種

       具體看一下數據庫鍵空間的例子,對鍵空間的增刪改查與常規數據庫類似,略過不表:
在這裏插入圖片描述

4、讀寫鍵空間觸發額外操作

       redis對鍵空間進行讀取,會觸發額外的維護操作,包括如下:

  • 讀寫之後,服務器會根據鍵是否存在來更新服務器的鍵空間命中次數或者鍵空間的不命中次數
  • 讀寫之後,更新鍵的LRU時間,這個屬性可用於計算鍵的空閒時間。
  • 如果數據庫讀取鍵的時候發現鍵已經過期,會先刪除這個鍵,然後纔會執行餘下操作。
  • 如果使用WATCH命令監視某個鍵,服務器會標記被修改後的鍵爲dirty。
  • 服務器每次修改一個鍵都會對髒鍵計數器的值加一,這個計數器會觸發服務器的持久化以及複製操作。
  • 如果開啓了數據庫通知功能,則在修改之後會觸發相應的數據庫通知。

2、鍵過期判定與實現

1、過期設置命令

  • EXPIRE :將key的生存時間設置爲ttl
  • PEXPIRE :將key的生存時間設置爲ttl 毫秒
  • EXPIREAT :將過期時間設置爲timestamp所指定的秒數時間戳
  • PEXPIREAT :將過期時間設置爲timestamp所指定的毫秒數時間戳
  • TTL :計算key剩餘的生存時間秒數
  • PTTL :計算key剩餘的生存時間毫秒數
  • PERSIST :移除key對應的過期時間
           

2、過期時間的結構化存儲

redis通過redisDb結構的expires字典保存數據庫所有鍵的過期時間

  • 過期字典的鍵是一個指針(指向對應的某個鍵對象)
  • 過期字典的值是一個long long 類型的正數,保存了對應數據庫鍵的過期時間(毫秒精度的UNIX時間戳)

expire字典結構:

	typedef struct redisDb{
		dict *expires;//過期數據字典,保存鍵的過期時間
	}

結構化關係:
在這裏插入圖片描述


3、刪除策略

1、常見刪除策略對比

1、定時刪除

優點:

  • 這種策略對內存是最友好的,通過定時器實現,定時刪除可以保證過期鍵儘快刪除,並釋放過期鍵所佔用的內存空間

缺點:

  • 對於CPU時間(CPU輪轉執行)是最不友好的,在過期鍵比較多的情況下,刪除過期鍵會佔用相當一部分CPU時間,在CPU時間緊張的情況下,CPU時間被用在刪除與當前任務無關的過期鍵上,對服務器的響應時間和吞吐量會造成影響
  • 創建一個定時任務需要用到redis服務器中的時間事件,當前的時間事件的實現方式是無序鏈表,查找的複雜度爲O(N),並不能高效的處理大量的時間事件。

       

2、惰性刪除

優點:

  • 對CPU時間是最友好的,程序只會對需要被取出的鍵進行是否過期的判斷,沒有在無關的過期鍵上面花費任何CPU時間

缺點:

  • 對內存最不友好,如果一個鍵過期了,但是沒有被調用,那麼它會一直停留在內存,佔用的內存空間不會被釋放。服務器不會自動去釋放它們,對於Redis這種運行非常依賴內存的服務器來說,非常不友好。

       

3、定期刪除

定期刪除是前兩種方式的整合和折中。採用定期刪除必須根據情況設置刪除操作的執行時長和頻率,不然就可能退化成上面兩種其中任意一種刪除策略,缺點暴露
       

2、Redis使用的刪除策略

Redis的實現採用 惰性刪除定期刪除兩種刪除策略。配合這兩種刪除策略,服務器可以很好的在合理使用CPU時間避免內存浪費之間取得平衡

1、惰性刪除的實現

       惰性刪除策略是通過 db.c/expireIfNeeded函數實現,所有讀寫數據庫的命令在執行之前都會先調用這個方法進行檢查。如果鍵過期則進行刪除,否則不做具體操作。
       具體的實現分爲兩種情況:1、鍵不存在(前一次操作被刪除)2、鍵存在。具體的操作過程如下(以GET舉例):
在這裏插入圖片描述

2、定期刪除的實現

       定期刪除通過redi.c/activeExpireCycle函數實現,當redis服務器週期性操作redis.h/serverCron函數執行的時候會調用定期刪除函數,它會在規定時間內,分多次遍歷服務器的各個數據庫,從expire字典中隨機檢查一部分數量鍵的過期時間,刪除其中的過期鍵。

對應的函數屬性信息:

	{
		DEFAULT_DB_NUMBERS = 16 //默認每次需要檢查的數據庫數量
		DEFAULT_KEY_NUMBERS = 20 //默認每次檢查的key的數量
		current_db = 1	//全局變量,檢查進度,因爲是分多次進行檢查,所以記錄的是當前執行的數據庫,下一次操作就是從下一個數據庫開始,檢查結束,變量直接置爲0
	}

操作過程:

  • 根據要檢查數據庫數量,進行遍歷,取出檢查的key數量個鍵,進行判斷expire字典過期時間,如果過期則進行刪除
  • 本次檢查保存檢查進度信息,等待本次的下一波檢查開始,從檢查進度對應的下一個數據開始檢查,執行上一步相應操作
  • 一次檢查分爲幾輪,本次檢查結束之後,把檢查進度信息置爲0,等待下一次被觸發。
           

3、RDB複製對過期鍵的處理

1、生成RDB文件

       在執行SAVE或者BGSAVE命令創建一個新的RDB文件時,程序會對數據庫中的鍵進行檢查,已過期的鍵bu8hui保存到新創建的RDB文件中。

       因此,數據庫中包含過期鍵不會對生成新的RDB文件造成影響。

2、載入RDB文件

       在啓動Redis服務器時,如果開啓了RDB功能,服務器對RDB文件進行載入有兩種情況:

  • 如果以主服務器模式運行,載入RDB文件時,程序會對鍵進行檢查,未過期的鍵會被載入到數據庫,過期的會被忽略
  • 如果是以從服務器模式運行,載入RDB文件時,程序會把所有鍵都進行載入,當主服務器進行數據同步的時候,從服務器的數據庫會被清空,所以過期鍵對載入RDB文件不會造成影響。

       

4、AOF賦值對過期鍵的處理

1、AOF文件寫入

       當程序以AOF持久化模式運行時,如果某個鍵已經過期,但是還沒有被刪除策略刪除,對AOF文件不會造成任何影響。因爲AOF文件寫入對於過期的key是這麼操作的:

  • 從數據庫中刪除過期鍵
  • 追加一條 DEL 命令到AOF文件
  • 向執行讀取的命令返回空回覆

       

2、AOF文件重寫

       和AOF文件寫入類型,不會對過期的key進行寫入,所以過期的key對AOF文件重寫不會造成任何影響。

       

3、複製

       當服務器運行在複製模式下,從服務器的過期刪除動作主要由主服務器控制

  • 主服務器刪除一個過期鍵之後,會顯示的想所有從服務器發送一個DEL命令,告訴服務器刪除這個過期鍵
  • 從服務在執行客戶端的命令,即使碰到過期鍵也不會對其進行刪除,而是想處理未過期鍵一樣(通過主服務器統一刪除過期鍵,可以保證主從服務器的一致性
  • 從服務器收到DEL命令之後,纔會刪除過期鍵

4、數據庫訂閱與通知

1、功能描述

       該功能可以讓客戶端通過訂閱給定的頻道或者模式,來獲知數據庫中鍵的變化,以及數據庫中命令的執行情況。

       Redis客戶端訂閱命令:SUBSCRIBE _ _keyspace@【數據庫編號】_ _ :【命令或者key名】,比如說訂閱0號數據庫的message這個key的命令就是:SUBSCRIBE _ _keyspace@0_ _:message

       服務器通過配置**notify-keyspace-events**選項決定發送通知的類型:

在這裏插入圖片描述

2、功能實現

       發送通知功能由notify.c/notifyKeysSpaceEvent函數實現:

	/**
	 * type:當前想要發送的通知類型
	 * event:時間的名稱
	 * keys:產生事件的鍵
	 * dbid:數據庫號碼
	 */
	void notifyKeysSpaceEvent(int type,char *Event,robj *key,int dbid)

       當一個redis命令需要發送數據庫通知的時候會調用該函數,實際實現步驟:

  • 判斷操作類型是否是notify-keyspace-events配置的允許發送的內容,如果不是則直接返回
  • 判斷服務器是否允許發送鍵空間通知,如果允許則會發送事件通知

       訂閱事件其實本質就是通過PUBLISH命令來執行的

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