在Redis 的命令中,用於對鍵key)進行處理的命令佔了很大一部分,而對於鍵所儲存的值的
類型後簡稱“鍵的類型” ),鍵能執行的命令又各不相同。
比如說,LPUSH 和LLEN 只能用於列表鍵,而SADD 和SRANDMEMBER 只能用於集合
鍵,等等。
另外一些命令,比如DEL 、TTL 和TYPE ,可以用於任何類型的鍵,但是,要正確實現這些
命令,必須為不同類型的鍵設定不同的處理方式:比如說,刪除一個列表鍵和刪除一個字串
鍵的操作過程就不太一樣。
以上的描述說明,Redis 必須讓每個鍵都帶有類型資訊,使得程式可以檢查鍵的類型,並為它
選擇合適的處理方式。
另外,在前面介紹各個底層資料結構時有提到,Redis 的每一種資料類型,比如字串、列表、
有序集,它們都擁有不只一種底層實現Redis 內部稱之為編碼,encoding),這說明,每當對
某種資料類型的鍵進行操作時,程式都必鬚根據鍵所採取的編碼,進行不同的操作。
比如說,集合類型就可以由字典和整數集合兩種不同的資料結構實現,但是,當使用者執行
ZADD 命令時,他/她應該不必關心集合使用的是什麼編碼,只要Redis 能按照ZADD 命令的
指示,將新元素添加到集合就可以了。
這說明,操作資料類型的命令除了要對鍵的類型進行檢查之外,還需要根據資料類型的不同編
碼進行多態處理。
為瞭解決以上問題,Redis 構建了自己的類型系統,這個系統的主要功能包括:
redisObject 對象。
基於redisObject 對象的類型檢查。
基於redisObject 對象的顯式多態函數。
對redisObject 進行分配、共用和銷毀的機制。
以下小節將分別介紹類型系統的這幾個方面。
Note: 因為C 並不是物件導向語言,這裡將redisObject 稱呼為對象一是為了講述的方便,
二是希望通過模仿OOP 的常用術語,讓這裡的內容更容易被理解,redisObject 實際上是只
是一個結構類型。
redisObject 資料結構,以及Redis 的資料類型
redisObject 是Redis 類型系統的核心,資料庫中的每個鍵、值,以及Redis 本身處理的參數,
都表示為這種資料類型。
redisObject 的定義位於redis.h :
/** Redis 對象*/typedef struct redisObject {// 類型unsigned type:4;// 對齊位unsigned notused:2;// 編碼方式unsigned encoding:4;// LRU 時間相對於server.lruclock)unsigned lru:22;// 引用計數int refcount;// 指向對象的值void *ptr;} robj;
type 、encoding 和ptr 是最重要的三個屬性。
type 記錄了對象所儲存的值的類型,它的值可能是以下常量的其中一個定義位於redis.h):
/** 物件類型*/#define REDIS_STRING 0 // 字串#define REDIS_LIST 1 // 列表#define REDIS_SET 2 // 集合#define REDIS_ZSET 3 // 有序集#define REDIS_HASH 4 // 雜湊表
encoding 記錄了對象所儲存的值的編碼,它的值可能是以下常量的其中一個定義位於
redis.h):
/** 對象編碼*/#define REDIS_ENCODING_RAW 0 // 編碼為字串#define REDIS_ENCODING_INT 1 // 編碼為整數#define REDIS_ENCODING_HT 2 // 編碼為雜湊表#define REDIS_ENCODING_ZIPMAP 3 // 編碼為zipmap#define REDIS_ENCODING_LINKEDLIST 4 // 編碼為雙端鏈表#define REDIS_ENCODING_ZIPLIST 5 // 編碼為壓縮列表#define REDIS_ENCODING_INTSET 6 // 編碼為整數集合#define REDIS_ENCODING_SKIPLIST 7 // 編碼為跳躍表
ptr 是一個指標,指向實際儲存值的資料結構,這個資料結構由type 屬性和encoding 屬性決
定。
舉個例子, 如果一個redisObject 的type 屬性為REDIS_LIST , encoding 屬性為
REDIS_ENCODING_LINKEDLIST ,那麼這個對象就是一個Redis 列表,它的值儲存在一個雙
端鏈表內,而ptr 指標就指向這個雙端鏈表;
另一方面, 如果一個redisObject 的type 屬性為REDIS_HASH , encoding 屬性為
REDIS_ENCODING_ZIPMAP ,那麼這個對象就是一個Redis 雜湊表,它的值儲存在一個zipmap
裡,而ptr 指標就指向這個zipmap ;諸如此類。
展示了redisObject 、Redis 所有資料類型、以及Redis 所有編碼方式底層實現)三者
之間的關係:
650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/140211/22540KU6-0.jpg" title="QQ20140210214451.png" alt="wKiom1L42DPwvSEaAAF7ivDMBUg512.jpg" />
這個圖展示了Redis 各種資料類型,以及它們的編碼方式。
Note: REDIS_ENCODING_ZIPMAP 沒有出現在圖中,因為從Redis 2.6 開始,它不再是任何數
據類型的底層結構。
命令的類型檢查和多態
有了redisObject 結構的存在,在執行處理資料類型的命令時,進行類型檢查和對編碼進行多
態操作就簡單得多了。
當執行一個處理資料類型的命令時,Redis 執行以下步驟:
1. 根據給定key ,在資料庫字典中尋找和它像對應的redisObject ,如果沒找到,就返回
NULL 。
2. 檢查redisObject 的type 屬性和執行命令所需的類型是否相符,如果不相符,返回類
型錯誤。
3. 根據redisObject 的encoding 屬性所指定的編碼,選擇合適的操作函數來處理底層的
資料結構。
4. 返回資料結構的操作結果作為命令的傳回值。
作為例子,以下展示了對鍵key 執行LPOP 命令的完整過程:
650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/140211/22540G3W-1.jpg" title="QQ20140210214451.png" alt="wKiom1L42Imy4-ZLAAGu7U_h9E8702.jpg" />
對象共用
有一些對象在Redis 中非常常見,比如命令的傳回值OK 、ERROR 、WRONGTYPE 等字元,另外,
一些小範圍的整數,比如個位、十位、百位的整數都非常常見。
為了利用這種常見情況,Redis 在內部使用了一個Flyweight 模式:通過預分配一些常見的值
對象,並在多個資料結構之間共用這些對象,程式避免了重複分配的麻煩,也節約了一些CPU
時間。
Redis 預分配的值對象有如下這些:
各種命令的傳回值,比如執行成功時返回的OK ,執行錯誤時返回的ERROR ,類型錯誤時
返回的WRONGTYPE ,命令入隊事務時返回的QUEUED ,等等。
包括0 在內, 小於redis.h/REDIS_SHARED_INTEGERS 的所有整數
REDIS_SHARED_INTEGERS 的預設值為10000)
因為命令的回複值直接返回給用戶端,所以它們的值無須進行共用;另一方面,如果某個命令
的輸入值是一個小於REDIS_SHARED_INTEGERS 的整數對象,那麼當這個對象要被儲存進資料
庫時,Redis 就會釋放原來的值,並將值的指標指向共用對象。
作為例子,展示了三個列表,它們都帶有指向共用對象數組中某個值對象的指標:
650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/140211/22540I0I-2.jpg" title="QQ20140210214451.png" alt="wKioL1L42JjRFnk3AAB-yp0St_A115.jpg" />
三個列表的值分別為:
列表A :[20130101, 300, 10086] ,
列表B :[81, 12345678910, 999] ,
列表C :[100, 0, -25, 123] 。
Note: 共用對象只能被帶指標的資料結構使用。
需要提醒的一點是,共用對象只能被字典和雙端鏈表這類能帶有指標的資料結構使用。
像整數集合和壓縮列表這些只能儲存字串、整數等字面值的記憶體資料結構,就不能使用共用
對象。
引用計數以及對象的銷毀
當將redisObject 用作資料庫的鍵或者值,而不是用來儲存參數時,對象的生命期是非常長
的,因為C 語言本身沒有自動釋放記憶體的相關機制,如果只依靠程式員的記憶來對對象進行追
蹤和銷毀,基本是不太可能的。
另一方面,正如前面提到的,一個共用對象可能被多個資料結構所引用,這時像是“這個對象被
引用了多少次? ”之類的問題就會出現。
為瞭解決以上兩個問題,Redis 的對象系統使用了引用計數技術來負責維持和銷毀對象,它的
運作機制如下:
每個redisObject 結構都帶有一個refcount 屬性,指示這個對象被引用了多少次。
當新建立一個對象時,它的refcount 屬性被設定為1 。
當對一個對象進行共用時,Redis 將這個對象的refcount 增一。
當使用完一個對象之後,或者取消對共用對象的引用之後,程式將對象的refcount 減
一。
當對象的refcount 降至0 時,這個redisObject 結構,以及它所引用的資料結構的內
存,都會被釋放。
小結
Redis 使用自己實現的對象機制來實作類別型判斷、命令多態和基於引用計數的記憶體回收。
一種Redis 類型的鍵可以有多種底層實現。
Redis 會預分配一些常用的資料對象,並通過共用這些對象來減少記憶體佔用,和避免頻繁
地為小對象分配記憶體。
本文出自 “phper-每天一點點~” 部落格,請務必保留此出處http://janephp.blog.51cto.com/4439680/1357861