【Redis学习】Redis数据类型及存储结构
最近再進行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原語命令的封裝,了解原語命令有助于理解客戶端的設計原理,知其然,知其所以然。
1、數據類型
1.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 (null) --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是一個數字 |
1.2、哈希
Redis的哈希是field和value之間的映射,即鍵值對的集合,所以特別適合用于存儲對象。
Redis 中每個 hash 最多可以存儲 232 - 1 鍵值對(40多億)。
例如,我們想在Redis中存儲一個用戶信息,包括用戶ID,用戶名,郵箱地址三個字段:
127.0.0.1:6379>HMSET user_1 userId 123 userName clf email chenlongfei@163.com OK 127.0.0.1:6379> HGETALL user_1 1) "userId" 2) "123" 3) "userName" 4) "clf" 5) "email" 6) "chenlongfei@163.com"| 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 中的指定字段的浮點數值加上增量 |
1.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秒之內該列表存入了元素,則彈出: 127.0.0.1:6379> BLPOP myList 20 --若無元素則進入阻塞狀態,限時20秒 1) "myList" 2) "hello" (6.20s) 如果超時后仍然沒有等到元素,則結束阻塞,返回nil: 127.0.0.1:6379> BLPOP myList 20 (null) (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/AFTER pivot value | 在key 列表中尋找pivot,并在pivot值之前 |
| LLEN key | 獲取列表長度 |
1.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集合中 |
1.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之間的元素 |
2、存儲結構
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文件中,定義了這些結構:
/* 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 */ typedef struct redisObject {unsigned type:4;unsigned encoding:4;unsigned lru:REDIS_LRU_BITS; /* lru time(relative to server.lruclock) */intrefcount;void*ptr; } robj;其中type即Redis支持的邏輯類型,包括:
/* Object types*/ #define REDIS_STRING 0 #define REDIS_LIST 1 #define REDIS_SET 2 #define REDIS_ZSET 3 #define REDIS_HASH 4即前面所列舉的五種數據類型。type定義的只是邏輯類型,encoding才是物理存儲方式,一種邏輯類型可以使用不同的存儲方式,包括:
/* 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 */(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再進行修改。
2.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”等。
2.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。
2.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)
2.4 集合的存儲結構
REDIS_SET有兩種encoding方式: REDIS_ENCODING_INTSET 和 REDIS_ENCODING_HT。
集合的元素類型和數量決定了encoding方式,默認采用REDIS_ENCODING_INTSET ,當滿足以下條件時,轉換為REDIS_ENCODING_HT:
(1)元素類型不是整數
(2)元素個數超過配置的set-max-intset-entries(默認512)
2.5 有序列表的存儲結構
REDIS_ZSET有兩種encoding方式: REDIS_ENCODING_ZIPLIST(同上)和 REDIS_ENCODING_SKIPLIST。
由于有序集合每一個元素包括:
2.6 總結
針對同一種數據類型,Redis會根據元素類型/大小/個數采用不同的編碼方式,不同的編碼方式在內存使用效率/查詢效率上差距巨大,可以通過配置文件調整參數來達到最優。
總結
以上是生活随笔為你收集整理的【Redis学习】Redis数据类型及存储结构的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【深入理解JVM】JVM内存模型
- 下一篇: 【Redis学习】Redis管理命令总结