文章目錄
- 對象
- REDIS_STRING (字符串)
- REDIS_LIST 列表
- REDIS_SET (集合)
- REDIS_ZSET (有序集合)
- REDIS_HASH (hash表)
- int refcount(引用計數器)
- unsigned lru:REDIS_LRU_BITS
對象
對于 Redis 來說使用了 redisObject 來對所有的對象進行了封裝:
typedef struct redisObject {// 對象類型unsigned type:4;// 編碼unsigned encoding:4;// 對象最后一次被訪問的時間unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */// 引用計數int refcount;// 指向實際值的指針void *ptr;} robj;
我們先關注兩個參數
type 和 encoding :
/* Object types */
// 對象類型
#define REDIS_STRING 0
#define REDIS_LIST 1
#define REDIS_SET 2
#define REDIS_ZSET 3
#define REDIS_HASH 4
/* Objects encoding. Some kind of objects like Strings and Hashes can be* internally represented in multiple ways. The 'encoding' field of the object* is set to one of this fields for this object. */
// 對象編碼
#define REDIS_ENCODING_RAW 0 /* Raw representation */
#define REDIS_ENCODING_INT 1 /* Encoded as integer */
#define REDIS_ENCODING_HT 2 /* Encoded as hash table */
#define REDIS_ENCODING_ZIPMAP 3 /* Encoded as zipmap */
#define REDIS_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */
#define REDIS_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define REDIS_ENCODING_INTSET 6 /* E dncoded as intset */
#define REDIS_ENCODING_SKIPLIST 7 /* Encoded as skiplist */
#define REDIS_ENCODING_EMBSTR 8 /* Embedded sds string encoding */
所以通過這段代碼我們可以知道 Redis 支持的數據類型如下:
type 類型
REDIS_STRING 字符串
REDIS_LIST 列表
REDIS_SET 集合
REDIS_ZSET 有序集合
REDIS_HASH 哈希表
Redis 的 Object 通過 ptr 指向具體的底層數據。Redis 的底層數據:
編碼 類型
REDIS_ENCODING_RAW SDS 實現的動態字符串對象
REDIS_ENCODING_INT 整數實現的動態字符串對象
REDIS_ENCODING_HT 字典實現的 hash 對象
REDIS_ENCODING_ZIPMAP 壓縮map實現對對象,(3.0)版本未使用
REDIS_ENCODING_LINKEDLIST 雙向鏈表實現的對象
REDIS_ENCODING_ZIPLIST 壓縮列表實現的對象
REDIS_ENCODING_INTSET 整數集合實現的對象
REDIS_ENCODING_SKIPLIST 跳躍表實現的對象
REDIS_ENCODING_EMBSTR 使用 embstr 實現的動態字符串的對象
PS:下文會解釋 RAW 和 EMBSTR 的區別。
我就按照類型的順序看看 Redis 是怎么利用底層的數據結構實現不同的對象類型的。
REDIS_STRING (字符串)
Redis 的字符串 String,主要由 int、raw 和 emstr 底層數據實現的。 Redis 遵循以下的原則來決定使用底層數據結構的使用。
- 如果數據是可以用 long 表示的整數,那就直接使用將ptr 的類型設置為long。將RedisObject 的 encoding 設置為 REDIS_ENCODING_INT。
- 如果是一個字符串,那就需要考察字符串的字節數。如果字節數小于 39 就是使用 emstr,encoding 就使用 REDIS_ENCODING_EMBSTR,底層依然是我們之前介紹的 SDS 。
- 如果字符串的長度超過 39 那就使用 raw,encoding 就是 REDIS_ENCODING_RAW。
問題來了:
- 為什么是 39 個字符?
我們所String對象是由一個 RedisObject 和 sdshdr 組成的。所以我們如下公式在在64位的系統中,一個 emstr 最大占用 64bite。
RedisObject(16b) + sds header(8b) + emstr + “\0”(1b) <= 64
簡單的 四則運算 emstr <= 39。 - 一直都是 39 么?
在 3.2 的版本的時候,作者對 sdshdr 做了修改,從 39 改成了 44。為什么?
之前我們說過一個 sdshdr 包含三個參數,len、free 還有 buf,在3.2之前 len 和 free 的數據類型都是 unsigned int。 這個就是為什么上面的公式 sds header 是 8個字節了。新版本的 sdshdr 變成了 sdshdr8, sdshdr16 和 sdshdr32還有 sdshdr64。優化的地方就在于如果 buf 小,使用更小位數的數據類型來描述 len 和 free 減少他們占用的內存,同時增加了一個char flags。emstr使用了最小的 sdshdr8。 這個時候 sds header 就變成了(len(1b) + free(1b) + flags(1b)) 3個字節, 比之前的實現少了5個字節。 所以新版本的 emstr 的最大字節變成了 44。 還是那句話 Redis 對內存真是 “斤斤計較” - SDS 是動態的為什么要區分 emstr 和 raw?
區別在于生產 raw 的時候,會有兩步操作,分別產生 redisObject 和 sdshdr。而 emstr 一次成型,同時生成 redisObject 和 sdshdr 。就是為了高效。同時注意 emstr 是不可變的。 - 他們之間是什么關系?
如果不能用 long 表示的數據,double 也是使用 raw 或者 emstr 來保存的。
按照 Redis 的套路這三個底層數據在條件滿足的是是會發生裝換的。REDIS_ENCODING_INT 的數據如果不是整數了,那就會變成 raw 或者 emstr。emstr 發生了變化就會變成 raw。
REDIS_LIST 列表
Reids 的列表,底層是一個 ziplist 或者 linkedlist。
當列表對象保存的字符串元素的長度都小于64字節。
保存的元素數量小于512個。
兩個條件都滿足使用ziplist編碼,兩個條件任意一個不滿足時,ziplist會變為linkedlist。
3.2 以后使用 quicklist 保存。這個數據結構之前沒有講解過。
實際上 quicklist 是 ziplist 和雙向鏈表結合的產物。我們這樣理解,每個雙向鏈表的節點上是一個ziplist。之所以這么設計,應該是空間和時間之間的取舍或者一個折中的方案。 具體的實現我會在以后的文章里面具體分析。
REDIS_SET (集合)
Redis 的集合底層是一個 intset 或者 一個字典(hashtable)。
這個比較容易理解:
當集合都是整數且不超過512個的時候,就使用intset。
剩下都是用字典。
使用字典的時候,字典的每一個 key 就是集合的一個元素,對應的 value 就是一個 null。
REDIS_ZSET (有序集合)
Redis 的有序集合使用 ziplist 或者 skiplist 實現的。
元素小于 128 個
每個元素長度 小于 64 字節。
同時滿足以上條件使用ziplist,否則使用skiplist。
對于 ziplist 的實現,redis 使用相鄰的兩個 entity 分別保存對象以及對象的排序因子。這樣對于插入和查詢的復雜度都是 O(n) 的。直接看圖:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-oaNn00zx-1573628572107)(media/15663772797131/15663782632938.jpg)]
元素開發工程師,排序的因子就是月薪。(好吧php是世界上最好的語言)。
對于skiplist 的實現:
typedef struct zset{zskiplist *zsl;dict *dict}zset;
skiplist 的有序鏈表的實現不只是只有一個 skiplist ,還有一個字典存儲對象的key 和 排序因子的映射,這個是為了保證按照key 查詢的時候時間負責度為 O(1)。同時有序性依賴 skiplist 維護。大家可以看我之前的教程。所以直接看圖:
REDIS_HASH (hash表)
Redis 的 hash 表 使用 ziplist 和 字典 實現的。
鍵值對的鍵和值都小于 64 個字節
鍵值對的數量小于 512。
都滿足的時候使用 ziplist,否則使用字典。
ziplist 的實現類似,類似 zset 的實現。兩個entity成對出現。一個存儲key,另一個存儲 velue。
ziplist
還是可以使用上面使用過的圖。這個時候 entity 不用排序。key 是職位名稱,velue 是對應的月薪。(好吧php還是世界上最好的語言)。與zset實現的區別就是查詢是 O(n) 的,插入直接往tail后面插入就行時間復雜度O(1)。
使用字典實現一個 hash表。好像沒有什么可以多說的。
int refcount(引用計數器)
這個參數是引用計數。Redis 自己管理內存,所以就使用了最簡單的內存管理方式–引用計數。
創建對象的時候計數器為1
每被一個地方引用,計數器加一
每被取消引用,計數器減一
計數器為0的時候,就說明沒有地方需要這個對象了。內存就會被 Redis 回收。
unsigned lru:REDIS_LRU_BITS
這個參數記錄了對象的最后一次活躍時間。
如果 Redis 開啟了淘汰策略,且淘汰的方式是 LRU 的時候,這個參數就派上了用場。Redis 會優先回收 lru 最久的對象。