配置文件存int类型_Redis详解(五)------ redis的五大数据类型实现原理
> 本系列教程持續(xù)更新,可以微信搜索「 IT可樂 」第一時間閱讀。回復(fù)《電子書》有我為大家特別篩選的書籍資料
前面兩篇博客,第一篇介紹了五大數(shù)據(jù)類型的基本用法,第二篇介紹了Redis底層的六種數(shù)據(jù)結(jié)構(gòu)。在Redis中,并沒有直接使用這些數(shù)據(jù)結(jié)構(gòu)來實現(xiàn)鍵值對數(shù)據(jù)庫,而是基于這些數(shù)據(jù)結(jié)構(gòu)創(chuàng)建了一個對象系統(tǒng),這些對象系統(tǒng)也就是前面說的五大數(shù)據(jù)類型,每一種數(shù)據(jù)類型都至少用到了一種數(shù)據(jù)結(jié)構(gòu)。通過這五種不同類型的對象,Redis可以在執(zhí)行命令之前,根據(jù)對象的類型判斷一個對象是否可以執(zhí)行給定的命令,而且可以針對不同的場景,為對象設(shè)置多種不同的數(shù)據(jù)結(jié)構(gòu),從而優(yōu)化對象在不同場景下的使用效率。
1、對象的類型與編碼
Redis使用前面說的五大數(shù)據(jù)類型來表示鍵和值,每次在Redis數(shù)據(jù)庫中創(chuàng)建一個鍵值對時,至少會創(chuàng)建兩個對象,一個是鍵對象,一個是值對象,而Redis中的每個對象都是由 redisObject 結(jié)構(gòu)來表示:
typedef struct redisObject{//類型unsigned type:4;//編碼unsigned encoding:4;//指向底層數(shù)據(jù)結(jié)構(gòu)的指針void *ptr;//引用計數(shù)int refcount;//記錄最后一次被程序訪問的時間unsigned lru:22;}robj①、type屬性 對象的type屬性記錄了對象的類型,這個類型就是前面講的五大數(shù)據(jù)類型:
可以通過如下命令來判斷對象類型:
type key注意:在Redis中,鍵總是一個字符串對象,而值可以是字符串、列表、集合等對象,所以我們通常說的鍵為字符串鍵,表示的是這個鍵對應(yīng)的值為字符串對象,我們說一個鍵為集合鍵時,表示的是這個鍵對應(yīng)的值為集合對象。
*②、encoding 屬性和 prt 指針 對象的 prt 指針指向?qū)ο蟮讓拥臄?shù)據(jù)結(jié)構(gòu),而數(shù)據(jù)結(jié)構(gòu)由 encoding 屬性來決定。
而每種類型的對象都至少使用了兩種不同的編碼:
可以通過如下命令查看值對象的編碼:
OBJECT ENCODING key比如 string 類型:(可以是 embstr編碼的簡單字符串或者是 int 整數(shù)值實現(xiàn))
2、字符串對象
字符串是Redis最基本的數(shù)據(jù)類型,不僅所有key都是字符串類型,其它幾種數(shù)據(jù)類型構(gòu)成的元素也是字符串。注意字符串的長度不能超過512M。
①、編碼
字符串對象的編碼可以是int,raw或者embstr。
1、int 編碼:保存的是可以用 long 類型表示的整數(shù)值。
2、raw 編碼:保存長度大于44字節(jié)的字符串(redis3.2版本之前是39字節(jié),之后是44字節(jié))。
3、embstr 編碼:保存長度小于44字節(jié)的字符串(redis3.2版本之前是39字節(jié),之后是44字節(jié))。
由上可以看出,int 編碼是用來保存整數(shù)值,raw編碼是用來保存長字符串,而embstr是用來保存短字符串。其實 embstr 編碼是專門用來保存短字符串的一種優(yōu)化編碼,raw 和 embstr 的區(qū)別:
embstr與raw都使用redisObject和sds保存數(shù)據(jù),區(qū)別在于,embstr的使用只分配一次內(nèi)存空間(因此redisObject和sds是連續(xù)的),而raw需要分配兩次內(nèi)存空間(分別為redisObject和sds分配空間)。因此與raw相比,embstr的好處在于創(chuàng)建時少分配一次空間,刪除時少釋放一次空間,以及對象的所有數(shù)據(jù)連在一起,尋找方便。而embstr的壞處也很明顯,如果字符串的長度增加需要重新分配內(nèi)存時,整個redisObject和sds都需要重新分配空間,因此redis中的embstr實現(xiàn)為只讀。
ps:Redis中對于浮點數(shù)類型也是作為字符串保存的,在需要的時候再將其轉(zhuǎn)換成浮點數(shù)類型。
②、編碼的轉(zhuǎn)換
當(dāng) int 編碼保存的值不再是整數(shù),或大小超過了long的范圍時,自動轉(zhuǎn)化為raw。
對于 embstr 編碼,由于 Redis 沒有對其編寫任何的修改程序(embstr 是只讀的),在對embstr對象進(jìn)行修改時,都會先轉(zhuǎn)化為raw再進(jìn)行修改,因此,只要是修改embstr對象,修改后的對象一定是raw的,無論是否達(dá)到了44個字節(jié)。
3、列表對象
list 列表,它是簡單的字符串列表,按照插入順序排序,你可以添加一個元素到列表的頭部(左邊)或者尾部(右邊),它的底層實際上是個鏈表結(jié)構(gòu)。
①、編碼
列表對象的編碼可以是 ziplist(壓縮列表) 和 linkedlist(雙端鏈表)。 關(guān)于鏈表和壓縮列表的特性可以看我前面的這篇博客。
比如我們執(zhí)行以下命令,創(chuàng)建一個 key = ‘numbers’,value = ‘1 three 5’ 的三個值的列表。
rpush numbers 1 "three" 5ziplist 編碼表示如下:
linkedlist表示如下:
②、編碼轉(zhuǎn)換
當(dāng)同時滿足下面兩個條件時,使用ziplist(壓縮列表)編碼:
1、列表保存元素個數(shù)小于512個
2、每個元素長度小于64字節(jié)
不能滿足這兩個條件的時候使用 linkedlist 編碼。
上面兩個條件可以在redis.conf 配置文件中的 list-max-ziplist-value選項和 list-max-ziplist-entries 選項進(jìn)行配置。
4、哈希對象
哈希對象的鍵是一個字符串類型,值是一個鍵值對集合。
①、編碼
哈希對象的編碼可以是 ziplist 或者 hashtable。
當(dāng)使用ziplist,也就是壓縮列表作為底層實現(xiàn)時,新增的鍵值對是保存到壓縮列表的表尾。比如執(zhí)行以下命令:
hset profile name "Tom" hset profile age 25 hset profile career "Programmer"如果使用ziplist,profile 存儲如下:
當(dāng)使用 hashtable 編碼時,上面命令存儲如下:
hashtable 編碼的哈希表對象底層使用字典數(shù)據(jù)結(jié)構(gòu),哈希對象中的每個鍵值對都使用一個字典鍵值對。
在前面介紹壓縮列表時,我們介紹過壓縮列表是Redis為了節(jié)省內(nèi)存而開發(fā)的,是由一系列特殊編碼的連續(xù)內(nèi)存塊組成的順序型數(shù)據(jù)結(jié)構(gòu),相對于字典數(shù)據(jù)結(jié)構(gòu),壓縮列表用于元素個數(shù)少、元素長度小的場景。其優(yōu)勢在于集中存儲,節(jié)省空間。
②、編碼轉(zhuǎn)換
和上面列表對象使用 ziplist 編碼一樣,當(dāng)同時滿足下面兩個條件時,使用ziplist(壓縮列表)編碼:
1、列表保存元素個數(shù)小于512個
2、每個元素長度小于64字節(jié)
不能滿足這兩個條件的時候使用 hashtable 編碼。第一個條件可以通過配置文件中的 set-max-intset-entries 進(jìn)行修改。
5、集合對象
集合對象 set 是 string 類型(整數(shù)也會轉(zhuǎn)換成string類型進(jìn)行存儲)的無序集合。注意集合和列表的區(qū)別:集合中的元素是無序的,因此不能通過索引來操作元素;集合中的元素不能有重復(fù)。
①、編碼
集合對象的編碼可以是 intset 或者 hashtable。
intset 編碼的集合對象使用整數(shù)集合作為底層實現(xiàn),集合對象包含的所有元素都被保存在整數(shù)集合中。
hashtable 編碼的集合對象使用 字典作為底層實現(xiàn),字典的每個鍵都是一個字符串對象,這里的每個字符串對象就是一個集合中的元素,而字典的值則全部設(shè)置為 null。這里可以類比Java集合中HashSet 集合的實現(xiàn),HashSet 集合是由 HashMap 來實現(xiàn)的,集合中的元素就是 HashMap 的key,而 HashMap 的值都設(shè)為 null。
SADD numbers 1 3 5SADD Dfruits "apple" "banana" "cherry"②、編碼轉(zhuǎn)換
當(dāng)集合同時滿足以下兩個條件時,使用 intset 編碼:
1、集合對象中所有元素都是整數(shù)
2、集合對象所有元素數(shù)量不超過512
不能滿足這兩個條件的就使用 hashtable 編碼。第二個條件可以通過配置文件的 set-max-intset-entries 進(jìn)行配置。
6、有序集合對象
和上面的集合對象相比,有序集合對象是有序的。與列表使用索引下標(biāo)作為排序依據(jù)不同,有序集合為每個元素設(shè)置一個分?jǐn)?shù)(score)作為排序依據(jù)。
①、編碼
有序集合的編碼可以是 ziplist 或者 skiplist。
ziplist 編碼的有序集合對象使用壓縮列表作為底層實現(xiàn),每個集合元素使用兩個緊挨在一起的壓縮列表節(jié)點來保存,第一個節(jié)點保存元素的成員,第二個節(jié)點保存元素的分值。并且壓縮列表內(nèi)的集合元素按分值從小到大的順序進(jìn)行排列,小的放置在靠近表頭的位置,大的放置在靠近表尾的位置。
ZADD price 8.5 apple 5.0 banana 6.0 cherryskiplist 編碼的有序集合對象使用 zet 結(jié)構(gòu)作為底層實現(xiàn),一個 zset 結(jié)構(gòu)同時包含一個字典和一個跳躍表:
typedef struct zset{//跳躍表zskiplist *zsl;//字典dict *dice; } zset;字典的鍵保存元素的值,字典的值則保存元素的分值;跳躍表節(jié)點的 object 屬性保存元素的成員,跳躍表節(jié)點的 score 屬性保存元素的分值。
這兩種數(shù)據(jù)結(jié)構(gòu)會通過指針來共享相同元素的成員和分值,所以不會產(chǎn)生重復(fù)成員和分值,造成內(nèi)存的浪費。
說明:其實有序集合單獨使用字典或跳躍表其中一種數(shù)據(jù)結(jié)構(gòu)都可以實現(xiàn),但是這里使用兩種數(shù)據(jù)結(jié)構(gòu)組合起來,原因是假如我們單獨使用 字典,雖然能以 O(1) 的時間復(fù)雜度查找成員的分值,但是因為字典是以無序的方式來保存集合元素,所以每次進(jìn)行范圍操作的時候都要進(jìn)行排序;假如我們單獨使用跳躍表來實現(xiàn),雖然能執(zhí)行范圍操作,但是查找操作有 O(1)的復(fù)雜度變?yōu)榱薕(logN)。因此Redis使用了兩種數(shù)據(jù)結(jié)構(gòu)來共同實現(xiàn)有序集合。
②、編碼轉(zhuǎn)換
當(dāng)有序集合對象同時滿足以下兩個條件時,對象使用 ziplist 編碼:
1、保存的元素數(shù)量小于128;
2、保存的所有元素長度都小于64字節(jié)。
不能滿足上面兩個條件的使用 skiplist 編碼。以上兩個條件也可以通過Redis配置文件zset-max-ziplist-entries 選項和 zset-max-ziplist-value 進(jìn)行修改。
7、五大數(shù)據(jù)類型的應(yīng)用場景
對于string 數(shù)據(jù)類型,因為string 類型是二進(jìn)制安全的,可以用來存放圖片,視頻等內(nèi)容,另外由于Redis的高性能讀寫功能,而string類型的value也可以是數(shù)字,可以用作計數(shù)器(INCR,DECR),比如分布式環(huán)境中統(tǒng)計系統(tǒng)的在線人數(shù),秒殺等。
對于 hash 數(shù)據(jù)類型,value 存放的是鍵值對,比如可以做單點登錄存放用戶信息。
對于 list 數(shù)據(jù)類型,可以實現(xiàn)簡單的消息隊列,另外可以利用lrange命令,做基于redis的分頁功能
對于 set 數(shù)據(jù)類型,由于底層是字典實現(xiàn)的,查找元素特別快,另外set 數(shù)據(jù)類型不允許重復(fù),利用這兩個特性我們可以進(jìn)行全局去重,比如在用戶注冊模塊,判斷用戶名是否注冊;另外就是利用交集、并集、差集等操作,可以計算共同喜好,全部的喜好,自己獨有的喜好等功能。
對于 zset 數(shù)據(jù)類型,有序的集合,可以做范圍查找,排行榜應(yīng)用,取 TOP N 操作等。
8、內(nèi)存回收和內(nèi)存共享
①、內(nèi)存回收 前面講 Redis 的每個對象都是由 redisObject 結(jié)構(gòu)表示:
typedef struct redisObject{//類型unsigned type:4;//編碼unsigned encoding:4;//指向底層數(shù)據(jù)結(jié)構(gòu)的指針void *ptr;//引用計數(shù)int refcount;//記錄最后一次被程序訪問的時間unsigned lru:22;}robj其中關(guān)鍵的 type屬性,encoding 屬性和 ptr 指針都介紹過了,那么 refcount 屬性是干什么的呢?
因為 C 語言不具備自動回收內(nèi)存功能,那么該如何回收內(nèi)存呢?于是 Redis自己構(gòu)建了一個內(nèi)存回收機(jī)制,通過在 redisObject 結(jié)構(gòu)中的 refcount 屬性實現(xiàn)。這個屬性會隨著對象的使用狀態(tài)而不斷變化:
1、創(chuàng)建一個新對象,屬性 refcount 初始化為1
2、對象被一個新程序使用,屬性 refcount 加 1
3、對象不再被一個程序使用,屬性 refcount 減 1
4、當(dāng)對象的引用計數(shù)值變?yōu)?0 時,對象所占用的內(nèi)存就會被釋放。
在 Redis 中通過如下 API 來實現(xiàn):
學(xué)過Java的應(yīng)該知道,引用計數(shù)的內(nèi)存回收機(jī)制其實是不被Java采用的,因為不能克服循環(huán)引用的例子(比如 A 具有 B 的引用,B 具有 C 的引用,C 具有 A 的引用,除此之外,這三個對象沒有任何用處了),這時候 A B C 三個對象會一直駐留在內(nèi)存中,造成內(nèi)存泄露。那么 Redis 既然采用引用計數(shù)的垃圾回收機(jī)制,如何解決這個問題呢?
在前面介紹 redis.conf 配置文件時,在 MEMORY MANAGEMENT 下有個 maxmemory-policy 配置:
maxmemory-policy :當(dāng)內(nèi)存使用達(dá)到最大值時,redis使用的清楚策略。有以下幾種可以選擇:
1)volatile-lru 利用LRU算法移除設(shè)置過過期時間的key (LRU:最近使用 Least Recently Used )
2)allkeys-lru 利用LRU算法移除任何key
3)volatile-random 移除設(shè)置過過期時間的隨機(jī)key
4)allkeys-random 移除隨機(jī)key
5)volatile-ttl 移除即將過期的key(minor TTL)
6)noeviction noeviction 不移除任何key,只是返回一個寫錯誤 ,默認(rèn)選項
通過這種配置,也可以對內(nèi)存進(jìn)行回收。 ②、內(nèi)存共享 refcount 屬性除了能實現(xiàn)內(nèi)存回收以外,還能用于內(nèi)存共享。
比如通過如下命令 set k1 100,創(chuàng)建一個鍵為 k1,值為100的字符串對象,接著通過如下命令 set k2 100 ,創(chuàng)建一個鍵為 k2,值為100 的字符串對象,那么 Redis 是如何做的呢?
1、將數(shù)據(jù)庫鍵的值指針指向一個現(xiàn)有值的對象
2、將被共享的值對象引用refcount 加 1
注意:Redis的共享對象目前只支持整數(shù)值的字符串對象。之所以如此,實際上是對內(nèi)存和CPU(時間)的平衡:共享對象雖然會降低內(nèi)存消耗,但是判斷兩個對象是否相等卻需要消耗額外的時間。對于整數(shù)值,判斷操作復(fù)雜度為O(1);對于普通字符串,判斷復(fù)雜度為O(n);而對于哈希、列表、集合和有序集合,判斷的復(fù)雜度為O(n^2)。
雖然共享對象只能是整數(shù)值的字符串對象,但是5種類型都可能使用共享對象(如哈希、列表等的元素可以使用)。
9、對象的空轉(zhuǎn)時長
在 redisObject 結(jié)構(gòu)中,前面介紹了 type、encoding、ptr 和 refcount 屬性,最后一個 lru 屬性,該屬性記錄了對象最后一次被命令程序訪問的時間。
使用 OBJECT IDLETIME 命令可以打印給定鍵的空轉(zhuǎn)時長,通過將當(dāng)前時間減去值對象的 lru 時間計算得到。
lru 屬性除了計算空轉(zhuǎn)時長以外,還可以配合前面內(nèi)存回收配置使用。如果Redis打開了maxmemory選項,且內(nèi)存回收算法選擇的是volatile-lru或allkeys—lru,那么當(dāng)Redis內(nèi)存占用超過maxmemory指定的值時,Redis會優(yōu)先選擇空轉(zhuǎn)時間最長的對象進(jìn)行釋放。
參考文章:《Redis設(shè)計與實現(xiàn)》
本系列教程持續(xù)更新,可以微信搜索「 IT可樂 」第一時間閱讀。回復(fù)《電子書》有我為大家特別篩選的書籍資料總結(jié)
以上是生活随笔為你收集整理的配置文件存int类型_Redis详解(五)------ redis的五大数据类型实现原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: smith标准型_线性系统理论(八)多项
- 下一篇: sparkstreaming 读取mys