1. 程式人生 > >Redis的基礎資料結構

Redis的基礎資料結構

Redis的魅力

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

儲存結構

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

Redis的安裝

1.獲取redis資源

  wget http://download.redis.io/releases/redis-4.0.8.tar.gz

2.解壓

  tar xzvf redis-4.0.8.tar.gz

3.安裝

  cd redis-4.0.8

  make

  cd src

  make install PREFIX=/usr/local/redis

4.移動配置檔案到安裝目錄下

  cd ../

  mkdir /usr/local/redis/etc

  mv redis.conf /usr/local/redis/etc

 5.配置redis為後臺啟動

  vi /usr/local/redis/etc/redis.conf //將daemonize no

 改成daemonize yes

6.將redis加入到開機啟動

  vi /etc/rc.local //在裡面新增內容:/usr/local/redis/bin/redis-server /usr/local/redis/etc/redis.conf (意思就是開機呼叫這段開啟redis的命令)

7.開啟redis

  /usr/local/redis/bin/redis-server /usr/local/redis/etc/redis.conf 

常用命令  

  redis-server /usr/local/redis/etc/redis.conf //啟動redis

  pkill redis  //停止redis

  解除安裝redis:

    rm -rf /usr/local/redis //刪除安裝目錄

    rm -rf /usr/bin/redis-* //刪除所有redis相關命令指令碼

    rm -rf /root/download/redis-4.0.4 //刪除redis解壓資料夾

資料型別

字串型別

       字串型別是redis中最基本的資料型別,它能儲存任何形式的字串,包括二進位制資料。你可以用它儲存使用者的郵箱、json化的物件甚至是圖片。一個字元型別鍵允許儲存的最大容量是512M

內部資料結構

        在Redis內部,String型別通過 int、SDS(simple dynamic string)作為結構儲存,int用來存放整型資料,sds存放位元組/字串和浮點型資料。在C的標準字串結構下進行了封裝,用來提升基本操作的效能,同時也充分利用已有的C的標準庫,簡化實現邏輯。我們可以在redis的原始碼中【sds.h】中看到sds的結構如下;

typedef char *sds;

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

`struct __attribute__ ((__packed__)) sdshdr8 { 8表示字串最大長度是2^8-1 (長度為255)`` 
 uint8_t len;//表示當前sds的長度(單位是位元組)``  uint8_t alloc; //表示已為sds分配的記憶體大小(單
位是位元組)``  unsigned char flags; //用一個位元組表示當前sdshdr的型別,因為有sdshdr有五種型別,所
以至少需要3位來表示000:sdshdr5,001:sdshdr8,010:sdshdr16,011:sdshdr32,100:sdshdr64。高5位
用不到所以都為0。``  char buf[];//sds實際存放的位置``};`

sdshdr8的記憶體佈局

列表型別

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

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

內部資料結構

       redis3.2之前,List型別的value物件內部以linkedlist或者ziplist來實現, 當list的元素個數和單個元素的長度比較小的時候,Redis會採用ziplist(壓縮列表)來實現來減少記憶體佔用。否則就會採用linkedlist(雙向連結串列)結構。
        redis3.2之後,採用的一種叫quicklist的資料結構來儲存list,列表的底層都由quicklist實現。
        這兩種儲存方式都有優缺點,雙向連結串列在連結串列兩端進行push和pop操作,在插入節點上覆雜度比較低,但是記憶體開銷比較大; ziplist儲存在一段連續的記憶體上,所以儲存效率很高,但是插入和刪除都需要頻繁申請和釋放記憶體;quicklist仍然是一個雙向連結串列,只是列表的每個節點都是一個ziplist,其實就是linkedlist和ziplist的結合,quicklist中每個節點ziplist都能夠儲存多個數據元素,在原始碼中的檔案為【quicklist.c】,在原始碼第一行中有解釋為:A doubly linked list of ziplists意思為一個由ziplist組成的雙向連結串列;

hash型別

資料結構

map提供兩種結構來儲存,一種是hashtable、另一種是前面講的ziplist,資料量小的時候用ziplist. 在redis中,雜湊表分為三層,分別是,原始碼地址【dict.h】

dictEntry

管理一個key-value,同時保留同一個桶中相鄰元素的指標,用來維護雜湊桶的內部鏈;

typedef struct dictEntry {
  void *key;
  union {  //因為value有多種型別,所以value用了union來儲存
    void *val;
    uint64_t u64;
    int64_t s64;
    double d;
 } v;
  struct dictEntry *next;//下一個節點的地址,用來處理碰撞,所有分配到同一索引的元素通過next指標
連結起來形成連結串列key和v都可以儲存多種型別的資料
} dictEntry;

dictht

        實現一個hash表會使用一個buckets存放dictEntry的地址,一般情況下通過hash(key)%len得到的值就是buckets的索引,這個值決定了我們要將此dictEntry節點放入buckets的哪個索引裡,這個buckets實際上就是我們說的hash表。dict.h的dictht結構中table存放的就是buckets的地址

typedef struct dictht {
  dictEntry **table;//buckets的地址
  unsigned long size;//buckets的大小,總保持為 2^n
  unsigned long sizemask;//掩碼,用來計算hash值對應的buckets索引
  unsigned long used;//當前dictht有多少個dictEntry節點
} dictht;

dict

       dictht實際上就是hash表的核心,但是隻有一個dictht還不夠,比如rehash、遍歷hash等操作,所以redis定義了一個叫dict的結構以支援字典的各種操作,當dictht需要擴容/縮容時,用來管理dictht的遷移,以下是它的資料結構,原始碼在

typedef struct dict {
  dictType *type;//dictType裡存放的是一堆工具函式的函式指標,
  void *privdata;//儲存type中的某些函式需要作為引數的資料
  dictht ht[2];//兩個dictht,ht[0]平時用,ht[1] rehash時用
  long rehashidx; //當前rehash到buckets的哪個索引,-1時表示非rehash狀態
  int iterators; //安全迭代器的計數。
} dict;

       比如我們要講一個數據儲存到hash表中,那麼會先通過murmur計算key對應的hashcode,然後根據hashcode取
模得到bucket的位置,再插入到連結串列中

集合型別

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

資料結構

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

有序集合

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

資料結構

       zset型別的資料結構就比較複雜一點,內部是以ziplist或者skiplist+hashtable來實現,這裡面最核心的一個結構就是skiplist,也就 是跳躍表