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
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,也就 是跳躍表