一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

Mysql|Sql Server|Oracle|Redis|MongoDB|PostgreSQL|Sqlite|DB2|mariadb|Access|數(shù)據(jù)庫技術(shù)|

服務(wù)器之家 - 數(shù)據(jù)庫 - Redis - 壓縮列表犧牲速度來節(jié)省內(nèi)存,Redis是膨脹了嗎

壓縮列表犧牲速度來節(jié)省內(nèi)存,Redis是膨脹了嗎

2021-07-26 16:21雙子孤狼 Redis

這篇文章主要給大家解釋了Redis 當(dāng)中的 ziplist(壓縮列表)犧牲速度來節(jié)省內(nèi)存的原因,希望大家能夠喜歡

正常情況下我們選擇使用 redis 就是為了提升查詢速度,然而讓人意外的是,redis 當(dāng)中卻有一種比較有意思的數(shù)據(jù)結(jié)構(gòu),這種數(shù)據(jù)結(jié)構(gòu)通過犧牲部分讀寫速度來達(dá)到節(jié)省內(nèi)存的目的,這就是 ziplist(壓縮列表),redis 為什么要這么做呢?難道真的是覺得自己的速度太快了,犧牲一點速度也不影響嗎?

什么是壓縮列表

 

ziplist 是為了節(jié)省內(nèi)存而設(shè)計出來的一種數(shù)據(jù)結(jié)構(gòu)。ziplist 是由一系列特殊編碼組成的連續(xù)內(nèi)存塊的順序型數(shù)據(jù)結(jié)構(gòu),一個 ziplist 可以包含任意多個 entry,而每一個 entry 又可以保存一個字節(jié)數(shù)組或者一個整數(shù)值。

ziplist 作為一種列表,其和普通的雙端列表,如 linkedlist 的最大區(qū)別就是 ziplist 并不存儲前后節(jié)點的指針,而 linkedlist 一般每個節(jié)點都會維護(hù)一個指向前置節(jié)點和一個指向后置節(jié)點的指針。那么 ziplist 不維護(hù)前后節(jié)點的指針,它又是如何尋找前后節(jié)點的呢?

ziplist 雖然不維護(hù)前后節(jié)點的指針,但是它卻維護(hù)了上一個節(jié)點的長度和當(dāng)前節(jié)點的長度,然后每次通過長度來計算出前后節(jié)點的位置。既然涉及到了計算,那么相對于直接存儲指針的方式肯定有性能上的損耗,這就是一種典型的用時間來換取空間的做法。因為每次讀取前后節(jié)點都需要經(jīng)過計算才能得到前后節(jié)點的位置,所以會消耗更多的時間,而在 redis 中,一個指針是占了 8 個字節(jié),但是大部分情況下,如果直接存儲長度是達(dá)不到 8 個字節(jié)的,所以采用存儲長度的設(shè)計方式在大部分場景下是可以節(jié)省內(nèi)存空間的。

ziplist 的存儲結(jié)構(gòu)

 

ziplist 的組成結(jié)構(gòu)為:

?
1
<zlbytes> <zltail> <zllen> <entry> <entry> ... <entry> <zlend>

其中 zlbyteszltailzllenziplisthead 部分,entryziplistentries 部分,每一個 entry 代表一個數(shù)據(jù),最后 zlend 表示 ziplistend 部分,如下圖所示:

壓縮列表犧牲速度來節(jié)省內(nèi)存,Redis是膨脹了嗎

ziplist 中每個屬性代表的含義如下表格所示:

 

 

屬性 類型 長 度 說明
zlbytes uint32_t 4字節(jié) 記錄壓縮列表占用內(nèi)存字節(jié)數(shù)(包括本身所占用的 4 個字節(jié))。
zltail uint32_t 4字節(jié) 記錄壓縮列表尾節(jié)點距離壓縮列表的起始地址有多少個字節(jié)(通過這個值可以計算出尾節(jié)點的地址)
zllen uint16_t 2字節(jié) 記錄壓縮列表中包含的節(jié)點數(shù)量,當(dāng)列表值超過可以存儲的最大值(65535)時,此值固定存儲 65535(即 216 次方 減 1),因此此時需要遍歷整個壓縮列表才能計算出真實節(jié)點數(shù)。
entry 節(jié)點 - 壓縮列表中的各個節(jié)點,長度由存儲的實際數(shù)據(jù)決定。
zlend uint8_t 1字節(jié) 特殊字符 0xff(即十進(jìn)制 255),用來標(biāo)記壓縮列表的末端(其他正常的節(jié)點沒有被標(biāo)記為 255 的,因為 255 用來標(biāo)識末尾,后面可以看到,正常節(jié)點都是標(biāo)記為 254)。

 

entry 存儲結(jié)構(gòu)

 

ziplistheadend 存的都是長度和標(biāo)記,而 entry 存儲的是具體元素,這又是經(jīng)過特殊的設(shè)計的一種存儲格式,每個 entry 都以包含兩段信息的元數(shù)據(jù)作為前綴,每一個 entry 的組成結(jié)構(gòu)為:

?
1
<prevlen> <encoding> <entry-data>

prevlen

prevlen 屬性存儲了前一個 entry 的長度,通過此屬性能夠從后到前遍歷列表。 prevlen 屬性的長度可能是 1 字節(jié)也可能是 5 字節(jié):

當(dāng)鏈表的前一個 entry 占用字節(jié)數(shù)小于 254,此時 prevlen 只用 1 個字節(jié)進(jìn)行表示。

<prevlen from 0 to 253> <encoding> <entry>

當(dāng)鏈表的前一個 entry 占用字節(jié)數(shù)大于等于 254,此時 prevlen5 個字節(jié)來表示,其中第 1 個字節(jié)的值固定是 254(相當(dāng)于是一個標(biāo)記,代表后面跟了一個更大的值),后面 4 個字節(jié)才是真正存儲前一個 entry 的占用字節(jié)數(shù)。

0xfe <4 bytes unsigned little endian prevlen> <encoding> <entry>

注意:1 個字節(jié)完全你能存儲 255 的大小,之所以只取到 254 是因為 zlend 就是固定的 255,所以 255 這個數(shù)要用來判斷是否是 ziplist 的結(jié)尾。

encoding

encoding 屬性存儲了當(dāng)前 entry 所保存數(shù)據(jù)的類型以及長度。encoding 長度為 1 字節(jié),2 字節(jié)或者 5 字節(jié)長。前面我們提到,每一個 entry 中可以保存字節(jié)數(shù)組和整數(shù),而 encoding 屬性的第 1 個字節(jié)就是用來確定當(dāng)前 entry 存儲的是整數(shù)還是字節(jié)數(shù)組。當(dāng)存儲整數(shù)時,第 1 個字節(jié)的前兩位總是 11,而存儲字節(jié)數(shù)組時,則可能是 000110 三種中的一種。

當(dāng)存儲整數(shù)時,第 1 個字節(jié)的前 2 位固定為 11,其他位則用來記錄整數(shù)值的類型或者整數(shù)值(下表所示的編碼中前兩位均為 11):

 

編碼 長度 entry保存的數(shù)據(jù)
11000000 1字節(jié) int16_t類型整數(shù)
11010000 1字節(jié) int32_t類型整數(shù)
11100000 1字節(jié) int64_t類型整數(shù)
11110000 1字節(jié) 24位有符號整數(shù)
11111110 1字節(jié) 8位有符號整數(shù)
1111xxxx 1字節(jié) xxxx 代表區(qū)間 0001-1101,存儲了一個介于 0-12 之間的整數(shù),此時 entry-data 屬性被省略

 

注意:xxxx 四位編碼范圍是 0000-1111,但是 000011111110 已經(jīng)被表格中前面表示的數(shù)據(jù)類型占用了,所以實際上的范圍是 0001-1101,此時能保存數(shù)據(jù) 1-13,再減去 1 之后范圍就是 0-12。至于為什么要減去 1 是從使用習(xí)慣來說 0 是一個非常常用的數(shù)據(jù),所以才會選擇統(tǒng)一減去 1 來存儲一個 0-12 的區(qū)間而不是直接存儲 1-13 的區(qū)間。

當(dāng)存儲字節(jié)數(shù)組時,第 1 個字節(jié)的前 2 位為 0001 或者 10,其他位則用來記錄字節(jié)數(shù)組的長度:

 

編碼 長度 entry保存的數(shù)據(jù)
00pppppp 1字節(jié) 長度小于等于 63 字節(jié)(6 位)的字節(jié)數(shù)組
01pppppp qqqqqqqq 2字節(jié) 長度小于等于 16383 字節(jié)(14 位)的字節(jié)數(shù)組
10000000 qqqqqqqq rrrrrrrr ssssssss tttttttt 5字節(jié) 長度小于等于 232 次方減 132 位)的字節(jié)數(shù)組,其中第 1 個字節(jié)的后 6 位設(shè)置為 0,暫時沒有用到,后面的 32 位(4 個字節(jié))存儲了數(shù)據(jù)

 

entry-data

entry-data 存儲的是具體數(shù)據(jù)。當(dāng)存儲小整數(shù)(0-12)時,因為 encoding 就是數(shù)據(jù)本身,此時 entry-data 部分會被省略,省略了 entry-data 部分之后的 ziplist 中的 entry 結(jié)構(gòu)如下:

<prevlen> <encoding>

壓縮列表中 entry 的數(shù)據(jù)結(jié)構(gòu)定義如下(源碼 ziplist.c 文件內(nèi)),當(dāng)然實際存儲并沒有直接使用到這個結(jié)構(gòu)定義,這個結(jié)構(gòu)只是用來接收數(shù)據(jù),所以大家了解一下就可以了:

typedef struct zlentry {
 unsigned int prevrawlensize;//存儲prevrawlen所占用的字節(jié)數(shù)
 unsigned int prevrawlen;//存儲上一個鏈表節(jié)點需要的字節(jié)數(shù)
 unsigned int lensize;//存儲len所占用的字節(jié)數(shù)
 unsigned int len;//存儲鏈表當(dāng)前節(jié)點的字節(jié)數(shù)
 unsigned int headersize;//當(dāng)前鏈表節(jié)點的頭部大小(prevrawlensize + lensize)即非數(shù)據(jù)域的大小
 unsigned char encoding;//編碼方式
 unsigned char *p;//指向當(dāng)前節(jié)點的起始位置(因為列表內(nèi)的數(shù)據(jù)也是一個字符串對象)
} zlentry;

ziplist 數(shù)據(jù)示例

 

上面講解了大半天,可能大家都覺得枯燥無味了,也可能會覺得云里霧里,這個沒有關(guān)系,這些只要心里有個概念,用到的時候再查詢對應(yīng)資料就可以了,并不需要全部記住,接下來讓我們一起通過兩個例子來體會一下 ziplist 到底是如何來組織存儲數(shù)據(jù)的。

下面就是一個壓縮列表的存儲示例,這個壓縮列表里面存儲了 2 個節(jié)點,節(jié)點中存儲的是整數(shù) 25

[0f 00 00 00] [0c 00 00 00] [02 00] [00 f3] [02 f6] [ff]
  |    |   |  |  |  |
 zlbytes  zltail  zllen "2"  "5" end

第一組 4 個字節(jié)為 zlbytes 部分,0f 轉(zhuǎn)成二進(jìn)制就是 1111 也就是 15,代表整個 ziplist 長度是 15 個字節(jié)。第二組 4 個字節(jié) zltail 部分,0c 轉(zhuǎn)成二進(jìn)制就是 1100 也就是 12,這里記錄的是壓縮列表尾節(jié)點距離起始地址有多少個字節(jié),也就是就是說 [02 f6] 這個尾節(jié)點距離起始位置有 12 個字節(jié)。第三組 2 個字節(jié)就是記錄了當(dāng)前 ziplistentry 的數(shù)量,02 轉(zhuǎn)成二進(jìn)制就是 10,也就是說當(dāng)前 ziplist2 個節(jié)點。第四組 2 個字節(jié) [00 f3] 就是第一個 entry00 表示 0,因為這是第 1 個節(jié)點,所以前一個節(jié)點長度為 0f3 轉(zhuǎn)成二進(jìn)制就是 11110011,剛好對應(yīng)了表格中的編碼 1111xxxx,所以后面四位就是存儲了一個 0-12位的整數(shù)。0011 轉(zhuǎn)成十進(jìn)制就是 3,減去 1 得到 2,所以第一個 entry 存儲的數(shù)據(jù)就是 2。第五組 2 個字節(jié) [02 f6] 就是第二個 entry02 即為 2,表示前一個節(jié)點的長度為 2,注意,因為這里算出來的結(jié)果是小于 254,所以就代表了這里只用到了 1 個字節(jié)來存儲上一個節(jié)點的長度(如果等于 254,這說明接下來 4 個字節(jié)才存儲的是長度),所以后面的 f6 就是當(dāng)前節(jié)點的數(shù)據(jù),轉(zhuǎn)換成二進(jìn)制為 11110110,對應(yīng)了表格中的編碼 1111xxxx,同樣的后四位 0110 存儲的是真實數(shù)據(jù),計算之后得出是5。最后一組1個字節(jié)[ff]轉(zhuǎn)成二進(jìn)制就是 11111111,代表這是整個 ziplist 的結(jié)尾。

假如這時候又添加了一個 hello world 字符串到列表中,那么就會新增一個 entry,如下所示:

[02] [0b] [48 65 6c 6c 6f 20 57 6f 72 6c 64]

第一組的 1 個字節(jié) 02 轉(zhuǎn)成十進(jìn)制就是 2 ,表示前一個節(jié)點(即上面示例中的 [02 f6])長度是 2。第 二組的2 個字節(jié) 0b 轉(zhuǎn)成二進(jìn)制為 00001011,以 00 開頭,符合編碼 00pppppp,而除掉最開始的兩位 00,計算之后得到十進(jìn)制 11,這就說明后面字節(jié)數(shù)組的長度是 11。第三組剛好是 11 個字節(jié),對應(yīng)了上面的長度,所以這里就是真正存儲了 hello world 的字節(jié)數(shù)組。

ziplist 連鎖更新問題

 

上面提到 entry 中的 prevlen 屬性可能是 1 個字節(jié)也可能是 5 個字節(jié),那么我們來設(shè)想這么一種場景:假設(shè)一個 ziplist 中,連續(xù)多個 entry 的長度都是一個接近但是又不到 254 的值(介于 250~253 之間),那么這時候 ziplist 中每個節(jié)點都只用了 1 個字節(jié)來存儲上一個節(jié)點的長度,假如這時候添加了一個新節(jié)點,如 entry1 ,其長度大于 254 個字節(jié),此時 entry1 的下一個節(jié)點 entry2prelen 屬性就必須要由 1 個字節(jié)變?yōu)?5 個字節(jié),也就是需要執(zhí)行空間重分配,而此時 entry2 因為增加了 4 個字節(jié),導(dǎo)致長度又大于 254 個字節(jié)了,那么它的下一個節(jié)點 entry3prelen 屬性也會被改變?yōu)?5 個字節(jié)。依此類推,這種產(chǎn)生連續(xù)多次空間重分配的現(xiàn)象就稱之為連鎖更新。同樣的,不僅僅是新增節(jié)點,執(zhí)行刪除節(jié)點操作同樣可能會發(fā)生連鎖更新現(xiàn)象。

雖然 ziplist 可能會出現(xiàn)這種連鎖更新的場景,但是一般如果只是發(fā)生在少數(shù)幾個節(jié)點之間,那么并不會嚴(yán)重影響性能,而且這種場景發(fā)生的概率也比較低,所以實際使用時不用過于擔(dān)心。

總結(jié)

 

本文主要講解了 redis 當(dāng)中的 ziplist(壓縮列表),一種用時間換取空間的數(shù)據(jù)結(jié)構(gòu),在介紹壓縮列表存儲結(jié)構(gòu)的同時通過一個存儲示例來分析了 ziplist 是如何存儲數(shù)據(jù)的,最后介紹了 ziplist 中可能發(fā)生的連鎖更新問題。

到此這篇關(guān)于壓縮列表犧牲速度來節(jié)省內(nèi)存,redis是膨脹了嗎的文章就介紹到這了,更多相關(guān)redis壓縮列表內(nèi)容請搜索服務(wù)器之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持服務(wù)器之家!

原文鏈接:https://www.cnblogs.com/lonely-wolf/p/14281136.html

延伸 · 閱讀

精彩推薦
  • RedisRedis的配置、啟動、操作和關(guān)閉方法

    Redis的配置、啟動、操作和關(guān)閉方法

    今天小編就為大家分享一篇Redis的配置、啟動、操作和關(guān)閉方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧 ...

    大道化簡5312019-11-14
  • Redisredis中如何使用lua腳本讓你的靈活性提高5個逼格詳解

    redis中如何使用lua腳本讓你的靈活性提高5個逼格詳解

    這篇文章主要給大家介紹了關(guān)于redis中如何使用lua腳本讓你的靈活性提高5個逼格的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具...

    一線碼農(nóng)5812019-11-18
  • RedisRedis如何實現(xiàn)數(shù)據(jù)庫讀寫分離詳解

    Redis如何實現(xiàn)數(shù)據(jù)庫讀寫分離詳解

    Redis的主從架構(gòu),能幫助我們實現(xiàn)讀多,寫少的情況,下面這篇文章主要給大家介紹了關(guān)于Redis如何實現(xiàn)數(shù)據(jù)庫讀寫分離的相關(guān)資料,文中通過示例代碼介紹...

    羅兵漂流記6092019-11-11
  • RedisRedis 事務(wù)知識點相關(guān)總結(jié)

    Redis 事務(wù)知識點相關(guān)總結(jié)

    這篇文章主要介紹了Redis 事務(wù)相關(guān)總結(jié),幫助大家更好的理解和學(xué)習(xí)使用Redis,感興趣的朋友可以了解下...

    AsiaYe8232021-07-28
  • Redisredis 交集、并集、差集的具體使用

    redis 交集、并集、差集的具體使用

    這篇文章主要介紹了redis 交集、并集、差集的具體使用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友...

    xiaojin21cen10152021-07-27
  • Redisredis實現(xiàn)排行榜功能

    redis實現(xiàn)排行榜功能

    排行榜在很多地方都能使用到,redis的zset可以很方便地用來實現(xiàn)排行榜功能,本文就來簡單的介紹一下如何使用,具有一定的參考價值,感興趣的小伙伴們...

    乘月歸5022021-08-05
  • Redis詳解Redis復(fù)制原理

    詳解Redis復(fù)制原理

    與大多數(shù)db一樣,Redis也提供了復(fù)制機(jī)制,以滿足故障恢復(fù)和負(fù)載均衡等需求。復(fù)制也是Redis高可用的基礎(chǔ),哨兵和集群都是建立在復(fù)制基礎(chǔ)上實現(xiàn)高可用的...

    李留廣10222021-08-09
  • RedisRedis全量復(fù)制與部分復(fù)制示例詳解

    Redis全量復(fù)制與部分復(fù)制示例詳解

    這篇文章主要給大家介紹了關(guān)于Redis全量復(fù)制與部分復(fù)制的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用Redis爬蟲具有一定的參考學(xué)習(xí)...

    豆子先生5052019-11-27
主站蜘蛛池模板: 国产美女亚洲精品久久久综合91 | 国产美女久久久久 | 女人张开腿让男人桶视频免费大全 | bedfriend泰剧全集免费观看 | 国产香蕉在线视频 | 天堂在线中文无弹窗全文阅读 | 嫩交18xxxx | 国产麻豆视频 | 四虎影院久久久 | 精品一区二区三区免费视频 | 国产欧美日韩精品高清二区综合区 | 成人在线免费看 | 亚洲区在线 | 蜜桃破解版免费看nba | 91chinese 永久免费 | 女人叉开腿让男人捅 | 午夜一级| 国产精品女主播大秀在线 | 色图大全 | 不卡一区二区三区 | 国产午夜精品福利久久 | 99综合在线 | 狐媚小说| 高清欧美videossexo免费 | 久99视频精品免费观看福利 | 成人免费在线视频 | 99久久国产综合精品女不卡 | 色哟哟在线播放 | 四缺一的小说 | 亚洲成在人线视频 | 亚洲激情欧美 | 99久久久久久久 | 国产91精品区 | a黄色 | 热色综合 | 扒开黑女人p大荫蒂老女人 扒开大腿狠狠挺进视频 | 日本网络视频www色高清免费 | 91制片厂制作传媒网站破解 | 亚洲国产成人久久综合区 | 国产午夜不卡 | 98在线视频噜噜噜国产 |