Redis源碼解析3

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的操作進行講解

Redis源碼解析3

?

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

?

Redis源碼解析3

?

?

?

引用計數 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. 寫時復制:變量改變會引發變量分離,導致復制一份新變量

? 版權聲明
THE END
喜歡就支持一下吧
點贊14 分享