Redis的设计与实现之对象
Redis中對象概述
在前面說的是一些Redis的底層數據結構,那些數據結構是對象實現的基礎。在Reids的對象系統中有五種類型的對象。
- 字符串對象
- 列表對象
- 哈希對象
- 集合對象
- 有序集合對象
在Java中也有對象,也可以將這些對象看作為相對獨立的個體。還有一個和Java中很相似的地方就是Redis對象系統實現了內存回收機制(基于引用計數技術),當程序不需要的話就會被自動釋放。Redis還可以在引用計數技術下,在適當的條件,可以讓多個數據庫鍵共享同一個對象來節約資源。在Redis對象中還有比較好的一點是,Redis對象帶有訪問事件的記錄信息(可以用作我們后邊將會講到的計算空轉)。
在了解具體對象之前,先來了解以下對象的主要數據結構。
對象的數據結構
typedef struct redisObject{//類型unsigned type;//編碼unsigned encoding;//指向底層數據結構的指針void *ptr;.... }比如我們在數據庫加入一個鍵值對,在這個過程中至少會創建兩個對象。
- type:記錄著這個對象是什么類型的對象。也就是開篇中提到的五種對象之一。
- encoding:在Redis中有一些編碼常量被用來表示不同對象類型。基于任意一種type,type和encoding之間的一些組合。這些后邊也會看到。比如:set類型可以和intset組合就是使用整數集合實現的集合對象。
- ptr:指向對象實現底層的數據結構,這些數據結構由encoding來決定。
type和encoding的區別是:type展示的是最外層的形態,encoding掌握的是底層的原子實現。底層由encoding來掌控可以增加Redis的靈活性,因為如果數據結構的決定因素由多個組成,那就很難分配。例如:在列表對象包含的元素比較少的時候,它的encoding是壓縮列表,當它大的時候,encoding就會變為雙端鏈表。這大大提高的靈活性。
深入了解Redis的五大對象
字符串對象
字符串對象的編碼可以是int,raw,embstr。
-
int如果字符串對象里保存的是整數。
-
這個整數可以用long類型的來表示,那么ptr的返回值會從void變為long,并將字符串對象的編碼設置為int。
-
如果超過long的長度就是embstr和raw類型。
-
embstr:當保存的字符串長度小于等于39字節,那字符串對象將使用embstr編碼的方式來保存。
-
embstr是專門用來保存短字符串的優化編碼方式,它采用的是使用一塊連續的內存空間依次保存redisObject和sdshdr。而raw就不用,它要連續的分配兩次內存空間分別給redisobject和sdshdr。
-
釋放embstr因此也只需要釋放一次資源。
-
字符串對象的所有數據保存在連續的單元里,能夠更好的利用緩存帶來的優勢。
-
raw:當字符串長度大于39,那字符串對象使用SDS來保存這個對象,并將編碼設置為raw。
-
在前面對SDS已經了解它的底層了,SDS對于embstr的優勢就是它可以存儲長度很長的字符串,如果embstr存儲了超過它極限的字符串它就得重新分配資源轉為SDS。
字符串對象編碼的轉換
- 對于int和embstr,如果滿足轉換條件就會轉為raw。
- 對int類型執行一些命令(如append一個字符或者字符串)就從int變為raw。
- 對embstr執行任何的修改命令時,都會從embstr變為raw。
列表對象
列表對象的編碼可以是ziplist,linkedlist。
- ziplist:使用壓縮列表作為底層實現。
- 列表對象保存的所有字符串長度都小于64字節。
- 列表對象保存的元素數量小于512個。
- linkedlist:使用雙向鏈表作為底層實現,每一個節點都是一個字符串對象。
- 不能滿足ziplist的就使用linklist作為對象的編碼。
例如對一個短的字符串擴充為長度字符串,或者增加節點數量都有可能使列表對象的編碼方式發生變化。
ziplist的極限條件是可以變的,可以在配置文件中設置。
哈希對象
哈希對象的編碼可以是ziplist,hashtable。
- ziplist:
- 當有新鍵值對進來的時候,先把鍵節點推入壓縮列表表尾,然后再把值節點推入哈希列表表尾。鍵的后邊緊跟著值。先進的在列表的表頭,后進的在表尾。
- 列表對象保存的所有字符串長度都小于64字節。
- 列表對象保存的元素數量小于512個。
- hashlist:使用字典作為底層實現。
- 不能滿足ziplist就轉為hashlist。
ziplist的條件都一樣,當然也可以和上邊一樣在配置文件中修改。
集合對象
集合對象可以是intset,hashtable。
- intset:使用整數集合作為底層實現。
- 集合中保存的必須都是整數。
- 集合對象保存的元素小于等于512。
- hashtable:使用字典作為底層實現,每一個鍵都是一個字符串對象,值都為空。也就是將所有的鍵作為一個字符串集合。
- 不能滿足instset條件的都使用字典作為底層實現。
集合對象保存的元素數量可以更改。
有序集合對象
有序集合的編碼可以是ziplist,skiplist。
-
ziplist:使用壓縮列表作為底層實現。每個集合元素緊挨在一起,第一個是元素成員(member)緊跟其后的是分值(score)。
-
壓縮列表按分值大小進行排序,分值小的靠近表頭。
-
有序集合保存的元素數量小于128
-
有序集合保存的所有元素長度都小于64字節。
-
skiplist:使用zset作為底層實現。zset的結構體中同時包含一個字典和一個跳躍表。主要用于排序。
-
在zset中,zsl按照分值由大到小保存了所有的元素,每個跳躍表節點都保存了一個元素(跳躍表節點的obj保存的是元素成員,score保存的分值)。
-
在zset中dict字典為有序集合創建了一個從成員到分值的映射,字典中每一個鍵值對都保存一個元素。主要用于查找。
-
如果不能滿足ziplist的所有要求,那就用zset作為底層實現的數據結構。
設計者很精巧的設計了這一方案,各取所長,用指針來共享相同元素和分值,所以這樣不會產生重復元素,并不會浪費太多的空間。
前面介紹完Redis的五大對象,那如果對不同對象進行不同的命令操作會怎么樣?
類型的檢測
可以籠統的把Redis中的命令分為兩種
如下操作
-
set,get,append,strlen等只能對字符串鍵執行
-
hdel,hset,hget,hlen等只能對哈希鍵執行,用別的操作也不能操作哈希鍵。如下圖
-
rpush,lpop,linsert,llen,lrange等只能對列表鍵使用
-
sadd,spop,sinsert,scard等命名只能對集合鍵使用
-
zadd,zcard,zrank,zscore等只能對有序集合鍵使用
類型檢測的實現流程
多態命令
什么是多態命令?
多態命令就是Reids除了根據值的對象類型來判斷指令的能否執行之外,也還會根據值對象的encoding來選擇命令是否可執行。
多態命令的執行流程
打個比方就是這個命令相當于是一個總管,給下面的人要收他們中某一個人這一個月的銷售報告,不歸他管的人就走開了,歸他管的人就各自找他們的銷售報告,相當于總管這個命令對他的下屬都有效。
內存回收
前面說到過Redis使用引用計數技術和LRU(最近最久未使用算法)實現的垃圾回收。
- 當創建一個新的對象時,計數的值會被初始化為1
- 當對象被一個新的程序使用,計數的值+1
- 當對象不再被程序使用時,計數的值會-1
- 當值變為0時,就會被釋放掉
雖然,原理很簡單,但是實現的過程也是挺麻煩的呢。比如怎么能認為程序不會再使用這個對象了呢?
算法的實現采用的是HashMap+Double LinkedList。
詳情還得看大佬寫的文章https://www.cnblogs.com/WJ5888/p/4371647.html
對象共享
什么是對象共享?
就是一段內存存的值,被多個對象使用。
Redis中對象共享的步驟
這個也正好和垃圾回收對接上了。
比如下邊這個例子: refcount是查看引用計數器的值,可以看到在共享后,refcount就+1,刪除后refcount就-1.
這些共享不止字符串鍵可以使用,那些嵌套了字符串鍵的其他對象也可以使用。但是Redis中不共享字符串的對象,字符串比對需要的時間復雜度高,而且如果這個對象內含多個字符串對象那驗證的事件復雜度將會是O(ne2)
對象的空轉時長
還記得在前面在說對象的時候說的lru屬性,它是用來記錄最后一次被命令訪問的事件。這也就可也順理成章的和前面說的LRU垃圾回收相應了。可以通過object idletime 來查看空轉的時間(now-lastTime),然后根據空轉的時長來判斷是否進行回收,前提是服務器打開maxmemory選項,超過上限時,會將空轉時間長的進行回收。
總結
以上是生活随笔為你收集整理的Redis的设计与实现之对象的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Redis的设计与实现之整数集合和压缩列
- 下一篇: Redis设计与实现RDB持久化