Redis 基础——五大类型与数据结构
引言
Redis 區(qū)分于 memcahced 的一個重要不同就是它具有明確的類型概念,在Redis 的使用過程中,都離不開這些類型的學(xué)習(xí),它不僅是 Redis 能力的基礎(chǔ),同時也是一些重要數(shù)據(jù)結(jié)構(gòu)和算法思想的體現(xiàn)。
本博客總結(jié)了五大類型的書面重點(diǎn),幫助快速梳理和總結(jié) Redis 類型相關(guān)的知識點(diǎn),理論性和記憶性較強(qiáng)。可以作為?Redis 數(shù)據(jù)類型的學(xué)習(xí)大綱,建議在實(shí)踐之前牢記這些知識。
一、Redis簡介
在開始之前,回顧一下redis的介紹性知識。
redis的底層語言是C,它是一種高性能鍵值對、NoSQL內(nèi)存數(shù)據(jù)庫。可以用作緩存、數(shù)據(jù)庫、消息中間件、分布式鎖等。
它的幾大優(yōu)點(diǎn):
1、性能優(yōu)秀:內(nèi)存中運(yùn)行,讀寫速度快,支持10W并發(fā)QPS。
2、單進(jìn)程單線程:線程安全,采用IO多路復(fù)用機(jī)制。
3、豐富的數(shù)據(jù)結(jié)構(gòu):五大數(shù)據(jù)類型,類型檢查和命令多態(tài)。
4、數(shù)據(jù)持久化:AOF和RDB,以及混用模式。
5、高可用方案:主從復(fù)制、哨兵模式等。
6、適合多種分布式業(yè)務(wù)場景:消息中間件、分布式鎖、消息訂閱發(fā)布等等。
二、鍵值存儲對象 ——?RedisObject
Redis 中使用 redisObject 對象來表示數(shù)據(jù)庫中的 key(始終是字符串對象)和 value(五種對象類型的任意一種)。
每次在 Redis 中新創(chuàng)建一個鍵值對時,它至少會創(chuàng)建兩個對象(鍵對象、值對象)。
redisObject 包含幾個重要的屬性:
type?屬性記錄對象類型——五大類型;
encoding 屬性記錄對象所使用的編碼,即對象的底層實(shí)現(xiàn)是怎樣的數(shù)據(jù)結(jié)構(gòu);
ptr 屬性指向?qū)ο蟮牡讓訑?shù)據(jù)結(jié)構(gòu),這些數(shù)據(jù)結(jié)構(gòu)由encoding決定;
refcount 屬性記錄對象的引用計(jì)數(shù),redis 可以改變這個值是否 = 0 來決定是否回收對象內(nèi)存;同時,它也是實(shí)現(xiàn)對象共享機(jī)制的基礎(chǔ)。
lru 屬性記錄對象最后一次被命令訪問的時間。
三、五大基本類型
一般我們說Redis中的數(shù)據(jù)類型通常指值對象的類型,因?yàn)殒I始終是字符串對象。
對象類型由 redisObject 中的 type 屬性記錄,它有以下五種類型,可以使用 TYPE 命令查看對象類型:
TYPE keyname五大基本類型:
1、String :最基本的類型,二進(jìn)制安全可以存儲包括圖片等文件格式,編碼方式有raw、int 等。最大能夠存儲512M。
2、Hash :鍵值對集合對象,類似Java 中的 Map,但鍵值都得是字符串。適合存儲對象信息,且支持更改某一項(xiàng)屬性。
3、List :簡單的字符串列表,底層實(shí)現(xiàn)是雙向鏈表。按照插入順序排序,操作分為左右,如 LPUSH、RPUSH等。可以用作消息隊(duì)列模型。
4、Set :無序、不可重復(fù)字符串集合。通過 hashtable實(shí)現(xiàn)。增刪查都是O(1)復(fù)雜度,支持交、并、差集操作。
5、ZSet:有序、不可重復(fù)字符串集合。通過 hashtable 和 skiplist 實(shí)現(xiàn)。可以根據(jù) Double 類型的 score 從小到大排序。
三、八種編碼方式
對象編碼表示 Redis 以何種結(jié)構(gòu)結(jié)構(gòu)存儲數(shù)據(jù),由 redisObject 的 encoding 屬性記錄。
編碼方式并不一定表示某種具體的數(shù)據(jù)結(jié)構(gòu),例如 skiplist 編碼的 ZSet 對象,底層實(shí)際使用了字典+跳躍表的復(fù)合結(jié)構(gòu)。
Redis 為何要將數(shù)據(jù)類型拆分為 type 和 encoding 呢?
對象類型不關(guān)聯(lián)固定的編碼,是為了提升redis的靈活性和效率,同時也有性能方面的考慮。
Redis可以根據(jù)不同的使用場景為一個對象設(shè)置不同的編碼,從而優(yōu)化對象在某一場景下的效率。例如,在列表對象包含元素較少時,使用 ziplist,它比 linkedlist 更節(jié)約內(nèi)存,在內(nèi)存中以連續(xù)的方式保存數(shù)據(jù),可以更快的載入到緩存中。但如果 List 中的元素越來越多,就會使用 linkedlist,它更適合保存大量元素的場景。
編碼方式有 8 種:
int、embstr、raw、hashtable、linkedlist、ziplist、intset、skiplist
可以使用 OBJECT ENCODING k1 來查看 key 的值對象的編碼方式。
它們與類型的關(guān)系如下:
String :int、embstr、raw
Hash :ziplist、hashtable
List :ziplist、linkedlist
Set :intset、hashtable
ZSet? :ziplist、skiplist
四、字符串
39個字節(jié)區(qū)分 raw 和 embstr (<=39)編碼,embstr 編碼是專門用于保存短字符串的一種優(yōu)化編碼方式。
對于某些浮點(diǎn)數(shù)字符串,在執(zhí)行類似 INCRBYFLOAT命令時,會先將類型轉(zhuǎn)化為浮點(diǎn)數(shù),執(zhí)行運(yùn)算操作后,再轉(zhuǎn)換回字符串。
編碼轉(zhuǎn)換 :int 和 embstr 在某些條件下會轉(zhuǎn)為 raw
- int :在執(zhí)行某些命令后,使得值不再是一個整數(shù)(如APPEND),那么編碼會從int轉(zhuǎn)為raw。
-
embstr:它實(shí)際上是只讀的,當(dāng)對embstr執(zhí)行修改時,Redis 一定會將其轉(zhuǎn)為 raw,再執(zhí)行修改命令。
五、哈希
Hash 對象的編碼可以是 ziplist 或 hashtable。
ziplist:是一種連續(xù)的數(shù)據(jù)結(jié)構(gòu),類似數(shù)組,當(dāng)以 ziplist 存儲鍵值對時,它們會以 k-v-k-v... 的形式間隔存入,因此同一鍵值對的key和value總是緊挨著的。
hashtable:意為“字典”,它以數(shù)組保存所有鍵值對,每對鍵值都被封裝為一個叫 entry 的結(jié)構(gòu),這與Java中的HashMap非常類似。
編碼轉(zhuǎn)換:當(dāng)所有鍵和值的字符串長度小于64字節(jié),且鍵值對數(shù)量小于512個時,使用ziplist編碼;否則,使用hashtable編碼。當(dāng)然,這兩個條件的上限是可以修改的。
六、列表
List 對象的編碼可以是 ziplist 或 linkedlist。
ziplist:同 hash類型。
linkedlist:是一種雙端鏈表結(jié)構(gòu),每個鏈表節(jié)點(diǎn)都會包含一個字符串對象,這是一種嵌套字符串行為,字符串對象是Redis五種類型中唯一一種會被其他四種類型對象嵌套的對象。
也就是說,五種類型對象的鍵和值都只能是字符串相關(guān)的數(shù)據(jù)結(jié)構(gòu)。
?編碼轉(zhuǎn)換:當(dāng)所有元素長度都小于64字節(jié),且元素個數(shù)小于512個時,使用ziplist;否則,使用 linkedlist。限制條件與hash對象是相同的。
七、集合
Set 對象的編碼可以是 intset 或 hashtable。
intset:代表一個整數(shù)集合。
hashtable:在實(shí)現(xiàn)set時,字典的每個鍵保存了一個元素,而字典的值全部都為NULL。在 Java 中,也會使用 HashMap 來實(shí)現(xiàn) HashSet,不過,在Java中,為了避免空指針,每個 Entry 的值并不是 null,而是都指向了同一個空的 Object。
?編碼轉(zhuǎn)換:當(dāng)所有元素都是整數(shù),且元素個數(shù)不超過512個時,使用 intset;否則,使用 hashtable。
八、有序集合
ZSet 對象的編碼可以是 ziplist 或 skiplist。
ziplist:每個集合元素使用兩個緊挨在一起的節(jié)點(diǎn)來保存,前節(jié)點(diǎn)保存元素的成員,后節(jié)點(diǎn)保存分?jǐn)?shù)score。
skiplist:是一個復(fù)合結(jié)構(gòu)——字典 + 跳躍表。它們會引用共享的數(shù)據(jù),不會造成重復(fù)存儲的情況。跳躍表可以按分?jǐn)?shù)順序存儲所有元素,程序可以基于此對有序集合進(jìn)行范圍操作:ZRANK、ZRANGE等;
字典創(chuàng)建了從成員到分?jǐn)?shù)的映射,程序可以用O(1)查找給定成員的分?jǐn)?shù),如:ZSCORE等。
為什么ZSet 要同時使用跳躍表和字典來實(shí)現(xiàn)呢?
單獨(dú)使用其中一種都達(dá)不到同時使用兩種結(jié)構(gòu)的性能。可以說這兩種結(jié)構(gòu)的結(jié)合彌補(bǔ)了有序結(jié)構(gòu)在查找與范圍搜索上的先天不足。因此,為了讓有序集合在查找和范圍操作都盡可能快,Redis 選擇了同時使用字典和跳躍表兩種數(shù)據(jù)結(jié)構(gòu)來實(shí)現(xiàn)ZSet。
編碼的轉(zhuǎn)換:當(dāng)元素個數(shù)小于128,所有元素長度都小于64字節(jié)時,使用ziplist;否則使用skiplist編碼。?
九、Redis 的類型檢查和命令多態(tài)
Redis中用于操作key的命令可以分為兩類:對全部類型都可用和只對特定類型可用。
全部類型可用:DEL、EXPIRE、RENAME、TYPE、OBJECT等。
特定類型可用:SET、GET、APPEND、HDEL、RPUSH、SADD、ZADD等等。
9.1 類型檢查的實(shí)現(xiàn)
為了保證只有特定類型的key可以執(zhí)行某些特定命令,在執(zhí)行特定命令之前,redis會先檢查key 所對應(yīng)的value的類型,然后決定是否執(zhí)行。這種類型檢查是通過redisObject的type屬性來實(shí)現(xiàn)的。
9.2 多態(tài)命令的實(shí)現(xiàn)
redis會根據(jù)值對象的編碼,選擇正確的“命令實(shí)現(xiàn)代碼”來執(zhí)行命令。
例如,List 的編碼有 ziplist 和 linkedlist 兩種,Redis 會根據(jù)encoding的不同,在執(zhí)行LLEN命令時,考慮使用ziplistlen函數(shù)還是使用listlength函數(shù)。
以面向?qū)ο蟮男g(shù)語來說,LLEN命令是多態(tài)的。
十、內(nèi)存回收與對象共享
Redis 使用引用計(jì)數(shù)來實(shí)現(xiàn)對象內(nèi)存空間的回收。
每個值對象上都有一個引用計(jì)數(shù)——refcount,Redis 可以通過增加或減少引用計(jì)數(shù)來實(shí)現(xiàn)內(nèi)存回收和對象共享。
十一、對象的空轉(zhuǎn)時長
除了type、encoding、ptr、refcount等屬性外,redisObject還有一個屬性lru。
lru屬性記錄了對象最后一次被命令訪問的時間。
OBJECT IDLETIME k1 // 該命令可以查看鍵的空轉(zhuǎn)時長,這是通過將當(dāng)前時間減去值對象的lru時間計(jì)算得出的。
當(dāng)redis設(shè)置了maxmemory選項(xiàng),且內(nèi)存回收算法設(shè)置為volatile-lru或allkeys-lru,那么當(dāng)內(nèi)存超過maxmemory時,空轉(zhuǎn)時間較高的那部分key會優(yōu)先被服務(wù)器釋放。
總結(jié)
以上是生活随笔為你收集整理的Redis 基础——五大类型与数据结构的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php合成图片设置图片大小,php 上传
- 下一篇: Android日志[进阶篇]一-使用 L