分佈式專題(十二)Redis數據結構

目錄

存儲結構

數據類型

字符串類型(String)

內部數據結構

列表類型(list)

內部數據結構

哈希類型(hash)

內部數據結構

集合類型(set)

內部數據結構

有序集合(sorted-set)

內部數據結構


緩存大致可以分爲兩類,一種是應用內緩存,比如Map(簡單的數據結構),以及EH Cache(Java第三方庫),另一種 就是緩存組件,比如Memached,Redis;Redis(remote dictionary server)是一個基於KEY-VALUE的高性能的 存儲系統,通過提供多種鍵值數據類型來適應不同場景下的緩存與存儲需求 

存儲結構

大家一定對字典類型的數據結構非常熟悉,比如map ,通過key-value的方式存儲的結構。 redis的全稱是remote dictionary server(遠程字典服務器),它以字典結構存儲數據,並允許其他應用通過TCP協議讀寫字典中的內容。數據結構如下

數據類型

字符串類型(String)

字符串類型是redis中最基本的數據類型,它能存儲任何形式的字符串,包括二進制數據。你可以用它存儲用戶的 郵箱、json化的對象甚至是圖片。一個字符類型鍵允許存儲的最大容量是512M

內部數據結構

在Redis內部,String類型通過 intSDS(simple dynamic string)作爲結構存儲,int用來存放整型數據,sds存放字節/字符串和浮點型數據。在C的標準字符串結構下進行了封裝,用來提升基本操作的性能,同時也充分利用已有的C的標準庫,簡化實現邏輯。我們可以在redis的源碼中【sds.h】中看到sds的結構如下:
typedef char *sds;

redis3.2分支引入了五種sdshdr類型,目的是爲了滿足不同長度字符串可以使用不同大小的Header,從而節省內存,每次在創建一個sds時根據sds的實際長度判斷應該選擇什麼類型的sdshdr,不同類型的sdshdr佔用的內存空間不同。這樣細分一下可以省去很多不必要的內存開銷,下面是3.2sdshdr定義

// 8表示字符串最大長度是2^8-1 (長度爲255)
struct __attribute__ ((__packed__)) sdshdr8 {
 
    //表示當前sds的長度(單位是字節)
    uint8_t len;

    //表示已爲sds分配的內存大小(單位是字節)
    uint8_t alloc; 

    //用一個字節表示當前sdshdr的類型,因爲有sdshdr有五種類型,所以至少需要3位來表示000:sdshdr5,001:sdshdr8,010:sdshdr16,011:sdshdr32,100:sdshdr64。高5位 用不到所以都爲0
    unsigned char flags; 

    //sds實際存放的位置
    char buf[];

};

列表類型(list)

列表類型(list)可以存儲一個有序的字符串列表,常用的操作是向列表兩端添加元素或者獲得列表的某一個片段。

列表類型內部使用雙向鏈表實現,所以向列表兩端添加元素的時間複雜度爲O(1), 獲取越接近兩端的元素速度就越快。這意味着即使是一個有幾千萬個元素的列表,獲取頭部或尾部的10條記錄也是很快的

內部數據結構

redis3.2之前,List類型的value對象內部以linkedlist或者ziplist來實現, list的元素個數和單個元素的長度達到以下兩個要求時,Redis會採用ziplist(壓縮列表)來實現來減少內存佔用。否則就會採用linkedlist(雙向鏈表)結構。
 
  • 列表對象保存的所有字符串元素的長度都小於64字節
  • 列表對象保存的元素數量小於512個
這兩種存儲方式都有優缺點,linkedlist在鏈表兩端進行pushpop操作,在插入節點上複雜度比較低,但是內存開銷比較大;ziplist存儲在一段連續的內存上,所以存儲效率很高,但是插入和刪除都需要頻繁申請和釋放內存;
 
redis3.2之後,採用的一種叫quicklist的數據結構來存儲list,列表的底層都由quicklist實現。
 
quicklist仍然是一個雙向鏈表,只是列表的每個節點都是一個ziplist,其實就是linkedlistziplist的結合,quicklist中每個節點ziplist都能夠存儲多個數據元素,在源碼中的文件爲【quicklist.c】,在源碼第一行中有解釋爲:A doubly linked list of ziplists意思爲一個由ziplist組成的雙向鏈表,如下圖所示:

哈希類型(hash)

內部數據結構

  • 一種是ziplist,上面已經提到過。當存儲的數據超過配置的閥值時就是轉用hashtable的結構。這種轉換比較消耗性能,所以應該儘量避免這種轉換操作。同時滿足以下兩個條件時纔會使用這種結構:
    • 當鍵的個數小於hash-max-ziplist-entries(默認512)
    • 當所有值都小於hash-max-ziplist-value(默認64)
  • 另一種就是hashtable。這種結構的時間複雜度爲O(1),但是會消耗比較多的內存空間

集合類型(set)

集合類型中,每個元素都是不同的,也就是不能有重複數據,同時集合類型中的數據是無序的。一個集合類型鍵可 以存儲至多232-1個 。集合類型和列表類型的最大的區別是有序性和唯一性 集合類型的常用操作是向集合中加入或刪除元素、判斷某個元素是否存在。由於集合類型在redis內部是使用的值 爲空的散列表(hash table),所以這些操作的時間複雜度都是O(1).

內部數據結構

Set在的底層數據結構以intset或者hashtable來存儲。當set中只包含整數型的元素時,採用intset來存儲,否則,採用hashtable存儲,但是對於set來說,該hashtablevalue值用於爲NULL通過key來存儲元素

有序集合(sorted-set)

有序集合類型,顧名思義,和前面講的集合類型的區別就是多了有序的功能
在集合類型的基礎上,有序集合類型爲集合中的每個元素都關聯了一個分數,這使得我們不僅可以完成插入、刪除 和判斷元素是否存在等集合類型支持的操作,還能獲得分數最高(或最低)的前N個元素、獲得指定分數範圍內的元 素等與分數有關的操作。雖然集合中每個元素都是不同的,但是他們的分數卻可以相同

內部數據結構

  • 一種是ziplist結構

          與上面的hash中的ziplist類似,member和score順序存放並按score的順序排列

  • 另一種是skiplist與dict的結合

         skiplist是一種跳躍表結構,用於有序集合中快速查找,大多數情況下它的效率與平衡樹差不多,但比平衡樹實現簡單。redis的作者對普通的跳躍表進行了修改,包括添加span\tail\backward指針、score的值可重複這些設計,從而實現排序功能和反向遍歷的功能。

一般跳躍表的實現,主要包含以下幾個部分:

  • 表頭(head):指向頭節點
  • 表尾(tail):指向尾節點
  • 節點(node):實際保存的元素節點,每個節點可以有多層,層數是在創建此節點的時候隨機生成的一個數值,而且每一層都是一個指向後面某個節點的指針。
  • 層(level):目前表內節點的最大層數
  • 長度(length):節點的數量。

跳躍表的遍歷總是從高層開始,然後隨着元素值範圍的縮小,慢慢降低到低層。

跳躍表的實現原理可以參考:https://blog.csdn.net/Acceptedxukai/article/details/17333673

前面也說了,有序列表是使用skiplist和dict結合實現的,skiplist用來保障有序性和訪問查找性能,dict就用來存儲元素信息,並且dict的訪問時間複雜度爲O(1)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章