Everything is Object 數據結構 在redis中,用 robj 結構表示一切數據對象,可以把它看作一種元數據(MetaData) 各種不同的結構化數據,通過該對象進行封裝、傳遞、變換、編碼,而該對象本身卻十分簡單 其類型定義如下: 1 typedef struct redisObject { un
Everything is Object
?
數據結構
在Redis中,用 robj 結構表示一切數據對象,可以把它看作一種元數據(MetaData)
各種不同的結構化數據,通過該對象進行封裝、傳遞、變換、編碼,而該對象本身卻十分簡單
其類型定義如下:
1 typedef struct redisObject { unsigned storage:unsigned encoding:unsigned lru:refcount; } robj;
type字段表示數據類型,有以下幾種定義:
REDIS_STRING ? // 字符串
REDIS_LIST ?// 鏈表
REDIS_SET ?// 集合
REDIS_ZSET ?// 有序集合
REDIS_HASH ?// HASH結構(注意,此處不同于傳統意義上的哈希表(如stl::hash_map),這里的hash僅有字段散列的語義)
REDIS_VMPOINTER ?// VM指針(表示數據處于VM管理之下)
?
encoding字段表示數據編碼方式,有以下幾種定義:
REDIS_ENCODING_RAW ?// 原始編碼,就是一個原始字符串
REDIS_ENCODING_INT ?// INT型編碼,會將數字類型的字符串編碼成該格式
REDIS_ENCODING_HT ?// 哈希表編碼,源代碼中以dict結構來管理
REDIS_ENCODING_ZIPMAP ?// 精簡編碼的hash結構,更省內存
REDIS_ENCODING_LINKEDLIST ?// 雙向鏈表
REDIS_ENCODING_ZIPLIST ?// 精簡編碼的鏈表,更省內存
REDIS_ENCODING_INTSET ?// 精簡編碼的集合,更省內存
REDIS_ENCODING_SKIPLIST ?//?
refcount字段是對象引用計數,每多一次引用,該計數加1;每減少一次引用,該計數減1,若減至0,則釋放該對象內存
?
操作流程
Redis源代碼中,有大量的robj指針在函數間傳遞
下面,以redis處理“set”命令為例,對 robj的操作進行講解
?
1. 在 ProcessInlineBuffer、ProcessMultiBulkBUffer中,對命令緩存進行解析
? ? 通常是以空格為分隔符進行分離,每一個字符塊都編碼為一個獨立的robj對象
? ? 對象存儲在redisClient結構中的argc數組中,提供給后續函數使用
? ? InlineBuffer:對應于Redis單行命令
? ? MultiBulkBuffer:對應于多行命令
2. ProcessCommand對命令進行解析,然后進行函數分發
? ? 處理流程進入具體的命令處理函數
3. setCommand是對應于“set”命令的處理函數
? ? 該函數非常簡單,網站空間,主要起到一個命令接入作用
? ? 它會對obj-val進行一次嘗試性的編碼轉換,在本例中,會嘗試將val對象轉換為一個INT型的對象
? ? 轉換完成后,進入內部共享函數setGenericCommand處理流程
4. setGenericCommand進行實際的“set”操作邏輯處理,即:
? ? 將kv鍵值對,加入到該連接對應的命名空間中(即一個dict結構)
? ? 對應于該dict結構,插入操作的具體語義由一個全局性的 dictType 進行定義:
1 dictType commandTableDictType = { NULL, NULL, dictSdsKeyCompare, dictSdsDestructor, dictRedisObjectDestructor };
? ?根據這個操作定義,執行完比后
? ?obj-key會復制一份原始字符串,將指針加入dict中(復制操作在dbAdd函數中實現)
? ?obj-val直接將指針加入dict中,同時將該對象的refcount加1(加引用操作在setKey函數中實現)
5. 整個處理流程結束后,釋放redisClient中的argc對象數組
? ? 在本例中,導致的結果是:
? ? obj-key引用計數減1,最終值為0,導致對象刪除
? ? obj-val引用計數減1,最終值為1,該對象繼續存在于全局key的dict表中
?
繼續上一條“set”命令,針對更進一步的命令,對redis的對象編碼轉換作個初步了解
在下圖中,主要注意 appendCommand中,由于該命令需要改變 obj-val對象的值,從而導致obj-val從INT編碼狀態解碼到RAW編碼狀態。
解碼時生成了臨時對象 obj-decoded
臨時對象與obj-append合并,將合并后的值賦給obj-val
?
?
?
?
引用計數 php中的變量
通過引用計數管理對象的生存期,是個好主意。通過上面兩張圖,也可以初步看出,redis如何通過引用計數來管理對象的生死
在很多動態類型的語言中,也有類似的做法
比如在PHP中,用以下結構表示變量:
1 struct _zval_struct { zend_uint refcount; zend_uchar type; zend_uchar is_ref; }; 7 typedef struct _zval_struct zval;
其中,value是一個 “zvalue_value” 類型的指針
zvalue_value是一個union類型的結構,將不同類型的數據整合進了同一個結構里
1 typedef union _zvalue_value { dval; { 5 char *val; 6 int len; HashTable *ht; zend_object_value obj; } zvalue_value;
?
可以看到,PHP中的變量有兩種引用計數
1. refcount:直接引用計數,賦值時計數增加
2. is_ref:間接引用計數,賦引用時計數增加
之所以有這兩個值,是因為PHP中的變量有引用的概念,香港虛擬主機,并且涉及到幾個重要原則:
1. 賦值零拷貝:變量賦值時,不會復制一份新的變量,而是直接對zval結構加引用,且引用計數加在refcount變量上
? ? ? ? ? ? ? ? ? ? 但是,自定義類對象的賦值,和普通變量不同,默認是賦引用,導致計數加在is_ref上
? ? ? ? ? ? ? ? ? ? 我認為這是PHP語言設計上很混亂的一個地方
2. 寫時復制:變量改變會引發變量分離,導致復制一份新變量