redis中的資料型別及儲存結構
Redis支援五中資料型別:String(字串),Hash(雜湊),List(列表),Set(集合)及zset(sortedset:有序集合)。
Redis定義了豐富的原語命令,可以直接與Redis伺服器互動。實際應用中,我們不太會直接使用這些原語命令,Redis提供了Java,C/C++,C#,PHP,JavaScript,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?- /* A redisobject, that is a type able to hold a string / list / set */
- /* The actualRedis Object */
- #define REDIS_LRU_BITS 24
- #define REDIS_LRU_CLOCK_MAX ((1<<REDIS_LRU_BITS)-1)/* Max value of obj->lru */
- #define REDIS_LRU_CLOCK_RESOLUTION 1000/* LRU clock resolution in ms */
- typedefstruct redisObject {
- unsigned type:4;
- unsigned encoding:4;
- unsigned lru:REDIS_LRU_BITS; /* lru time(relative to server.lruclock) */
- intrefcount;
- void*ptr;
- } robj;
其中type即Redis支援的邏輯型別,包括:
[cpp] view plain copy print?- /* Object types*/
- #define REDIS_STRING 0
- #define REDIS_LIST 1
- #define REDIS_SET 2
- #define REDIS_ZSET 3
- #define REDIS_HASH 4
[cpp] view plain copy print?
- /* Objectsencoding. Some kind of objects like Strings and Hashes can be
- * internally represented in multiple ways. The'encoding' field of the object
- * is set to one of this fields for thisobject. */
- #defineREDIS_ENCODING_RAW 0 /* Rawrepresentation */
- #defineREDIS_ENCODING_INT 1 /* Encoded asinteger */
- #define REDIS_ENCODING_HT2 /* Encoded as hash table */
- #defineREDIS_ENCODING_ZIPMAP 3 /* Encoded aszipmap */
- #defineREDIS_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */
- #defineREDIS_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
- #define REDIS_ENCODING_INTSET6 /* Encoded as intset */
- #defineREDIS_ENCODING_SKIPLIST 7 /* Encoded asskiplist */
- #defineREDIS_ENCODING_EMBSTR 8 /* Embedded sdsstring encoding */
(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 |