1. 程式人生 > >redis中的資料型別及儲存結構

redis中的資料型別及儲存結構

Redis支援五中資料型別:String(字串),Hash(雜湊),List(列表),Set(集合)及zset(sortedset:有序集合)。

Redis定義了豐富的原語命令,可以直接與Redis伺服器互動。實際應用中,我們不太會直接使用這些原語命令,Redis提供了Java,C/C++,C#,PHPJavaScript,Perl,Object-C,Python,Ruby,Erlang等客戶端,大多情況下我們是通過各式各樣的客戶端來操作Redis。但是,任何語言的客戶端實際上都是對Redis原語命令的封裝,瞭解原語命令有助於理解客戶端的設計原理,知其然,知其所以然。

3.1、字串

String是Redis最基本的資料型別,結構為一個key對應一個value。

String型別是二進位制安全的,意味著可以包含任何資料,比如jpg圖片或者序列化的物件。

String型別的最大能儲存512M。

不像Linux有那麼多充滿想象力的命令,還喜歡帶一對莫名其妙的引數。Redis的原語命令很簡單,而且有規律可循,一句話概括,就是乾淨利索脆。

比如我們想設定往Redis中存放一個使用者名稱,用String型別儲存:

127.0.0.1:6379> SET name chenlongfei

OK

“OK”是Redis返回的響應,代表設定成功。

取出這個name的值:

127.0.0.1:6379> GET name

"chenlongfei"

想修改name的值為“clf”,重新SET一遍,覆蓋掉原來的值:

127.0.0.1:6379> SET name clf

OK

127.0.0.1:6379> GET name

"clf" 

想刪除該條資料:

127.0.0.1:6379> DEL name

(integer) 1   --該數字代表影響的記錄總數

127.0.0.1:6379> GET name

(nil)     --nil代表為空,不存在該物件

增刪改查命令一分鐘學會,想忘記都難,媽媽再也不用擔心我的學習!

命令格式

說明

SET key value

設定指定 key 的值

GET key

獲取指定 key 的值

SETNX key value

Set if Not Exist)只有在 key 不存在時設定 key 的值

SETRANGE key offset value

用 value 引數覆寫給定 key 所儲存的字串值,從偏移量 offset 開始

GETRANGE key start end

返回 key 中字串值的子字元

GETSET key value

將給定 key 的值設為 value ,並返回 key 的舊值

MSET key value [key value ...]

Multi Set)同時設定一個或多個 key-value 對

MGET key1 [key2..]

獲取所有(一個或多個)給定 key 的值

APPEND key value

如果 key 已經存在並且是一個字串, APPEND 命令將 value 追加到 key 原來的值的末尾

SETEX key seconds value

Set Expire)將值 value 關聯到 key ,並將 key 的過期時間設為 seconds (以秒為單位)

PSETEX key milliseconds value

Precise Set Expire)這個命令和 SETEX 命令相似,但它以毫秒為單位設定 key 的生存時間,而不是像 SETEX 命令那樣,以秒為單位

STRLEN key

返回 key 所儲存的字串值的長度

INCR key

將 key 中儲存的數字值增一,前提是value是一個數字

INCRBY key increment

將 key 所儲存的值加上給定的增量值,前提是value是一個數字

INCRBYFLOAT key increment

將 key 所儲存的值加上給定的浮點增量值,前提是value是一個數字

DECR key

將 key 中儲存的數字值減一,前提是value是一個數字

DECRBY key decrement

key 所儲存的值減去給定的減量值,前提是value是一個數字

3.2、雜湊

Redis的雜湊是field和value之間的對映,即鍵值對的集合,所以特別適合用於儲存物件。

Redis 中每個 hash 最多可以儲存 232 - 1 鍵值對(40多億)。

例如,我們想在Redis中儲存一個使用者資訊,包括使用者ID,使用者名稱,郵箱地址三個欄位:

127.0.0.1:6379>HMSET user_1 userId 123 userName clf email [email protected]

OK

127.0.0.1:6379> HGETALL user_1

1) "userId"

2) "123"

3) "userName"

4) "clf"

5) "email"

6) "[email protected]"

命令格式

說明

HMSET key field1 value1 [field2 value2... ]

Hash Multi Set)同時將多個 field-value 對設定到雜湊表 key 中

HMGET key field1 [field2...]

獲取所有給定欄位的值

HSET key field value

將雜湊表 key 中的欄位 field 的值設為 value

HGET key field

獲取儲存在雜湊表中指定欄位的值

HGETALL key

獲取在雜湊表中指定 key 的所有欄位和值

HDEL key field2 [field2]

刪除一個或多個雜湊表字段

HSETNX key field value

只有在欄位 field 不存在時,設定雜湊表字段的值

HKEYS key

獲取所有雜湊表中的欄位

HVALS key

獲取雜湊表中所有值

HEXISTS key field

檢視雜湊表 key 中,指定的欄位是否存在

HLEN key

獲取雜湊表中欄位的數量

HINCRBY key field increment

為雜湊表 key 中的指定欄位的整數值加上增量

HINCRBYFLOAT key field increment

為雜湊表 key 中的指定欄位的浮點數值加上增量

3.3、列表

Redis列表是簡單的字串列表,按照插入順序排序。支援新增一個元素到列表頭部(左邊)或者尾部(右邊)的操作。

一個列表最多可以包含 232- 1 ,即超過40億個元素。

例如,我們想用一個名為“Continents”的列表盛放五大洲的名字:

127.0.0.1:6379> LPUSH Continents Asia Africa America Oceania Antarctica

(integer) 5

127.0.0.1:6379> LRANGE Continents 0 4  --獲取下標為0~4的元素

1) "Antarctica"

2) "Oceania"

3) "America"

4) "Africa"

5) "Asia"

Redis列表雖然名為列表,其實從特性上來講更像是棧,以最近放進去的元素為頭,以最早放進去的元素為尾,所以,Redis列表的下標呈倒序排列。上例中依次放進去的五個元素:Asia、Africa、America、Oceania、Antarctica,下標分別為4、3、2、1、0。這與Java中List的概念完全不一樣,需要特別注意。

與棧類似,當執行POP操作時,Redis列表彈出的是最新放進去的元素,類似於棧頂元素。

Redis列表還支援一種阻塞式操作,比如BLPOP(Blockd List Pop之縮寫),移出並獲取列表的第一個元素,如果列表沒有元素(或列表不存在)會阻塞列表直到等待超時或發現可彈出元素為止。

例如,我們對一個不存在的列表“myList”執行BLPOP命令:

BLPOPmyList 20 -- 彈出myList列表的第一個元素,如果沒有,阻塞20秒

該客戶端會進入阻塞狀態,如果20秒之內該列表存入了元素,則彈出:

27.0.0.1:6379> BLPOP myList 20 --若無元素則進入阻塞狀態,限時20

1) "myList"

2) "hello"

(6.20s)

如果超時後仍然沒有等到元素,則結束阻塞,返回nil:

127.0.0.1:6379> BLPOP myList 20

(nil)

(20.07s)

命令格式

說明

LPUSH key value1 [value2...]

將一個或多個值插入到列表頭部

LPOP key

移出並獲取列表的第一個元素

LPUSHX key value

List Push if exist)將一個或多個值插入到已存在的列表頭部

LINDEX key index

通過索引獲取列表中的元素

LRANGE key start stop

獲取列表指定範圍內的元素

LSET key index value

通過索引設定列表元素的值

LTRIM key start stop

只保留指定區間內的元素,不在指定區間之內的元素都將被刪除

RPOP key

Rear Pop)移除並獲取列表最後一個元素

RPUSH key value1 [value2...]

將一個或多個值插入到列表尾部

RPUSHX key value

將一個或多個值插入到已存在的列表尾部

LREM key count value

從列表中刪除欄位值為value的元素,刪除count的絕對值個value後結束,count > 0 從表頭刪除;count < 0 從表尾刪除;count=0 全部刪除

RPOPLPUSH source destination

移除列表的最後一個元素,並將該元素新增到另一個列表並返回

BLPOP key1 [key2... ] timeout

移出並獲取列表的第一個元素, 如果列表沒有元素會阻塞列表直到等待超時或發現可彈出元素為止,如果timeout為0則一直等待下去

BRPOP key1 [key2... ] timeout

移出並獲取列表的最後一個元素, 如果列表沒有元素會阻塞列表直到等待超時或發現可彈出元素為止,如果timeout為0則一直等待下去

LINSERT key BEFORE AFTERpivot value

在key 列表中尋找pivot,並在pivot值之前|之後插入value

LLEN key

獲取列表長度

3.4、集合

Redis集合是String型別的無序集合。集合成員是唯一的,這就意味著集合中不能出現重複的資料。

Redis集合是通過雜湊表實現的,所以新增,刪除,查詢的複雜度都是O(1)。

集合中最大的成員數為 232- 1 ,即每個集合最多可儲存40多億個成員。

集合的一大特點就是不能有重複元素,如果插入重複元素,Redis會忽略該操作:

127.0.0.1:6379> SADD direction east west south north

(integer) 4

127.0.0.1:6379> SMEMBERS direction

1) "west"

2) "east"

3) "north"

4) "south"

127.0.0.1:6379> SADD direction east

(integer) 0  --east元素已經存在,該操作無效

127.0.0.1:6379> SMEMBERS direction

1) "west"

2) "east"

3) "north"

4) "south"

Redis集合還有兩大特點,一是支援隨機獲取元素,二是支援集合間的取差集、交集與並集操作。

命令格式

說明

SADD key member1 [member2…]

向集合新增一個或多個成員

SREM key member1 [member2…]

移除集合中一個或多個成員

SPOP key

移除並返回集合中的一個隨機元素

SMEMBERS key

返回集合中的所有成員

SRANDMEMBER key [count]

返回集合中count個隨機元素,如count為空,則只返回一個

SCARD key

Set Cardinality)返回集合中的元素總數

SISMEMBER key member

判membe元素是否是集key 的成員

SMOVE source destination member

將member元素從source集合移動到destination集合

SDIFF key1 [key2…]

返回給定所有集合的差集,即以key1為基準,返回key1有且[key2...]沒有的元素

SDIFFSTORE destination key1 [key2…]

返回給定所有集合的差集並存儲在destination中

SINTER key1 [key2…]

返回給定所有集合的交集

SINTERSTORE destination key1 [key2…]

返回給定所有集合的交集並存儲在destination中

SUNION key1 [key2…]

返回所有給定集合的並集

SUNIONSTORE destination key1 [key2…]

所有給定集合的並集儲存在destination集合中

3.5、有序列表

Redis 有序集合和集合一樣也是String型別元素的集合,且不允許重複的成員。

不同的是每個元素都會關聯一個double型別的分數。Redis正是通過分數來為集合中的成員進行從小到大的排序。有序集合的成員是唯一的,但分數(score)卻可以重複。

集合是通過雜湊表實現的,所以新增,刪除,查詢的複雜度都是O(1)。

集合中最大的成員數為 232- 1 ,即每個集合最多可儲存40多億個成員。

例如,、使用有序列表來儲存學生的成績單:

127.0.0.1:6379> ZADD scoreList 82 Tom

(integer) 1

127.0.0.1:6379> ZADD scoreList 65.5 Jack

(integer) 1

127.0.0.1:6379> ZADD scoreList 43.5 Rubby

(integer) 1

127.0.0.1:6379> ZADD scoreList 99 Winner

(integer) 1

127.0.0.1:6379> ZADD scoreList 78 Linda

(integer) 1

127.0.0.1:6379> ZRANGE scoreList 0 100 WITHSCORES --獲取名次在0~100之間的記錄

 1)"Rubby"

 2)"43.5"

 3)"Jack"

 4)"65.5"

 5)"Linda"

 6)"78"

 7)"Tom"

 8)"82"

 9)"Winner"

10) "99"

需要注意的是,Redis有序集合是預設升序的,score越低排名越靠前,即score越低的元素下標越小。

命令格式

說明

ZADD key score1 member1 [score2 member2 ...]

新增一個或多個成員到有序集合,或者如果它已經存在更新其分數

ZRANGE key start stop [WITHSCORES]

把集合排序後,返回名次在[start,stop]之間的元素。 WITHSCORES是把score也打印出來

ZREVRANGE key start stop [WITHSCORES]

倒序排列(分數越大排名越靠前),返回名次在[start,stop]之間的元素

ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset n]

集合(升序)排序後取score在[min, max]內的元素,並跳過offset個,取出n個

ZREM key member [member ...]

從有序集合中刪除一個或多個成員

ZRANK key member

確定member在集合中的升序名次

ZREVRANK key member

確定member在集合中的降序名次

ZSCORE key member

獲取member的分數

ZCARD key

獲取有序集合中成員的數量

ZCOUNT key min max

計算分數在min與max之間的元素總數

ZINCRBY key increment member

給member的分數增加increment

ZREMRANGEBYRANK key start stop

移除名次在start與stop之間的元素

ZREMRANGEBYSCORE key min max

移除分數在min與max之間的元素

3.6、儲存結構

Redis的一種物件型別可以有不同的儲存結構來實現,從而同時兼顧效能和記憶體。

 

字典是Redis最基礎的資料結構,一個字典即一個DB,Redis支援多DB。

Redis字典採用Hash表實現,針對碰撞問題,採用的方法為“鏈地址法”,即將多個雜湊值相同的節點串連在一起,從而解決衝突問題。

“鏈地址法”的問題在於當碰撞劇烈時,效能退化嚴重,例如:當有n個數據,m個槽位,如果m=1,則整個Hash表退化為連結串列,查詢複雜度O(n)。為了避免Hash碰撞攻擊,Redis隨機化了Hash表種子。

Redis的方案是“雙buffer”,正常流程使用一個buffer,當發現碰撞劇烈(判斷依據為當前槽位數和Key數的對比),分配一個更大的buffer,然後逐步將資料從老的buffer遷移到新的buffer。

redisObject是真正儲存redis各種型別的結構,在Redis原始碼的redis.h檔案中,定義了這些結構:

[cpp] view plain copy  print?
  1. /* A redisobject, that is a type able to hold a string / list / set */
  2. /* The actualRedis Object */
  3. #define REDIS_LRU_BITS 24
  4. #define REDIS_LRU_CLOCK_MAX ((1<<REDIS_LRU_BITS)-1)/* Max value of obj->lru */
  5. #define REDIS_LRU_CLOCK_RESOLUTION 1000/* LRU clock resolution in ms */
  6. typedefstruct redisObject {  
  7.     unsigned type:4;  
  8.     unsigned encoding:4;  
  9.     unsigned lru:REDIS_LRU_BITS; /* lru time(relative to server.lruclock) */
  10.     intrefcount;  
  11.     void*ptr;  
  12. } robj;  

其中type即Redis支援的邏輯型別,包括:

[cpp] view plain copy  print?
  1. /* Object types*/
  2. #define REDIS_STRING 0
  3. #define REDIS_LIST 1
  4. #define REDIS_SET 2
  5. #define REDIS_ZSET 3
  6. #define REDIS_HASH 4
即前面所列舉的五種資料型別。type定義的只是邏輯型別,encoding才是物理儲存方式,一種邏輯型別可以使用不同的儲存方式,包括:
[cpp] view plain copy  print?
  1. /* Objectsencoding. Some kind of objects like Strings and Hashes can be 
  2.  * internally represented in multiple ways. The'encoding' field of the object 
  3.  * is set to one of this fields for thisobject. */
  4. #defineREDIS_ENCODING_RAW 0     /* Rawrepresentation */
  5. #defineREDIS_ENCODING_INT 1     /* Encoded asinteger */
  6. #define REDIS_ENCODING_HT2      /* Encoded as hash table */
  7. #defineREDIS_ENCODING_ZIPMAP 3  /* Encoded aszipmap */
  8. #defineREDIS_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */
  9. #defineREDIS_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
  10. #define REDIS_ENCODING_INTSET6  /* Encoded as intset */
  11. #defineREDIS_ENCODING_SKIPLIST 7  /* Encoded asskiplist */
  12. #defineREDIS_ENCODING_EMBSTR 8  /* Embedded sdsstring encoding */
(1)REDIS_ENCODING_RAW 即原生態的儲存結構,就是以字串形式儲存,字串型別在redis中用sds(simple dynamic string)封裝,主要為了解決長度計算和追加效率的問題。

(2)REDIS_ENCODING_INT 代表整數,以long型儲存。

(3) REDIS_ENCODING_HT 代表雜湊表(Hash Table),以雜湊表結構儲存,與字典的實現方法一致。

(4)REDIS_ENCODING_ZIPMAP 其實質是用一個字串陣列來依次儲存key和value,查詢時是依次遍列每個key-value 對,直到查到為止。


(5)REDIS_ENCODING_LINKEDLIST 代表連結串列,以典型的連結串列結構儲存。

(6) REDIS_ENCODING_ZIPLIST 代表一種雙端列表,且通過特殊的格式定義,壓縮記憶體適用,以時間換空間。ZIPLIST適合小資料量的讀場景,不適合大資料量的多寫/刪除場景。


(7) REDIS_ENCODING_INTSET  是用一個有序的整數陣列來實現的。


(8)REDIS_ENCODING_SKIPLIST  同時採用字典和有序集兩種資料結構來儲存資料元素。跳躍表(SkipList)是一個特殊的連結串列,相比一般的連結串列,有更高的查詢效率,其效率可比擬於二叉查詢樹。一張關於跳錶和跳錶搜尋過程如下圖:


在圖中,需要尋找 68,在給出的查詢過程中,利用跳錶資料結構優勢,只比較了 3次,橫箭頭不比較,豎箭頭比較。由此可見,跳錶預先間隔地儲存了有序連結串列中的節點,從而在查詢過程中能達到類似於二分搜尋的效果,而二分搜尋思想就是通過比較中點資料放棄另一半的查詢,從而節省一半的查詢時間。

缺點即浪費了空間,自古空間和時間兩難全。

(9)REDIS_ENCODING_EMBSTR  代表使用embstr編碼的簡單動態字串。好處有如下幾點: embstr的建立只需分配一次記憶體,而raw為兩次(一次為sds分配物件,另一次為objet分配物件,embstr省去了第一次)。相對地,釋放記憶體的次數也由兩次變為一次。embstr的objet和sds放在一起,更好地利用快取帶來的優勢。需要注意的是,Redis並未提供任何修改embstr的方式,即embstr是隻讀的形式。對embstr的修改實際上是先轉換為raw再進行修改。

3.6.1 字串的儲存結構

Redis的所有的key都採用字串儲存,而值可以是字串,列表,雜湊,集合和有序集合物件的其中一種。

字串儲存的邏輯型別即REDIS_STRING,其物理實現(enconding)可以為 REDIS_ENCODING_INT、 REDIS_ENCODING_EMBSTR或REDIS_ENCODING_RAW。

首先,如果可以使用REDIS_ENCODING_EMBSTR編碼,Redis首選REDIS_ENCODING_EMBSTR儲存;其次,如果可以轉換,Redis會嘗試將一個字串轉化為Long,儲存為REDIS_ENCODING_INT,如“26”、“180”等;最後,Redis會儲存為REDIS_ENCODING_RAW,如“chenlongfei”、“Redis”等。

3.6.2 雜湊的儲存結構

REDIS_HASH可以有兩種encoding方式: REDIS_ENCODING_ZIPLIST 和 REDIS_ENCODING_HT

Hash表預設的編碼格式為REDIS_ENCODING_ZIPLIST,在收到來自使用者的插入資料的命令時:

(1)呼叫hashTypeTryConversion函式檢查鍵/值的長度大於配置的hash_max_ziplist_value(預設64)

(2)呼叫hashTypeSet判斷節點數量大於配置的hash_max_ziplist_entries(預設512)

以上任意條件滿足則將Hash表的資料結構從REDIS_ENCODING_ZIPLIST轉為REDIS_ENCODING_HT。

3.6.3 列表的儲存結構

REDIS_SET有兩種encoding方式,REDIS_ENCODING_ZIPLIST和REDIS_ENCODING_LINKEDLIST。

列表的預設編碼格式為REDIS_ENCODING_ZIPLIST,當滿足以下條件時,編碼格式轉換為REDIS_ENCODING_LINKEDLIST:

(1)元素大小大於list-max-ziplist-value(預設64)

(2)元素個數大於配置的list-max-ziplist-entries(預設512)

3.6.4 集合的儲存結構

REDIS_SET有兩種encoding方式: REDIS_ENCODING_INTSET 和 REDIS_ENCODING_HT。

集合的元素型別和數量決定了encoding方式,預設採用REDIS_ENCODING_INTSET ,當滿足以下條件時,轉換為REDIS_ENCODING_HT:

(1)元素型別不是整數

(2)元素個數超過配置的set-max-intset-entries(預設512)

3.6.5 有序列表的儲存結構

REDIS_ZSET有兩種encoding方式: REDIS_ENCODING_ZIPLIST(同上)和 REDIS_ENCODING_SKIPLIST。

由於有序集合每一個元素包括:<member,score>兩個屬性,為了保證對member和score都有很好的查詢效能,REDIS_ENCODING_SKIPLIST同時採用字典和有序集兩種資料結構來儲存資料元素。字典和有序集通過指標指向同一個資料節點來避免資料冗餘。

字典中使用member作為key,score作為value,從而保證在O(1)時間對member的查詢跳躍表基於score做排序,從而保證在 O(logN) 時間內完成通過score對memer的查詢。

有序集合預設也是採用REDIS_ENCODING_ZIPLIST的實現,當滿足以下條件時,轉換為REDIS_ENCODING_SKIPLIST:

(1)資料元素個數超過配置zset_max_ziplist_entries 的值(預設值為 128 )

(2)新新增元素的 member 的長度大於配置的zset_max_ziplist_value 的值(預設值為 64 )

3.6.6 總結

針對同一種資料型別,Redis會根據元素型別/大小/個數採用不同的編碼方式,不同的編碼方式在記憶體使用效率/查詢效率上差距巨大,可以通過配置檔案調整引數來達到最優。

RedisObject

實現方式一

實現方式二

實現方式三

字串

REDIS_ENCODING_INT

REDIS_ENCODING_EMBSTR

REDIS_ENCODING_RAW

雜湊

REDIS_ENCODING_ZIPLIST

REDIS_ENCODING_HT

列表

REDIS_ENCODING_ZIPLIST

REDIS_ENCODING_LINKEDLIST

集合

REDIS_ENCODING_INTSET

REDIS_ENCODING_HT

有序集合

REDIS_ENCODING_ZIPLIST

REDIS_ENCODING_SKIPLIST