日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

Redis源码解析:07压缩列表

發(fā)布時間:2025/3/14 数据库 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Redis源码解析:07压缩列表 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

? ? ? ? 壓縮列表(ziplist)是列表鍵和哈希鍵的底層實現之一。當列表鍵只包含少量列表項,并且每個列表項要么是小整數值,要么是長度較短的字符串時;或者當哈希鍵只包含少量鍵值對,并且每個鍵值對的鍵和值要么是小整數值,要么是長度較短的字符串時,那么Redis就會使用壓縮列表來做為列表鍵或哈希鍵的底層實現。

?

? ? ? ??壓縮列表是Redis為了節(jié)約內存而開發(fā)的,可用于存儲字符串和整數值。它是一個順序型數據結構,由一系列特殊編碼的連續(xù)內存塊組成。一個壓縮列表可以包含任意多個結點(entry),每個entry的大小不定,每個entry可保存一個字符串或一個整數值。

?

???????? ziplist的相關實現在都在ziplist.c中。

?

一:ziplist結構

? ? ? ??ziplist的結構如下:

<zlbytes><zltail><zllen><entry1><entry2>...<entryN><zlend>

? ? ? ??zlbytes是一個uint32_t類型的整數,表示整個ziplist占用的內存字節(jié)數;

? ? ? ??zltail是一個uint32_t類型的整數,表示ziplist中最后一個entry的偏移量,通過該偏移量,無需遍歷整個ziplist就可以確定尾結點的地址;

? ? ? ??zllen是一個uint16_t類型的整數,表示ziplist中的entry數目。如果該值小于UINT16_MAX(65535),則該屬性值就是ziplist的entry數目,若該值等于UINT16_MAX(65535),則還需要遍歷整個ziplist才能得到真正的entry數目;

? ? ? ??entryX表示ziplist的結點,每個entry的長度不定;

? ? ? ??zlend是一個uint8_t類型的整數,其值為0xFF(255),表示ziplist的結尾。

?

???????? 在ziplist.c中,定義了一系列的宏,可以分別獲取ziplist中存儲的各個屬性,比如:

#define ZIPLIST_BYTES(zl) (*((uint32_t*)(zl))) #define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t)))) #define ZIPLIST_LENGTH(zl) (*((uint16_t*)((zl)+sizeof(uint32_t)*2))) #define ZIPLIST_HEADER_SIZE (sizeof(uint32_t)*2+sizeof(uint16_t)) #define ZIPLIST_ENTRY_HEAD(zl) ((zl)+ZIPLIST_HEADER_SIZE) #define ZIPLIST_ENTRY_TAIL(zl) ((zl)+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))) #define ZIPLIST_ENTRY_END(zl) ((zl)+intrev32ifbe(ZIPLIST_BYTES(zl))-1)

???????? ZIPLIST_BYTES獲取ziplist中的zlbytes屬性;ZIPLIST_TAIL_OFFSET獲取ziplist的zltail屬性;ZIPLIST_LENGTH獲取ziplist的zllen屬性

???????? ZIPLIST_ENTRY_HEAD得到ziplist頭結點的地址;ZIPLIST_ENTRY_TAIL得到ziplist中尾節(jié)點的首地址,ZIPLIST_ENTRY_END得到ziplist結尾字節(jié)zlend的地址。

???????? 注意,ziplist中所有的屬性值都是以小端的格式存儲的。因此取得ziplist中保存的屬性值后,還需要對內存做字節(jié)翻轉才能得到真正的值。intrev32ifbe就是在大端系統(tǒng)下對內存進行字節(jié)翻轉的函數。

?

二:entry結構

? ? ? ??每個壓縮列表節(jié)點都由previous_entry_length、encoding和content三部分組成。

? ? ? ??previous_entry_length表示前一個entry的字節(jié)長度,根據該字段值就可以從后向前遍歷ziplist。

? ? ? ??previous_entry_length字段長度可以是1字節(jié),也可以是5字節(jié)。如果前一個entry長度小于254字節(jié),則該字段只占用一個字節(jié);如果前一個entry長度大于等于254字節(jié),則該字段占用5個字節(jié):第一個字節(jié)置為0xFE(254),之后的4個字節(jié)保存entry的實際長度(小端格式存儲)。

? ? ? ??之所以用0xFE(254)這個值作為分界點,是因為0xFF(255)被用作ziplist的結束標志,一旦掃描到0xFF,就認為ziplist結束了。

?

? ? ? ??entry的content字段保存節(jié)點的實際內容,它可以是一個字符串或者整數,值的類型和長度由encoding屬性決定。

?

? ? ? ??encoding字段記錄了節(jié)點的content所保存的數據類型及長度。如果entry中保存的是字符串,則encoding字段的前2個二進制位可以是00、01和10,分別表示不同長度類型的字符串,剩下的二進制位就表示字符串的實際長度;如果entry中的內容為整數,則encoding字段的前2個二進制位都為11,剩下的2個二進制位表示整數的類型。encoding的形式如下:

? ? ? ??00pppppp,1字節(jié),表示長度小于等于63(2^6- 1) 字節(jié)的字符串;

? ? ? ??01pppppp|qqqqqqqq,2 字節(jié),表示長度小于等于16383(2^14 - 1) 字節(jié)的字符串;

? ? ? ??10______|qqqqqqqq|rrrrrrrr|ssssssss|tttttttt,5字節(jié),表示長度小于等于4294967295(2^32- 1) 字節(jié)的字符串

?

? ? ? ??11000000,1字節(jié),表示int16_t類型的整數;

? ? ? ??11010000,1字節(jié),表示int32_t類型的整數;

? ? ? ??11100000,1字節(jié),表示int64_t類型的整數;

? ? ? ??11110000,1字節(jié),表示24位有符號整數;

? ? ? ??11111110,1字節(jié),表示8位有符號整數;

? ? ? ??1111xxxx,1字節(jié),表示0到12之間的值。使用這一編碼的節(jié)點沒有相應的oontont屬性,因為xxxx就可以表示0到12之間的值。因為0000和1110不可用,所以xxxx的取值從0001到1101,也就是1到13之間的值。因此,需要從xxxx減去1,才能得到實際的值。

? ? ? ??注意,所有的整數都是以小端的格式存儲的。

?

三:連鎖更新

? ? ? ??每個節(jié)點的previous_entry_length字段記錄了前一個節(jié)點的長度。如果前一節(jié)點的長度小于254字節(jié),那么previous_entry_length字段占用1個字節(jié);如果前一節(jié)點的長度大于等于254字節(jié),那么previous_entry_length字段占用5個字節(jié)。

? ? ? ??考慮這樣一種情況:在一個壓縮列表中,有多個連續(xù)的、長度介于250字節(jié)到253字節(jié)之間的節(jié)點e1至eN。因所有節(jié)點的長度都小于254字節(jié),所以e1至eN節(jié)點的previous_entry_length字段都是1字節(jié)長。

? ? ? ??這時,如果將一個長度大于等于254字節(jié)的新節(jié)點new設置為壓縮列表的表頭節(jié)點,那么new將成為e1的前置節(jié)點,但是因為e1的previous_entry_length字段僅長1字節(jié),沒辦法保存新節(jié)點new的長度,所以需要對壓縮列表執(zhí)行空間重分配操作,將e1節(jié)點的previous_entry_length字段從原來的1字節(jié)擴展為5字節(jié)。

? ? ? ??現在,麻煩的事情來了,原e1的長度介于250字節(jié)至253字節(jié)之間,e1的previous_entry_length字段變成5個字節(jié)后,e1的長度就大于等于254了。從而e2的previous_entry_length字段,也需要從原來的1字節(jié)長擴展為5字節(jié)長。

? ? ? ??因此,需要再次對壓縮列表執(zhí)行空間重分配操作,并將e2節(jié)點的previous_entry_length屬性從原來的1字節(jié)長擴展為5字節(jié)長。

? ? ? ??以此類推,為了讓每個節(jié)點的previous_entry_length屬性都符合壓縮列表對節(jié)點的要求,程序需要不斷地對壓縮列表執(zhí)行空間重分配操作,直到eN為止。

? ? ? ??Redis將這種在特殊情況下產生的連續(xù)多次空間擴展操作稱之為“連鎖更新”(cascade update)。除了添加新節(jié)點可能會引發(fā)連鎖更新之外,刪除節(jié)點也可能會引發(fā)連鎖更新。

?

? ? ? ??因為連鎖更新在最壞情況下需要對壓縮列表執(zhí)行N次空間重分配操作,而每次空間重分配的最壞復雜度為O(N),所以連鎖更新的最壞復雜度為O(N^2)。

? ? ? ??盡管連鎖更新的復雜度較高,但它真正造成性能間題的幾率是很低的。

? ? ? ??首先,壓縮列表里要恰好有多個連續(xù)的、長度介于250字節(jié)至253字節(jié)之間的節(jié)點,連鎖更新才有可能觸發(fā)。在實際中,這種情況并不多見;

? ? ? ??其次,即使出現連鎖更新,但只要被更新的節(jié)點數量不多,就不會對性能造成任何影響,比如對三五個節(jié)點進行連鎖更新是絕對不會影響性能的;

? ? ? ??因此,ziplistPush等函數的平均復雜度僅為O(N)。在實際中,我們可以放心地使用這些函數,而不必擔心連鎖更新會影響壓縮列表的性能。

?

? ? ? ??ziplist變動時,previous_entry_length字段長度可能需要從1字節(jié)擴展為5字節(jié),從而會引起連鎖更新,也可能需要從5字節(jié)收縮為1字節(jié)。這就有可能會發(fā)生抖動現象,也就是節(jié)點的previous_entry_length字段,不斷的擴展和收縮的現象。Redis中,為了避免這種現象,允許previous_entry_length字段在需要收縮的情況下,保持5字節(jié)不變。

?

四:代碼

? ? ? ??Redis中的ziplist實現,并未涉及難以理解的算法。但是因為ziplist本身的編碼需求較多,因而代碼需要處理各種細節(jié),初看之下比較繁雜,分析之后,其實很容易理解。

?

1:連鎖更新

? ? ? ??下面就是處理連鎖更新的代碼,zl指向一個ziplist,p指向其中第一個不需要更新的節(jié)點(第一個已經更新過的節(jié)點),其后續(xù)的節(jié)點可能需要更新:

static unsigned char *__ziplistCascadeUpdate(unsigned char *zl, unsigned char *p) {size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), rawlen, rawlensize;size_t offset, noffset, extra;unsigned char *np;zlentry cur, next;while (p[0] != ZIP_END) {cur = zipEntry(p);rawlen = cur.headersize + cur.len;rawlensize = zipPrevEncodeLength(NULL,rawlen);/* Abort if there is no next entry. */if (p[rawlen] == ZIP_END) break;next = zipEntry(p+rawlen);/* Abort when "prevlen" has not changed. */if (next.prevrawlen == rawlen) break;if (next.prevrawlensize < rawlensize) {/* The "prevlen" field of "next" needs more bytes to hold* the raw length of "cur". */offset = p-zl;extra = rawlensize-next.prevrawlensize;zl = ziplistResize(zl,curlen+extra);p = zl+offset;/* Current pointer and offset for next element. */np = p+rawlen;noffset = np-zl;/* Update tail offset when next element is not the tail element. */if ((zl+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))) != np) {ZIPLIST_TAIL_OFFSET(zl) =intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+extra);}/* Move the tail to the back. */memmove(np+rawlensize,np+next.prevrawlensize,curlen-noffset-next.prevrawlensize-1);zipPrevEncodeLength(np,rawlen);/* Advance the cursor */p += rawlen;curlen += extra;} else {if (next.prevrawlensize > rawlensize) {/* This would result in shrinking, which we want to avoid.* So, set "rawlen" in the available bytes. */zipPrevEncodeLengthForceLarge(p+rawlen,rawlen);} else {zipPrevEncodeLength(p+rawlen,rawlen);}/* Stop here, as the raw length of "next" has not changed. */break;}}return zl; }

? ? ? ??首先獲取ziplist當前的長度curlen;

? ? ? ??然后從p開始輪訓,獲取p指向節(jié)點的總長度rawlen,以及編碼該字節(jié)長度需要的字節(jié)數rawlensize;

? ? ? ??如果p指向的結點沒有后繼結點,直接退出返回;否則,next表示p的后繼節(jié)點,next節(jié)點的previous_entry_length字段值為next.prevrawlen,該字段長度是next.prevrawlensize。如果next.prevrawlen與rawlen相等,則表示next節(jié)點的前繼節(jié)點的長度未發(fā)生變化,直接退出返回;

?

? ? ? ??如果next.prevrawlensize小于rawlensize,表示next節(jié)點的前繼節(jié)點的長度,原來小于254字節(jié),現在大于等于254字節(jié)了。因此next節(jié)點的previous_entry_length字段需要擴展長度,擴展的字節(jié)數extra為rawlensize-next.prevrawlensize,利用ziplistResize擴展ziplist的內存空間。注意,擴容前要保存p的偏移量,擴容后利用該偏移量,可以重新得到p的位置。

? ? ? ??如下圖,分別表示擴容前和擴容后的情況:

? ? ? ??如果next節(jié)點不是尾節(jié)點,則需要更新ziplist的zltail屬性;如果next是尾結點,因為next之前的內容沒有變化,因此無需更新zltail屬性;

? ? ? ??然后開始移動內存,移動的內容是,從next節(jié)點的previous_entry_length字段之后的內存開始,到ziplist末尾字節(jié)zlend之前的內容:memmove(np+rawlensize, np+next.prevrawlensize, curlen-noffset-next.prevrawlensize-1);

? ? ? ??然后更新next中的previous_entry_length字段為rawlen;

? ? ? ??然后p指向next節(jié)點,依次遍歷下一個節(jié)點;

????????

? ? ? ??如果next.prevrawlensize大于rawlensize,表示next節(jié)點的前繼節(jié)點的長度,原來大于等于254字節(jié),現在小于254字節(jié)了。為了避免“抖動”,調用zipPrevEncodeLengthForceLarge保持next節(jié)點的previous_entry_length字段長度不變,并強制編碼為rawlen,然后退出返回;

?

? ? ? ??如果next.prevrawlensize等于rawlensize,表示next節(jié)點的前繼節(jié)點的長度,原來小于254字節(jié),現在還是小于254字節(jié),或者原來大于等于254字節(jié),現在還是大于等于254字節(jié)。這種情況下,直接將next節(jié)點的previous_entry_length字段編碼為rawlen,然后退出返回。

?

2:刪除節(jié)點

? ? ? ??下面是刪除節(jié)點的代碼,從ziplist中,p指向的節(jié)點開始,最多刪除num個節(jié)點:

static unsigned char *__ziplistDelete(unsigned char *zl, unsigned char *p, unsigned int num) {unsigned int i, totlen, deleted = 0;size_t offset;int nextdiff = 0;zlentry first, tail;first = zipEntry(p);for (i = 0; p[0] != ZIP_END && i < num; i++) {p += zipRawEntryLength(p);deleted++;}totlen = p-first.p;if (totlen > 0) {if (p[0] != ZIP_END) {/* Storing `prevrawlen` in this entry may increase or decrease the* number of bytes required compare to the current `prevrawlen`.* There always is room to store this, because it was previously* stored by an entry that is now being deleted. */nextdiff = zipPrevLenByteDiff(p,first.prevrawlen);p -= nextdiff;zipPrevEncodeLength(p,first.prevrawlen);/* Update offset for tail */ZIPLIST_TAIL_OFFSET(zl) =intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))-totlen);/* When the tail contains more than one entry, we need to take* "nextdiff" in account as well. Otherwise, a change in the* size of prevlen doesn't have an effect on the *tail* offset. */tail = zipEntry(p);if (p[tail.headersize+tail.len] != ZIP_END) {ZIPLIST_TAIL_OFFSET(zl) =intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+nextdiff);}/* Move tail to the front of the ziplist */memmove(first.p,p,intrev32ifbe(ZIPLIST_BYTES(zl))-(p-zl)-1);} else {/* The entire tail was deleted. No need to move memory. */ZIPLIST_TAIL_OFFSET(zl) =intrev32ifbe((first.p-zl)-first.prevrawlen);}/* Resize and update length */offset = first.p-zl;zl = ziplistResize(zl, intrev32ifbe(ZIPLIST_BYTES(zl))-totlen+nextdiff);ZIPLIST_INCR_LENGTH(zl,-deleted);p = zl+offset;/* When nextdiff != 0, the raw length of the next entry has changed, so* we need to cascade the update throughout the ziplist */if (nextdiff != 0)zl = __ziplistCascadeUpdate(zl,p);}return zl; }

? ? ? ??首先將p轉化為zlentry結構的first,然后從p開始向后遍歷num個節(jié)點,這樣,就得到了需要刪除的節(jié)點數deleted,以及要刪除的總字節(jié)數totlen。只有當totlen大于0時,才進行刪除動作,否則直接返回zl;此時p指向最后一個刪除節(jié)點的后繼節(jié)點;

?

? ? ? ??若此時p不是ziplist的結尾字節(jié),則其指向節(jié)點的previous_entry_length字段,需要設置為新值first.prevrawlen。首先計算p指向節(jié)點,其新舊previous_entry_length字段長度的差值nextdiff,nextdiff為正,表示p節(jié)點的previous_entry_length字段需要擴容,從要刪除的內存中選擇擴容的部分,因此p-=nextdiff,p向前走nextdiff個字節(jié),表示少刪除nextdiff個字節(jié)。如果nextdiff為負,表示p節(jié)點的previous_entry_length字段需要收縮,因此p-=nextdiff,表示p向后走nextdiff個字節(jié),表示多刪除nextdiff個字節(jié);

? ? ? ??然后重新設置p指向節(jié)點的previous_entry_length字段值為first.prevrawlen;

????????

? ? ? ??接下來就是調整zltail屬性,分情況討論:如果遍歷num個節(jié)點之后,未做nextdiff調整之前,p指向的節(jié)點就是尾結點,如下圖所示(僅以nextdiff為正為例):

? ? ? ??上圖中,p'表示未做nextdiff調整之前的指針,也就是指向原尾結點的指針,p表示做出nextdiff調整之后的指針。刪除num個節(jié)點之后,zltail的值,也就是first.p-zl,因此有:new_zltail = old_zltail - totlen; 刪除后如下圖所示:

? ? ? ??上面的情況,對于nextdiff為負也是成立的,不再贅述。

????????

? ? ? ??如果遍歷num個節(jié)點之后,未做nextdiff調整之前,p指向的節(jié)點不是尾結點,則新的zltail,等于舊的zltail,減去刪除的總字節(jié)數,刪除的總字節(jié)數為totlen-nextdiff,因此有:new_zltail = old_zltail - totlen + nextdiff;

?

? ? ? ??更新完zltail屬性后,接下來就是移動內存了,將當前p指向的內存,移動到first.p指向的位置上,移動的內存總數就是,從當前p指向的內存開始,到ziplist結尾字節(jié)之前的內容;

?

? ? ? ??如果在遍歷num個節(jié)點之后,p指向的字節(jié)就是ziplist的結尾字節(jié),則無需移動內存,僅需要重新設置zltail屬性即可,此時的尾結點,是first.p的前繼節(jié)點,因此有:new_zltail = first.p - zl -first.prevrawlen;

?

? ? ? ??移動內存,以及設置新的zltail之后,剩下的就是重新為ziplist分配空間,并且設置新的zllen屬性。注意,重新分配ziplist內存之前,保存p的偏移量offset,這樣,在分配空間之后,就可以利用offset重新得到p的位置了;

? ? ? ??如果nextdiff不為0,表示p指向的節(jié)點的大小發(fā)生了變化,調用__ziplistCascadeUpdate處理后續(xù)的節(jié)點。

?

3:插入節(jié)點

???????? 下面是插入節(jié)點的代碼,根據原始數據s,及其長度slen,新建一個ziplist節(jié)點,將該節(jié)點插入到p之前的位置,也就是,p指向的節(jié)點,成為新結點的后繼節(jié)點。

static unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) {size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), reqlen;unsigned int prevlensize, prevlen = 0;size_t offset;int nextdiff = 0;unsigned char encoding = 0;long long value = 123456789; /* initialized to avoid warning. Using a valuethat is easy to see if for some reasonwe use it uninitialized. */zlentry tail;/* Find out prevlen for the entry that is inserted. */if (p[0] != ZIP_END) {ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);} else {unsigned char *ptail = ZIPLIST_ENTRY_TAIL(zl);if (ptail[0] != ZIP_END) {prevlen = zipRawEntryLength(ptail);}}/* See if the entry can be encoded */if (zipTryEncoding(s,slen,&value,&encoding)) {/* 'encoding' is set to the appropriate integer encoding */reqlen = zipIntSize(encoding);} else {/* 'encoding' is untouched, however zipEncodeLength will use the* string length to figure out how to encode it. */reqlen = slen;}/* We need space for both the length of the previous entry and* the length of the payload. */reqlen += zipPrevEncodeLength(NULL,prevlen);reqlen += zipEncodeLength(NULL,encoding,slen);/* When the insert position is not equal to the tail, we need to* make sure that the next entry can hold this entry's length in* its prevlen field. */nextdiff = (p[0] != ZIP_END) ? zipPrevLenByteDiff(p,reqlen) : 0;/* Store offset because a realloc may change the address of zl. */offset = p-zl;zl = ziplistResize(zl,curlen+reqlen+nextdiff);p = zl+offset;/* Apply memory move when necessary and update tail offset. */if (p[0] != ZIP_END) {/* Subtract one because of the ZIP_END bytes */memmove(p+reqlen,p-nextdiff,curlen-offset-1+nextdiff);/* Encode this entry's raw length in the next entry. */zipPrevEncodeLength(p+reqlen,reqlen);/* Update offset for tail */ZIPLIST_TAIL_OFFSET(zl) =intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+reqlen);/* When the tail contains more than one entry, we need to take* "nextdiff" in account as well. Otherwise, a change in the* size of prevlen doesn't have an effect on the *tail* offset. */tail = zipEntry(p+reqlen);if (p[reqlen+tail.headersize+tail.len] != ZIP_END) {ZIPLIST_TAIL_OFFSET(zl) =intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+nextdiff);}} else {/* This element will be the new tail. */ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(p-zl);}/* When nextdiff != 0, the raw length of the next entry has changed, so* we need to cascade the update throughout the ziplist */if (nextdiff != 0) {offset = p-zl;zl = __ziplistCascadeUpdate(zl,p+reqlen);p = zl+offset;}/* Write the entry */p += zipPrevEncodeLength(p,prevlen);p += zipEncodeLength(p,encoding,slen);if (ZIP_IS_STR(encoding)) {memcpy(p,s,slen);} else {zipSaveInteger(p,value,encoding);}ZIPLIST_INCR_LENGTH(zl,1);return zl; }

? ? ? ? ?首先獲取ziplist當前長度curlen;

???????? 然后得到p指向位置的前繼節(jié)點的長度prevlen;

???????? 然后計算新結點中content部分的長度:如果s中的內容能夠表示為整數值,則得到該整數值的長度reqlen,否則,reqlen等于slen;

???????? 根據prevlen,得到新結點的previous_entry_length字段長度,加到reqlen中;

???????? 根據encoding和slen,得到新結點的encoding字段長度,加到reqlen中,至此,reqlen就表示新結點的總長度;

????????

? ? ? ? ? 因新結點將成為p的前繼節(jié)點,因此,只要p并非ziplist的結尾字節(jié),就利用zipPrevLenByteDiff,計算p中新舊previous_entry_length字段的長度之差nextdiff,nextdiff為正,表示因新結點的長度大于等于254,p的previous_entry_length字段需要擴容nextdiff個字節(jié);如果nextdiff為負,表示因新結點的長度小于254,p的previous_entry_length字段需要收縮nextdiff個字節(jié);

?

???????? 接下來,利用ziplistResize對ziplist進行擴容,擴容長度為reqlen+nextdiff,擴容之前,保存p的偏移量offset,這樣擴容后根據offset,就可以重新得到p的位置;

?

???????? 如果p并非ziplist的結尾字節(jié),則接下來開始移動內存,從p-nextdiff指向的位置,移動到p+reqlen的位置,移動的字節(jié)數為curlen-offset-1+nextdiff;

???????? 然后設置新結點的后繼節(jié)點(也就是插入之前,p指向的節(jié)點)的previous_entry_length字段;

???????? 然后設置zltail屬性,這里根據p是否是尾結點而區(qū)分對待,情況類似與__ziplistDelete中的討論,不再贅述。

?

???????? 如果p就是ziplist的結尾字節(jié),則只需要更新zltail屬性即可;

????????

???????? 如果nextdiff非0,則需要調用__ziplistCascadeUpdate,處理p+reqlen節(jié)點開始的連鎖升級或是收縮情況;

???????? 最后,設置插入的新結點的各種字段值,并更新zllen屬性。

?

? ? ? ? ?其他關于ziplist的代碼,可參閱:

https://github.com/gqtc/redis-3.0.5/blob/master/redis-3.0.5/src/ziplist.c

轉載于:https://www.cnblogs.com/gqtcgq/p/7247072.html

總結

以上是生活随笔為你收集整理的Redis源码解析:07压缩列表的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。