閱讀32 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Redis數據結構

dict

字典(dict)是redis裏最核心的數據結構,正如其全稱Remote Dictionary Service所說,redis其實就是一個字典服務,字典以key、value的形式呈現給用戶,key是簡單的字符串,而value可以是各種數據結構,比如字符串(string)、鏈表(list)、集合(set)、排序集合(zset)、哈希表(hash)等。

redis裏dict的實現也比較簡單,通過鏈表來解決哈希衝突,值得一提的是dict的動態擴展能力,當dict裏元素不斷增加時,其會擴大哈希桶數,然後對現有元素進行rehash,重新分布到對應的桶上,但dict的rehash並不是一次性完成的,這樣會導致當dict裏元素較多時,rehash耗時較長,導致服務阻塞。

typedef struct dict {
    dictType *type;
    void *privdata;
    dictht ht[2];
    int rehashidx; /* rehashing not in progress if rehashidx == -1 */
    int iterators; /* number of iterators currently running */
} dict;

dict創建時,會初始化ht[0],插入和訪問都通過ht[0]來完成,當需要rehash時,創建一個新的dict ht[1],並以桶為單位將ht[0]裏的元素逐步遷移到新的ht[1]上(遷移會在訪問dict時觸發,也可以配置redis主動在後台周期性的遷移)。所以當訪問dict時,如果正在進行rehash,就必須先後查詢ht[0], ht[1],因為被訪問的元素可能已經遷到新的ht[1]上;當所有的元素都遷移到ht[1]後,用ht[1]代替ht[0],並釋放掉原來的ht[0]。

dict是通用的數據結構,采用void*來存儲任意類型的對象,由於需求多變,比如有的場景需要在插入元素時重新分配內存,而有的場景直接存儲指針,有的場景需要定製對象比較的方式,redis為解決這個問題,定義了一係列的接口,用戶可以根據需求在創建dict時指定。

typedef struct dictType {
    unsigned int (*hashFunction)(const void *key);      // key的hash方法
    void *(*keyDup)(void *privdata, const void *key);   // 插入元素時,複製key對象的方法
    void *(*valDup)(void *privdata, const void *obj);   // 插入元素時,複製value對象的方法
    int (*keyCompare)(void *privdata, const void *key1, const void *key2); // key比較的方法
    void (*keyDestructor)(void *privdata, void *key);   // 刪除元素時,釋放key的方法
    void (*valDestructor)(void *privdata, void *obj);   // 刪除元素時,釋放value的方法
} dictType;

string

redis所有的key都是string類型,redis的是基於c string的一個封裝,在字符串的頭部存儲了長度信息(strlen的複雜度為O(1)),並且支持動態擴展等特性。

struct sdshdr {
    unsigned int len;
    unsigned int free;
    char buf[];
};

typedef char *sds;

redis的sds類型其實就是char*,redis創建一個string時,返回的是buf的指針,通過這個指針,就可以計算出sdshdr的位置,來訪問到len、free等字段。

list

redis的list實現是一個雙向鏈表,與dict類似,list也可以定製對象對比、析構等方法;list在頭結點會存儲鏈表的長度,使得獲取list長度的複雜度為O(1);頭結點還存儲了list頭部、尾部節點的指針,使得可以從兩個方向遍曆list。

typedef struct listNode {
    struct listNode *prev;
    struct listNode *next;
    void *value;
} listNode;

typedef struct list {
    listNode *head;
    listNode *tail;
    void *(*dup)(void *ptr);  // 複製鏈表節點value的方法
    void (*free)(void *ptr);  // 釋放鏈表節點value的方法
    int (*match)(void *ptr, void *key); // 鏈表節點對比方法
    unsigned long len;
} list;

ziplist

雙向鏈表的的最大問題在於頭尾指針的開銷,64bit OS上,每個節點有16bytes的額外開銷,為了節省內存,當list裏的元素較少並且大小較小時,redis采用ziplist的格式來實現鏈表。

ziplist實際上是二進製的形式組織鏈表,"二進製數據"的存儲鏈表頭部,記錄總長度,頭尾的位置等信息,然後每個節點除了存儲節點內容外,還記錄自身的長度,以及上一個節點的長度,這樣通過遍曆"二進製數據"就能訪問到ziplist裏所有的元素。另外,ziplist節點的長度、以及上一個節點的長度通過變長整數編碼,進一步節省內存。

set

set代表一個集合(元素不重複),集合在redis裏實現為字典(字典的value為NULL);如果set裏所有的元素都是整數時,redis會以intset的形式存儲,intset是一個排序的整形數組,為節省內存,當intset裏元素值在[INT16_MIN, INT16_MAX]之間時,每個數組元素占2個字節;當inset裏元素值都在[INT32_MIN, INT32_MAX]之間時,每個數組元素占4個字節;否則每個元素占8個字節。

zset

zset是排序集合,與set不同的時,zset裏每個元素有一個score(double類型),作為排序的標準;redis裏zset默認通過一個dict和一個ziplist來實現,dict用於維護set元素到score的映射關係,所有的元素都追加到一個skiplist來保證其排序。當zset裏元素較少並且大小較小時,zset也可以ziplist的形式存儲,zset的元素以及對應的score會作為ziplist的兩個連續節點存儲。

hash

redis的hash是維護filed==>value的映射表,hash的默認實現也是dict,以通過field快速找到value;當hahs裏元素較少並且大小較小時,hash也以ziplist的形式存儲以節省內存。

最後更新:2017-04-01 13:37:09

  上一篇:go 阿裏通信基礎技術框架介紹
  下一篇:go Redis主備同步