1. 程式人生 > >redis知識盤點【貳】_五種型別

redis知識盤點【貳】_五種型別

為力爭此係列文章都可以保證一定的乾貨率,所以有別於其他介紹redis的文章,本文將捨棄那些通篇累牘的redis指令,需要時可自行去redis中文網進行查閱。

redis主要支援字串(String)、雜湊(Map)、列表(list)、集合(sets) 和有序集合(sorted sets)五種資料型別,而redis的五種資料型別是對外的,實際內部各種型別還有兩種以上自己的編碼實現,根據不同的場景使用不同的編碼。


可通過下面指令檢視key的型別

type  key ;

此時返回的型別列舉見下表:


其中,每種物件型別又有各自的編碼實現,如下圖:



可通過下面指令檢視key的字元編碼
object  encoding  key ;

此時返回的編碼列舉見下表:


redis首先內部構造了一個名為redisObject的結構體,可用於表示所有型別的物件。結構體中和儲存資料相關的有3個屬性,分別為type(用於儲存物件的型別),encoding(用於儲存物件的編碼)和*ptr(用於指向底層實現資料結構的指標)。具體細化到各種型別的不同實現,下面逐一進行說明。

字串型

內部編碼有如下3種:
int8個位元組的長整型
embstr:小於等於39個位元組的字串
raw:大於39個位元組的字串

其中,embstr和raw都是基於redis的內部抽象型別simple dynamic string (SDS)實現的。SDS封裝了int len(字串長度),int free(buf陣列中未使用位元組的數量)和char buf[](字串組)。SDS

主要解決了C字元陣列最後一位必須是'/0'的問題,redis是通過len來判斷字串長度,所以SDS中可以放空字元。另外,SDS在記憶體分配方面也做了足夠的優化,當對SDS修改後,SDS的長度(len)小於1MB,則分配和len屬性一樣大的未使用空間;如果大於等於1MB,則分配1MB的未使用空間。當刪除部分字元時,刪掉的字元空間不會釋放,而會繼續作為未使用空間。

當對int編碼的字串追加文字字串後,物件會自動轉換為raw編碼;另外由於embstr編碼不支援對內容進行修改,所以對embstr編碼物件執行任何修改命令後,物件都會轉換為raw編碼。


字串型別的應用場景
1.儲存使用者資訊json;
2.計數;
3.共享session;
4.限速(比如傳送簡訊五分鐘限制);

字串get和set時間複雜度為O(1)。

getrange、mget和mset時間複雜度為O(n),n為返回字串個數。



雜湊型

內部編碼2種:
ziplist(壓縮列表):當雜湊型別元素小於hash-max-ziplist-entries(預設512個,配置在.conf檔案中,下同)時、同時所有值都小於hash-max-ziplist-value(預設64位元組)時,redis使用ziplist作為內部實現。優點節省記憶體。
hashtable(雜湊表):當雜湊型別不能滿足ziplist時,redis使用hashtable作為內部實現。

ziplist是redis內部實現的一個抽象模型,其結構如下:

其中,zlbytes用於記錄壓縮列表佔用的記憶體位元組數;zltail用於記錄壓縮列表表尾節點距離起始地址有多少位元組;zllen用於記錄壓縮列表包含的節點數量;entry為壓縮列表各個節點;zlend為圖書值0xFF標記壓縮列表的末端。

entry的結構如下:


其中,previous_entry_length用於記錄前一個節點的長度;encoding用於記錄節點的content儲存資料的型別和長度;content用於儲存節點的值。

當雜湊型物件使用ziplist編碼時,其entry依次儲存元素的key和value。

hashtable抽象模型的結構如下:


字典dict結構中,type是一個紙箱dictType結構的指標,每個dictType結構儲存了一簇用於操作特定型別鍵值對的函式;privdata儲存了需要傳給那些型別特定函式的可選引數;ht包含2個dictht的陣列,一般使用ht[0],ht[1]位rehash時使用;trehashidx記錄rehash進度,沒有進行時為-1。

雜湊表dictht結構中,table指標是指向一個dictEntry陣列,其中每個dictEntry儲存一個鍵值對;size儲存了雜湊表的大小;used記錄了雜湊表目前已有節點的數量;sizemask總是等於size-1,用於決定鍵應該被路由到table陣列哪個索引上。

雜湊表節點dictEntry結構中,key儲存了鍵值對中的鍵;v儲存了鍵值對中的值,可以是一個指標、uint64_t整數或int64_t整數;next用於連線另一個雜湊表節點的指標,hash衝突時使用。

雜湊型別的應用場景
儲存使用者資訊,按field-value形式儲存,只用一個鍵儲存。

雜湊型別hget和hset時間複雜度為O(1),hmget、hmset、hkeys和hvals時間複雜度為O(n)。


列表型

儲存多個有序字串,可以進行兩邊的插入(push)和彈出(pop)。

內部編碼2種:
ziplist,當列表型別元素小於list-max-ziplist-entries(預設512個)時、同時所有值都小於list-max-ziplist-value(預設64位元組)時,redis使用ziplist作為內部實現。優點節省記憶體。

linkedlist,當列表型別無法滿足ziplist時,使用linkedlist。

ziplist的redis實現如上,不再贅述。

linkedlist也是redis自己實現的抽象模型,結構如下:


其中,head是表頭指標;tail是表尾指標;len是連結串列長度計數器;dup函式用於複製連結串列節點鎖儲存的值;free函式用於釋放連結串列節點鎖儲存的值;match函式則用於對比連結串列節點鎖儲存的值和另一個輸入值是否相等。

各ListNode節點之間通過prev和next指標組成雙端連結串列。表頭節點的prev指標和表尾節點的next指標都指向null,對連結串列的訪問以null為終點。


列表型別的應用場景
1.訊息佇列,lpush和brpop即可實現阻塞佇列。生產者客戶端使用lpush在列表左側插入元素,多個消費者使用brpop命令阻塞式搶列表尾部的元素,保證了消費的負載均衡和高可用;
2.文章列表,文章使用雜湊,再放入list中。

列表命令組合使用的口訣
lpush+lpop=stack(棧)
lpush+rpop=Queue(佇列)
lpush+ltrim=Capped Collection(有限集合)
lpush+brpop=Message Queue(訊息佇列)

列表lpush和rpush時間複雜度為O(1)。

linsert和lset時間複雜度為O(n)。對lset來說,n是列表長度,但是如果是列表第一個或最後一個元素,n則為1;對linsert來說,n是列表元素的位置,最壞情況是插在末尾。

lrange時間複雜度為O(s+n),s為從列表的表頭或者表尾到偏移量位置的元素個數,n代表返回的元素總數。



集合型

不允許有重複元素,不能通過索引下標獲取元素。一個集合最多可儲存2^32-1個元素。


內部編碼有2種:
intset(整數集合),當列表型別元素小於set-max-ziplist-entries(預設512個)時,redis使用intset 作為內部實現。優點節省記憶體。

hashtable(雜湊表),當集合型別無法滿足intset時。

intset為redis內部的抽象模型,結構如下:


其中,encoding為contents陣列的屬性,有int16_t、int32_t和int64_4三種;length是整數集合的元素數量;contents為整數集合的底層實現,每個元素都是contents陣列的一個數組項。當陣列中新儲存的數字超過原來的屬性長度時,redis會自動升級陣列屬性;但是一旦升級後就不會降級。

hashtable的redis實現如上,不再贅述。當集合使用hashtable作為底層實現時,value放的是null。


集合型別的應用場景
使用者標籤。

集合命令組合使用的口訣
sadd = 標籤
spop/srandmember = 抽籤
sadd + sinter = 社交需求

集合sadd時間複雜度為O(n),n是需要新增到集合的元素總數。
sismember時間複雜度為O(1)。
smembers時間複雜度為O(n)。
sunion和sunionstore(取並集)時間複雜度為O(n),n是集合的元素總數。
sinter和sinterstore(取交集)時間複雜度為O(n*m),n是最小集合的元素總數,m是集合個數。
sdiff和sdiffstore(取差集)時間複雜度為O(n),n是所有集合的元素總數。
有序集合型

不允許有重複元素,不能通過索引下標獲取元素。有序,以每個元素的score作為排序依據。


內部編碼有2種:
ziplist,當列表型別元素小於zset-max-ziplist-entries(預設128個)時、同時所有值都小於zset-max-ziplist-value(預設64位元組)時,redis使用ziplist作為內部實現。優點節省記憶體。

skiplist(跳躍表),當ziplist不滿足時。

ziplist的redis實現如上,不再贅述。當有序集合使用ziplist作為底層實現時,ziplist依次儲存元素的成員(member)和分數(score)。元素按分值從小到大進行排序,分值小的靠近表頭。

當有序集合採用skiplist編碼實現時,其實是同時使用了skiplisthashtable(字典)來實現。排序的時候讀取skiplist,而查詢成員時讀取hashtable。具體實現時底層資料是同一套,只不過被skiplisthashtable分別引用而已。


有序集合型別的使用場景
排行榜,可以多維度。

有序集合zadd時間複雜度為O(log(n)),n是集合的元素總數。
zinterstore時間複雜度為O(n*k)+O(mlog(m)),n是最小有序集合的元素總數,k是這些求交集的有序集合總數,m是最後返回的有序集合計算結果中元素個數。
zunionstore時間複雜度為O(n)+O(M long(M)),n是所有有序集合大小總數,m是最終有序集合的元素總數。

另外雖然redis官方沒有強制,但是我們一般在開發工作中約定俗成地使用冒號:作為key中各元素的分隔符。以圖書業務場景為例:雜湊型別用book:{id};集合型別用books:{type_id};有序集合用books:{ranking_list_type};整數型別用book:{id}:{property}。


下一篇文章計劃說一下redis的持久化。