redis支援的五種資料型別及其底層實現
Redis物件型別簡介
Redis是一種key/value型資料庫,其中,每個key和value都是使用物件表示的。比如,我們執行以下程式碼:
其中的key是message,是一個包含了字串"message"的物件。而value是一個包含了"hello redis"的物件。Redis共有五種物件的型別,分別是:
型別常量 | 物件的名稱 |
---|---|
REDIS_STRING |
字串物件 |
REDIS_LIST |
列表物件 |
REDIS_HASH |
雜湊物件 |
REDIS_SET |
集合物件 |
REDIS_ZSET |
有序集合物件 |
Redis中的一個物件的結構體表示如下:
/*
* Redis 物件
*/
typedef struct redisObject {
// 型別
unsigned type:4;
// 不使用(對齊位)
unsigned notused:2;
// 編碼方式
unsigned encoding:4;
// LRU 時間(相對於 server.lruclock)
unsigned lru:22;
// 引用計數
int refcount;
// 指向物件的值
void *ptr;
} robj;
type表示了該物件的物件型別,即上面五個中的一個。但為了提高儲存效率與程式執行效率,每種物件的底層資料結構實現都可能不止一種。encoding就表示了物件底層所使用的編碼。下面先介紹每種底層資料結構的實現,再介紹每種物件型別都用了什麼底層結構並分析他們之間的關係。
Redis物件底層資料結構
底層資料結構共有八種,如下表所示:
編碼常量 | 編碼所對應的底層資料結構 |
---|---|
REDIS_ENCODING_INT |
long 型別的整數 |
REDIS_ENCODING_EMBSTR |
embstr 編碼的簡單動態字串 |
REDIS_ENCODING_RAW |
簡單動態字串 |
REDIS_ENCODING_HT |
字典 |
REDIS_ENCODING_LINKEDLIST |
雙端連結串列 |
REDIS_ENCODING_ZIPLIST |
壓縮列表 |
REDIS_ENCODING_INTSET |
整數集合 |
REDIS_ENCODING_SKIPLIST |
跳躍表和字典 |
字串物件
字串物件的編碼可以是int、raw或者embstr。
如果一個字串的內容可以轉換為long,那麼該字串就會被轉換成為long型別,物件的ptr就會指向該long,並且物件型別也用int型別表示。
普通的字串有兩種,embstr和raw。embstr應該是Redis 3.0新增的資料結構,在2.8中是沒有的。如果字串物件的長度小於39位元組,就用embstr物件。否則用傳統的raw物件。
- embstr的建立只需分配一次記憶體,而raw為兩次(一次為sds分配物件,另一次為objet分配物件,embstr省去了第一次)。
- 相對地,釋放記憶體的次數也由兩次變為一次。
- embstr的objet和sds放在一起,更好地利用快取帶來的優勢。
列表物件
列表物件的編碼可以是ziplist或者linkedlist。
ziplist是一種壓縮連結串列,它的好處是更能節省記憶體空間,因為它所儲存的內容都是在連續的記憶體區域當中的。當列表物件元素不大,每個元素也不大的時候,就採用ziplist儲存。但當資料量過大時就ziplist就不是那麼好用了。因為為了保證他儲存內容在記憶體中的連續性,插入的複雜度是O(N),即每次插入都會重新進行realloc。如下圖所示,物件結構中ptr所指向的就是一個ziplist。整個ziplist只需要malloc一次,它們在記憶體中是一塊連續的區域。
linkedlist是一種雙向連結串列。它的結構比較簡單,節點中存放pre和next兩個指標,還有節點相關的資訊。當每增加一個node的時候,就需要重新malloc一塊記憶體。
雜湊物件
雜湊物件的底層實現可以是ziplist或者hashtable。
ziplist中的雜湊物件是按照key1,value1,key2,value2這樣的順序存放來儲存的。當物件數目不多且內容不大時,這種方式效率是很高的。
hashtable的是由dict這個結構來實現的
集合物件
集合物件的編碼可以是intset或者hashtable。
intset是一個整數集合,裡面存的為某種同一型別的整數,支援如下三種長度的整數:
- #define INTSET_ENC_INT16 (sizeof(int16_t))
- #define INTSET_ENC_INT32 (sizeof(int32_t))
- #define INTSET_ENC_INT64 (sizeof(int64_t))
有序集合物件
有序集合的編碼可能兩種,一種是ziplist,另一種是skiplist與dict的結合。
ziplist作為集合和作為雜湊物件是一樣的,member和score順序存放。按照score從小到大順序排列。它的結構不再複述。
skiplist是一種跳躍表,它實現了有序集合中的快速查詢,在大多數情況下它的速度都可以和平衡樹差不多。但它的實現比較簡單,可以作為平衡樹的替代品。它的結構比較特殊。下面分別是跳躍表skiplist和它內部的節點skiplistNode的結構體:
Redis的鍵值可以使用五種資料型別:字串,散列表,列表,集合,有序集合
字串型別
字串是Redis中最基本的資料型別,它能夠儲存任何型別的字串,包含二進位制資料。可以用於儲存郵箱,JSON化的物件,甚至是一張圖片,一個字串允許儲存的最大容量為512MB。字串是其他四種類型的基礎,與其他幾種型別的區別從本質上來說只是組織字串的方式不同而已。
基本命令
字串操作
- SET 賦值,用法:
SET key value
- GET 取值,用法:
GET key
- INCR 遞增數字,僅僅對數字型別的鍵有用,相當於Java的i++運算,用法:
INCR key
- INCRBY 增加指定的數字,僅僅對數字型別的鍵有用,相當於Java的i+=3,用法:
INCRBY key increment
,意思是key自增increment,increment可以為負數,表示減少。 - DECR 遞減數字,僅僅對數字型別的鍵有用,相當於Java的i–,用法:
DECR key
- DECRBY 減少指定的數字,僅僅對數字型別的鍵有用,相當於Java的i-=3,用法:
DECRBY key decrement
,意思是key自減decrement,decrement可以為正數,表示增加。 - INCRBYFLOAT 增加指定浮點數,僅僅對數字型別的鍵有用,用法:
INCRBYFLOAT key increment
- APPEND 向尾部追加值,相當於Java中的”hello”.append(“ world”),用法:
APPEND key value
- STRLEN 獲取字串長度,用法:
STRLEN key
- MSET 同時設定多個key的值,用法:
MSET key1 value1 [key2 value2 ...]
- MGET 同時獲取多個key的值,用法:
MGET key1 [key2 ...]
位操作
- GETBIT 獲取一個鍵值的二進位制位的指定位置的值(0/1),用法:
GETBIT key offset
- SETBIT 設定一個鍵值的二進位制位的指定位置的值(0/1),用法:
SETBIT key offset value
- BITCOUNT 獲取一個鍵值的一個範圍內的二進位制表示的1的個數,用法:
BITCOUNT key [start end]
- BITOP 該命令可以對多個字串型別鍵進行位運算,並將結果儲存到指定的鍵中,BITOP支援的運算包含:OR,AND,XOR,NOT,用法:
BITOP OP desKey key1 key2
- BITPOS 獲取指定鍵的第一個位值為0或者1的位置,用法:
BITPOS key 0/1 [start, end]
雜湊型別
雜湊型別相當於Java中的HashMap,他的值是一個字典,儲存很多key,value對,每對key,value的值個鍵都是字串型別,換句話說,雜湊型別不能巢狀其他資料型別。一個雜湊型別鍵最多可以包含2的32次方-1個欄位。
基本命令
- HSET 賦值,用法:
HSET key field value
- HMSET 一次賦值多個欄位,用法:
HMSET key field1 value1 [field2 values]
- HGET 取值,用法:
HSET key field
- HMGET 一次取多個欄位的值,用法:
HMSET key field1 [field2]
- HGETALL 一次取所有欄位的值,用法:
HGETALL key
- HEXISTS 判斷欄位是否存在,用法:
HEXISTS key field
- HSETNX 當欄位不存在時賦值,用法:
HSETNX key field value
- HINCRBY 增加數字,僅對數字型別的值有用,用法:
HINCRBY key field increment
- HDEL 刪除欄位,用法:
HDEL key field
- HKEYS 獲取所有欄位名,用法:
HKEYS key
- HVALS 獲取所有欄位值,用法:
HVALS key
- HLEN 獲取欄位數量,用法:
HLEN key
列表型別
列表型別(list)用於儲存一個有序的字串列表,常用的操作是向佇列兩端新增元素或者獲得列表的某一片段。列表內部使用的是雙向連結串列(double linked list)實現的,所以向列表兩端新增元素的時間複雜度是O(1),獲取越接近列表兩端的元素的速度越快。但是缺點是使用列表通過索引訪問元素的效率太低(需要從端點開始遍歷元素)。所以列表的使用場景一般如:朋友圈新鮮事,只關心最新的一些內容。藉助列表型別,Redis還可以作為訊息佇列使用。
基本命令
- LPUSH 向列表左端新增元素,用法:
LPUSH key value
- RPUSH 向列表右端新增元素,用法:
RPUSH key value
- LPOP 從列表左端彈出元素,用法:
LPOP key
- RPOP 從列表右端彈出元素,用法:
RPOP key
- LLEN 獲取列表中元素個數,用法:
LLEN key
- LRANGE 獲取列表中某一片段的元素,用法:
LRANGE key start stop
,index從0開始,-1表示最後一個元素 - LREM 刪除列表中指定的值,用法:
LREM key count value
,刪除列表中前count個值為value的元素,當count>0時從左邊開始數,count<0時從右邊開始數,count=0時會刪除所有值為value的元素 - LINDEX 獲取指定索引的元素值,用法:
LINDEX key index
- LSET 設定指定索引的元素值,用法:
LSET key index value
- LTRIM 只保留列表指定片段,用法:
LTRIM key start stop
,包含start和stop - LINSERT 像列表中插入元素,用法:
LINSERT key BEFORE|AFTER privot value
,從左邊開始尋找值為privot的第一個元素,然後根據第二個引數是BEFORE還是AFTER決定在該元素的前面還是後面插入value - RPOPLPUSH 將元素從一個列表轉義到另一個列表,用法:
RPOPLPUSH source destination
集合型別
集合在概念在高中課本就學過,集合中每個元素都是不同的,集合中的元素個數最多為2的32次方-1個,集合中的元素師沒有順序的。
基本命令
- SADD 新增元素,用法:
SADD key value1 [value2 value3 ...]
- SREM 刪除元素,用法:
SREM key value2 [value2 value3 ...]
- SMEMBERS 獲得集合中所有元素,用法:
SMEMBERS key
- SISMEMBER 判斷元素是否在集合中,用法:
SISMEMBER key value
- SDIFF 對集合做差集運算,用法:
SDIFF key1 key2 [key3 ...]
,先計算key1和key2的差集,然後再用結果與key3做差集 - SINTER 對集合做交集運算,用法:
SINTER key1 key2 [key3 ...]
- SUNION 對集合做並集運算,用法:
SUNION key1 key2 [key3 ...]
- SCARD 獲得集合中元素的個數,用法:
SCARD key
- SDIFFSTORE 對集合做差集並將結果儲存,用法:
SDIFFSTORE destination key1 key2 [key3 ...]
- SINTERSTORE 對集合做交集運算並將結果儲存,用法:
SINTERSTORE destination key1 key2 [key3 ...]
- SUNIONSTORE 對集合做並集運算並將結果儲存,用法:
SUNIONSTORE destination key1 key2 [key3 ...]
- SRANDMEMBER 隨機獲取集合中的元素,用法:
SRANDMEMBER key [count]
,當count>0時,會隨機中集合中獲取count個不重複的元素,當count<0時,隨機中集合中獲取|count|和可能重複的元素。 - SPOP 從集合中隨機彈出一個元素,用法:
SPOP key
有序集合型別
有序集合型別與集合型別的區別就是他是有序的。有序集合是在集合的基礎上為每一個元素關聯一個分數,這就讓有序集合不僅支援插入,刪除,判斷元素是否存在等操作外,還支援獲取分數最高/最低的前N個元素。有序集合中的每個元素是不同的,但是分數卻可以相同。有序集合使用散列表和跳躍表實現,即使讀取位於中間部分的資料也很快,時間複雜度為O(log(N)),有序集合比列表更費記憶體。
基本命令
- ZADD 新增元素,用法:
ZADD key score1 value1 [score2 value2 score3 value3 ...]
- ZSCORE 獲取元素的分數,用法:
ZSCORE key value
- ZRANGE 獲取排名在某個範圍的元素,用法:
ZRANGE key start stop [WITHSCORE]
,按照元素從小到大的順序排序,從0開始編號,包含start和stop對應的元素,WITHSCORE選項表示是否返回元素分數 - ZREVRANGE 獲取排名在某個範圍的元素,用法:
ZREVRANGE key start stop [WITHSCORE]
,和上一個命令用法一樣,只是這個倒序排序的。 - ZRANGEBYSCORE 獲取指定分數範圍內的元素,用法:
ZRANGEBYSCORE key min max
,包含min和max,(min
表示不包含min,(max
表示不包含max,+inf
表示無窮大 - ZINCRBY 增加某個元素的分數,用法:
ZINCRBY key increment value
- ZCARD 獲取集合中元素的個數,用法:
ZCARD key
- ZCOUNT 獲取指定分數範圍內的元素個數,用法:
ZCOUNT key min max
,min和max的用法和5中的一樣 - ZREM 刪除一個或多個元素,用法:
ZREM key value1 [value2 ...]
- ZREMRANGEBYRANK 按照排名範圍刪除元素,用法:
ZREMRANGEBYRANK key start stop
- ZREMRANGEBYSCORE 按照分數範圍刪除元素,用法:
ZREMRANGEBYSCORE key min max
,min和max的用法和4中的一樣 - ZRANK 獲取正序排序的元素的排名,用法:
ZRANK key value
- ZREVRANK 獲取逆序排序的元素的排名,用法:
ZREVRANK key value
- ZINTERSTORE 計算有序集合的交集並存儲結果,用法:
ZINTERSTORE destination numbers key1 key2 [key3 key4 ...] WEIGHTS weight1 weight2 [weight3 weight4 ...] AGGREGATE SUM | MIN | MAX
,numbers表示參加運算的集合個數,weight表示權重,aggregate表示結果取值 - ZUNIONSTORE 計算有序幾個的並集並存儲結果,用法和14一樣,不再贅述。