chinese直男口爆体育生外卖, 99久久er热在这里只有精品99, 又色又爽又黄18禁美女裸身无遮挡, gogogo高清免费观看日本电视,私密按摩师高清版在线,人妻视频毛茸茸,91论坛 兴趣闲谈,欧美 亚洲 精品 8区,国产精品久久久久精品免费

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Redis五種常見(jiàn)對(duì)象類型的底層數(shù)據(jù)結(jié)構(gòu)

數(shù)據(jù)分析與開(kāi)發(fā) ? 來(lái)源:數(shù)據(jù)分析與開(kāi)發(fā) ? 作者:伍陸七 ? 2020-11-14 09:50 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

Redis 是一個(gè)基于內(nèi)存中的數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)系統(tǒng),可以用作數(shù)據(jù)庫(kù)、緩存和消息中間件。Redis 支持五種常見(jiàn)對(duì)象類型:字符串(String)、哈希(Hash)、列表(List)、集合(Set)以及有序集合(Zset),我們?cè)谌粘9ぷ髦幸矔?huì)經(jīng)常使用它們。知其然,更要知其所以然,本文將會(huì)帶你讀懂這五種常見(jiàn)對(duì)象類型的底層數(shù)據(jù)結(jié)構(gòu)。

本文主要內(nèi)容參考自《Redis設(shè)計(jì)與實(shí)現(xiàn)》

1. 對(duì)象類型和編碼

Redis 使用對(duì)象來(lái)存儲(chǔ)鍵和值的,在Redis中,每個(gè)對(duì)象都由 redisObject 結(jié)構(gòu)表示。redisObject 結(jié)構(gòu)主要包含三個(gè)屬性:type、encoding 和 ptr。

typedef struct redisObject { // 類型 unsigned type:4; // 編碼 unsigned encoding:4; // 底層數(shù)據(jù)結(jié)構(gòu)的指針 void *ptr;} robj;

其中 type 屬性記錄了對(duì)象的類型。對(duì)于 Redis 來(lái)說(shuō),鍵對(duì)象總是字符串類型,值對(duì)象可以是任意支持的類型。因此,當(dāng)我們說(shuō) Redis 鍵采用哪種對(duì)象類型的時(shí)候,指的是對(duì)應(yīng)的值采用哪種對(duì)象類型。

*ptr 屬性指向了對(duì)象的底層數(shù)據(jù)結(jié)構(gòu),而這些數(shù)據(jù)結(jié)構(gòu)由 encoding 屬性決定。

之所以由 encoding 屬性來(lái)決定對(duì)象的底層數(shù)據(jù)結(jié)構(gòu),是為了實(shí)現(xiàn)同一對(duì)象類型,支持不同的底層實(shí)現(xiàn)。這樣就能在不同場(chǎng)景下,使用不同的底層數(shù)據(jù)結(jié)構(gòu),進(jìn)而極大提升Redis的靈活性和效率。

底層數(shù)據(jù)結(jié)構(gòu)后面會(huì)詳細(xì)講解,這里簡(jiǎn)單看一下即可。

2. 字符串對(duì)象

字符串是我們?nèi)粘9ぷ髦杏玫米疃嗟膶?duì)象類型,它對(duì)應(yīng)的編碼可以是 int、raw 和 embstr。字符串對(duì)象相關(guān)命令可參考:Redis命令-Strings。

如果一個(gè)字符串對(duì)象保存的是不超過(guò) long 類型的整數(shù)值,此時(shí)編碼類型即為 int,其底層數(shù)據(jù)結(jié)構(gòu)直接就是 long 類型。例如執(zhí)行 set number 10086,就會(huì)創(chuàng)建 int 編碼的字符串對(duì)象作為 number 鍵的值。

如果字符串對(duì)象保存的是一個(gè)長(zhǎng)度大于 39 字節(jié)的字符串,此時(shí)編碼類型即為 raw,其底層數(shù)據(jù)結(jié)構(gòu)是簡(jiǎn)單動(dòng)態(tài)字符串(SDS);如果長(zhǎng)度小于等于 39 個(gè)字節(jié),編碼類型則為 embstr,底層數(shù)據(jù)結(jié)構(gòu)就是 embstr 編碼 SDS。下面,我們?cè)敿?xì)理解下什么是簡(jiǎn)單動(dòng)態(tài)字符串。

2.1 簡(jiǎn)單動(dòng)態(tài)字符串

SDS 定義

在 Redis 中,使用 sdshdr 數(shù)據(jù)結(jié)構(gòu)表示 SDS:

struct sdshdr { // 字符串長(zhǎng)度 int len; // buf數(shù)組中未使用的字節(jié)數(shù) int free; // 字節(jié)數(shù)組,用于保存字符串 char buf[];};

SDS 遵循了 C 字符串以空字符結(jié)尾的慣例,保存空字符的 1 字節(jié)不會(huì)計(jì)算在 len 屬性里面。例如,Redis 這個(gè)字符串在 SDS 里面的數(shù)據(jù)可能是如下形式:

SDS 與 C 字符串的區(qū)別

C 語(yǔ)言使用長(zhǎng)度為 N+1 的字符數(shù)組來(lái)表示長(zhǎng)度為N的字符串,并且字符串的最后一個(gè)元素是空字符 。Redis 采用 SDS 相對(duì)于 C 字符串有如下幾個(gè)優(yōu)勢(shì):

常數(shù)復(fù)雜度獲取字符串長(zhǎng)度;

杜絕緩沖區(qū)溢出;

減少修改字符串時(shí)帶來(lái)的內(nèi)存重分配次數(shù);

二進(jìn)制安全。

常數(shù)復(fù)雜度獲取字符串長(zhǎng)度

因?yàn)?C 字符串并不記錄自身的長(zhǎng)度信息,所以為了獲取字符串的長(zhǎng)度,必須遍歷整個(gè)字符串,時(shí)間復(fù)雜度是O(N)。而 SDS 使用 len 屬性記錄了字符串的長(zhǎng)度,因此獲取 SDS字符串長(zhǎng)度的時(shí)間復(fù)雜度是O(1)。

杜絕緩沖區(qū)溢出

C 字符串不記錄自身長(zhǎng)度帶來(lái)的另一個(gè)問(wèn)題是,很容易造成緩存區(qū)溢出。比如使用字符串拼接函數(shù)(stract)的時(shí)候,很容易覆蓋掉字符數(shù)組原有的數(shù)據(jù)。

與 C 字符串不同,SDS 的空間分配策略完全杜絕了發(fā)生緩存區(qū)溢出的可能性。當(dāng) SDS 進(jìn)行字符串?dāng)U充時(shí),首先會(huì)檢查當(dāng)前的字節(jié)數(shù)組的長(zhǎng)度是否足夠。如果不夠的話,會(huì)先進(jìn)行自動(dòng)擴(kuò)容,然后再進(jìn)行字符串操作。

減少修改字符串時(shí)帶來(lái)的內(nèi)存重分配次數(shù)

因?yàn)?C 字符串的長(zhǎng)度和底層數(shù)據(jù)是緊密關(guān)聯(lián)的,所以每次增長(zhǎng)或者縮短一個(gè)字符串,程序都要對(duì)這個(gè)數(shù)組進(jìn)行一次內(nèi)存重分配:

如果是增長(zhǎng)字符串操作,需要先通過(guò)內(nèi)存重分配來(lái)擴(kuò)展底層數(shù)組空間大小,不這么做就導(dǎo)致緩存區(qū)溢出;

如果是縮短字符串操作,需要先通過(guò)內(nèi)存重分配來(lái)來(lái)回收不再使用的空間,不這么做就導(dǎo)致內(nèi)存泄漏。

因?yàn)閮?nèi)存重分配涉及復(fù)雜的算法,并且可能需要執(zhí)行系統(tǒng)調(diào)用,所以通常是個(gè)比較耗時(shí)的操作。對(duì)于 Redis 來(lái)說(shuō),字符串修改是一個(gè)十分頻繁的操作。如果每次都像 C 字符串那樣進(jìn)行內(nèi)存重分配,對(duì)性能影響太大了,顯然是無(wú)法接受的。

SDS 通過(guò)空閑空間解除了字符串長(zhǎng)度和底層數(shù)據(jù)之間的關(guān)聯(lián)。在 SDS 中,數(shù)組中可以包含未使用的字節(jié),這些字節(jié)數(shù)量由 free 屬性記錄。通過(guò)空閑空間,SDS 實(shí)現(xiàn)了空間預(yù)分配和惰性空間釋放兩種優(yōu)化策略。

1.空間預(yù)分配

空間預(yù)分配是用于優(yōu)化 SDS 字符串增長(zhǎng)操作的,簡(jiǎn)單來(lái)說(shuō)就是當(dāng)字節(jié)數(shù)組空間不足觸發(fā)重分配的時(shí)候,總是會(huì)預(yù)留一部分空閑空間。這樣的話,就能減少連續(xù)執(zhí)行字符串增長(zhǎng)操作時(shí)的內(nèi)存重分配次數(shù)。

有兩種預(yù)分配的策略:

len 小于 1MB 時(shí):每次重分配時(shí)會(huì)多分配同樣大小的空閑空間;

len 大于等于 1MB 時(shí):每次重分配時(shí)會(huì)多分配 1MB 大小的空閑空間。

2. 惰性空間釋放

惰性空間釋放是用于優(yōu)化 SDS 字符串縮短操作的。簡(jiǎn)單來(lái)說(shuō)就是當(dāng)字符串縮短時(shí),并不立即使用內(nèi)存重分配來(lái)回收多出來(lái)的字節(jié),而是用 free 屬性記錄,等待將來(lái)使用。SDS 也提供直接釋放未使用空間的 API,在需要的時(shí)候,也能真正的釋放掉多余的空間。

二進(jìn)制安全

C 字符串中的字符必須符合某種編碼,并且除了字符串末尾之外,其它位置不允許出現(xiàn)空字符。這些限制使得 C 字符串只能保存文本數(shù)據(jù)。

但是對(duì)于 Redis 來(lái)說(shuō),不僅僅需要保存文本,還要支持保存二進(jìn)制數(shù)據(jù)。為了實(shí)現(xiàn)這一目標(biāo),SDS 的 API 全部做到了二進(jìn)制安全(binary-safe)。

2.2 raw 和 embstr 編碼的 SDS 區(qū)別

我們?cè)谇懊嬷v過(guò),長(zhǎng)度大于 39 字節(jié)的字符串,編碼類型為 raw,底層數(shù)據(jù)結(jié)構(gòu)是簡(jiǎn)單動(dòng)態(tài)字符串(SDS)。這個(gè)很好理解,比如當(dāng)我們執(zhí)行 set story "Long, long, long ago there lived a king ..."(長(zhǎng)度大于39)之后,Redis 就會(huì)創(chuàng)建一個(gè) raw 編碼的 String 對(duì)象。

數(shù)據(jù)結(jié)構(gòu)如下:

長(zhǎng)度小于等于 39 個(gè)字節(jié)的字符串,編碼類型為 embstr,底層數(shù)據(jù)結(jié)構(gòu)則是 embstr 編碼 SDS。embstr 編碼是專門(mén)用來(lái)保存短字符串的,它和 raw 編碼最大的不同在于:raw 編碼會(huì)調(diào)用兩次內(nèi)存分配分別創(chuàng)建 redisObject 結(jié)構(gòu)和 sdshdr 結(jié)構(gòu);而 embstr 編碼則是只調(diào)用一次內(nèi)存分配,在一塊連續(xù)的空間上同時(shí)包含 redisObject 結(jié)構(gòu)和 sdshdr 結(jié)構(gòu)。

2.3 編碼轉(zhuǎn)換

int 編碼和 embstr 編碼的字符串對(duì)象在條件滿足的情況下會(huì)自動(dòng)轉(zhuǎn)換為 raw 編碼的字符串對(duì)象。

對(duì)于 int 編碼來(lái)說(shuō),當(dāng)我們修改這個(gè)字符串為不再是整數(shù)值的時(shí)候,此時(shí)字符串對(duì)象的編碼就會(huì)從 int 變?yōu)?raw。

對(duì)于 embstr 編碼來(lái)說(shuō),只要我們修改了字符串的值,此時(shí)字符串對(duì)象的編碼就會(huì)從 embstr 變?yōu)?raw。

embstr 編碼的字符串對(duì)象可以認(rèn)為是只讀的,因?yàn)?Redis 為其編寫(xiě)任何修改程序。當(dāng)我們要修改 embstr 編碼字符串時(shí),都是先將轉(zhuǎn)換為 raw 編碼,然后再進(jìn)行修改。

3. 列表對(duì)象

列表對(duì)象的編碼可以是 linkedlist 或者 ziplist,對(duì)應(yīng)的底層數(shù)據(jù)結(jié)構(gòu)是鏈表和壓縮列表。列表對(duì)象相關(guān)命令可參考:Redis 命令-List。

默認(rèn)情況下,當(dāng)列表對(duì)象保存的所有字符串元素的長(zhǎng)度都小于 64 字節(jié),且元素個(gè)數(shù)小于 512 個(gè)時(shí),列表對(duì)象采用的是 ziplist 編碼,否則使用 linkedlist 編碼。

可以通過(guò)配置文件修改該上限值。

3.1 鏈表

鏈表是一種非常常見(jiàn)的數(shù)據(jù)結(jié)構(gòu),提供了高效的節(jié)點(diǎn)重排能力以及順序性的節(jié)點(diǎn)訪問(wèn)方式。在 Redis 中,每個(gè)鏈表節(jié)點(diǎn)使用 listNode 結(jié)構(gòu)表示:

typedef struct listNode { // 前置節(jié)點(diǎn) struct listNode *prev; // 后置節(jié)點(diǎn) struct listNode *next; // 節(jié)點(diǎn)值 void *value;} listNode

多個(gè) listNode 通過(guò) prev 和 next 指針組成雙端鏈表,如下圖所示:

為了操作起來(lái)比較方便,Redis 使用了 list 結(jié)構(gòu)持有鏈表。

typedef struct list { // 表頭節(jié)點(diǎn) listNode *head; // 表尾節(jié)點(diǎn) listNode *tail; // 鏈表包含的節(jié)點(diǎn)數(shù)量 unsigned long len; // 節(jié)點(diǎn)復(fù)制函數(shù) void *(*dup)(void *ptr); // 節(jié)點(diǎn)釋放函數(shù) void (*free)(void *ptr); // 節(jié)點(diǎn)對(duì)比函數(shù) int (*match)(void *ptr, void *key);} list;

list 結(jié)構(gòu)為鏈表提供了表頭指針 head、表尾指針 tail,以及鏈表長(zhǎng)度計(jì)數(shù)器 len,而 dup、free 和 match 成員則是實(shí)現(xiàn)多態(tài)鏈表所需類型的特定函數(shù)。

Redis 鏈表實(shí)現(xiàn)的特征總結(jié)如下:

雙端:鏈表節(jié)點(diǎn)帶有 prev 和 next 指針,獲取某個(gè)節(jié)點(diǎn)的前置節(jié)點(diǎn)和后置節(jié)點(diǎn)的復(fù)雜度都是O(n);

無(wú)環(huán):表頭節(jié)點(diǎn)的 prev 指針和表尾節(jié)點(diǎn)的 next 指針都指向 NULL,對(duì)鏈表的訪問(wèn)以 NULL 為終點(diǎn);

帶表頭指針和表尾指針:通過(guò) list 結(jié)構(gòu)的 head 指針和 tail 指針,程序獲取鏈表的表頭節(jié)點(diǎn)和表尾節(jié)點(diǎn)的復(fù)雜度為O(1);

帶鏈表長(zhǎng)度計(jì)數(shù)器:程序使用 list 結(jié)構(gòu)的 len 屬性來(lái)對(duì) list 持有的節(jié)點(diǎn)進(jìn)行計(jì)數(shù),程序獲取鏈表中節(jié)點(diǎn)數(shù)量的復(fù)雜度為O(1);

多態(tài):鏈表節(jié)點(diǎn)使用 void* 指針來(lái)保存節(jié)點(diǎn)值,可以保存各種不同類型的值。

3.2 壓縮列表

壓縮列表(ziplist)是列表鍵和哈希鍵的底層實(shí)現(xiàn)之一。壓縮列表主要目的是為了節(jié)約內(nèi)存,是由一系列特殊編碼的連續(xù)內(nèi)存塊組成的順序型數(shù)據(jù)結(jié)構(gòu)。一個(gè)壓縮列表可以包含任意多個(gè)節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)可以保存一個(gè)字節(jié)數(shù)組或者一個(gè)整數(shù)值。

如上圖所示,壓縮列表記錄了各組成部分的類型、長(zhǎng)度以及用途。

4. 哈希對(duì)象

哈希對(duì)象的編碼可以是 ziplist 或者 hashtable。

4.1 hash-ziplist

ziplist 底層使用的是壓縮列表實(shí)現(xiàn),上文已經(jīng)詳細(xì)介紹了壓縮列表的實(shí)現(xiàn)原理。每當(dāng)有新的鍵值對(duì)要加入哈希對(duì)象時(shí),先把保存了鍵的節(jié)點(diǎn)推入壓縮列表表尾,然后再將保存了值的節(jié)點(diǎn)推入壓縮列表表尾。比如,我們執(zhí)行如下三條 HSET 命令:

HSET profile name "tom"HSET profile age 25HSET profile career "Programmer"

如果此時(shí)使用 ziplist 編碼,那么該 Hash 對(duì)象在內(nèi)存中的結(jié)構(gòu)如下:

3.2 hash-hashtable

hashtable 編碼的哈希對(duì)象使用字典作為底層實(shí)現(xiàn)。字典是一種用于保存鍵值對(duì)的數(shù)據(jù)結(jié)構(gòu),Redis 的字典使用哈希表作為底層實(shí)現(xiàn),一個(gè)哈希表里面可以有多個(gè)哈希表節(jié)點(diǎn),每個(gè)哈希表節(jié)點(diǎn)保存的就是一個(gè)鍵值對(duì)。

3.3 哈希表

Redis 使用的哈希表由 dictht 結(jié)構(gòu)定義:

typedef struct dictht{ // 哈希表數(shù)組 dictEntry **table; // 哈希表大小 unsigned long size; // 哈希表大小掩碼,用于計(jì)算索引值 // 總是等于 size-1 unsigned long sizemask; // 該哈希表已有節(jié)點(diǎn)數(shù)量 unsigned long used;} dictht

table 屬性是一個(gè)數(shù)組,數(shù)組中的每個(gè)元素都是一個(gè)指向 dictEntry 結(jié)構(gòu)的指針,每個(gè) dictEntry 結(jié)構(gòu)保存著一個(gè)鍵值對(duì)。

size 屬性記錄了哈希表的大小,即 table 數(shù)組的大小。used 屬性記錄了哈希表目前已有節(jié)點(diǎn)數(shù)量。sizemask 總是等于 size-1,這個(gè)值主要用于數(shù)組索引。

比如下圖展示了一個(gè)大小為 4 的空哈希表。

哈希表節(jié)點(diǎn)

哈希表節(jié)點(diǎn)使用 dictEntry 結(jié)構(gòu)表示,每個(gè) dictEntry 結(jié)構(gòu)都保存著一個(gè)鍵值對(duì):

typedef struct dictEntry { // 鍵 void *key; // 值 union { void *val; unit64_t u64; nit64_t s64; } v; // 指向下一個(gè)哈希表節(jié)點(diǎn),形成鏈表 struct dictEntry *next;} dictEntry;

key 屬性保存著鍵值對(duì)中的鍵,而 v 屬性則保存了鍵值對(duì)中的值。值可以是一個(gè)指針,一個(gè) uint64_t 整數(shù)或者是 int64_t 整數(shù)。next 屬性指向了另一個(gè) dictEntry 節(jié)點(diǎn),在數(shù)組桶位相同的情況下,將多個(gè) dictEntry 節(jié)點(diǎn)串聯(lián)成一個(gè)鏈表,以此來(lái)解決鍵沖突問(wèn)題(鏈地址法)。

3.4 字典

Redis 字典由 dict 結(jié)構(gòu)表示:

typedefstructdict{ // 類型特定函數(shù) dictType *type; // 私有數(shù)據(jù) void *privdata; // 哈希表 dictht ht[2]; //rehash索引 // 當(dāng)rehash不在進(jìn)行時(shí),值為-1 int rehashidx;}

ht 是大小為 2,且每個(gè)元素都指向 dictht 哈希表。一般情況下,字典只會(huì)使用 ht[0] 哈希表,ht[1] 哈希表只會(huì)在對(duì) ht[0] 哈希表進(jìn)行 rehash 時(shí)使用。rehashidx 記錄了 rehash 的進(jìn)度,如果目前沒(méi)有進(jìn)行 rehash,值為 -1。

rehash

為了使 hash 表的負(fù)載因子 (ht[0]).used/ht[0]).size) 維持在一個(gè)合理范圍,當(dāng)哈希表保存的元素過(guò)多或者過(guò)少時(shí),程序需要對(duì) hash 表進(jìn)行相應(yīng)的擴(kuò)展和收縮。

rehash(重新散列)操作就是用來(lái)完成 hash 表的擴(kuò)展和收縮的。

rehash 的步驟如下:

1. 為 ht [1] 哈希表分配空間;

如果是擴(kuò)展操作,那么 ht[1] 的大小為第一個(gè)大于 ht[0].used*2的2n。比如ht[0].used=5,那么此時(shí) ht[1] 的大小就為 16(大于 10 的第一個(gè) 2n 的值是 16);

如果是收縮操作,那么 ht[1] 的大小為第一個(gè)大于 ht[0].used 的 2n。比如ht[0].used=5,那么此時(shí) ht[1] 的大小就為 8(大于 5 的第一個(gè) 2n 的值是 8)。

2. 將保存在 ht[0] 中的所有鍵值對(duì) rehash 到 ht[1] 中;

3. 遷移完成之后,釋放掉 ht[0],并將現(xiàn)在的 ht[1] 設(shè)置為 ht[0],在 ht[1] 新創(chuàng)建一個(gè)空白哈希表,為下一次 rehash 做準(zhǔn)備。

哈希表的擴(kuò)展和收縮時(shí)機(jī)

當(dāng)服務(wù)器沒(méi)有執(zhí)行 BGSAVE 或者 BGREWRITEAOF 命令時(shí),負(fù)載因子大于等于 1 觸發(fā)哈希表的擴(kuò)展操作;

當(dāng)服務(wù)器在執(zhí)行 BGSAVE 或者 BGREWRITEAOF 命令,負(fù)載因子大于等于 5 觸發(fā)哈希表的擴(kuò)展操作;

當(dāng)哈希表負(fù)載因子小于 0.1,觸發(fā)哈希表的收縮操作。

漸進(jìn)式 rehash

前面講過(guò),擴(kuò)展或者收縮需要將 ht[0] 里面的元素全部 rehash 到 ht[1] 中,如果 ht[0] 元素很多,顯然一次性 rehash 成本會(huì)很大,從影響到 Redis 性能。

為了解決上述問(wèn)題,Redis 使用了漸進(jìn)式 rehash 技術(shù),具體來(lái)說(shuō)就是分多次,漸進(jìn)式地將 ht[0] 里面的元素慢慢地 rehash 到 ht[1] 中。

下面是漸進(jìn)式 rehash 的詳細(xì)步驟:

為 ht[1] 分配空間;

在字典中維持一個(gè)索引計(jì)數(shù)器變量 rehashidx,并將它的值設(shè)置為 0,表示 rehash 正式開(kāi)始;

在 rehash 進(jìn)行期間,每次對(duì)字典執(zhí)行添加、刪除、查找或者更新時(shí),除了會(huì)執(zhí)行相應(yīng)的操作之外,還會(huì)順帶將 ht[0] 在 rehashidx 索引位上的所有鍵值對(duì) rehash 到 ht[1] 中,rehash 完成之后,rehashidx 值加 1;

隨著字典操作的不斷進(jìn)行,最終會(huì)在啊某個(gè)時(shí)刻遷移完成,此時(shí)將 rehashidx 值置為 -1,表示 rehash 結(jié)束。

漸進(jìn)式 rehash 一次遷移一個(gè)桶上所有的數(shù)據(jù)。設(shè)計(jì)上采用分而治之的思想,將原本集中式的操作分散到每個(gè)添加、刪除、查找和更新操作上,從而避免集中式 rehash 帶來(lái)的龐大計(jì)算。

因?yàn)樵跐u進(jìn)式 rehash 時(shí),字典會(huì)同時(shí)使用 ht[0] 和 ht[1] 兩張表,所以此時(shí)對(duì)字典的刪除、查找和更新操作都可能會(huì)在兩個(gè)哈希表進(jìn)行。比如,如果要查找某個(gè)鍵時(shí),先在 ht[0] 中查找,如果沒(méi)找到,則繼續(xù)到 ht[1] 中查找。

hash 對(duì)象中的 hashtable

HSET profile name "tom"HSET profile age 25HSET profile career "Programmer"

還是上述三條命令,保存數(shù)據(jù)到 Redis 的哈希對(duì)象中,如果采用 hashtable 編碼保存的話,那么該 Hash 對(duì)象在內(nèi)存中的結(jié)構(gòu)如下:

當(dāng)哈希對(duì)象保存的所有鍵值對(duì)的鍵和值的字符串長(zhǎng)度都小于 64 個(gè)字節(jié),并且數(shù)量小于 512 個(gè)時(shí),使用 ziplist 編碼,否則使用 hashtable 編碼。

可以通過(guò)配置文件修改該上限值。

4. 集合對(duì)象

集合對(duì)象的編碼可以是 intset 或者 hashtable。當(dāng)集合對(duì)象保存的元素都是整數(shù),并且個(gè)數(shù)不超過(guò) 512 個(gè)時(shí),使用 intset 編碼,否則使用 hashtable 編碼。

4.1 set-intset

intset 編碼的集合對(duì)象底層使用整數(shù)集合實(shí)現(xiàn)。

整數(shù)集合(intset)是 Redis 用于保存整數(shù)值的集合抽象數(shù)據(jù)結(jié)構(gòu),它可以保存類型為 int16_t、int32_t 或者 int64_t 的整數(shù)值,并且保證集合中的數(shù)據(jù)不會(huì)重復(fù)。Redis 使用 intset 結(jié)構(gòu)表示一個(gè)整數(shù)集合。

typedef struct intset { // 編碼方式 uint32_t encoding; // 集合包含的元素?cái)?shù)量 uint32_t length; // 保存元素的數(shù)組 int8_t contents[];} intset;

contents 數(shù)組是整數(shù)集合的底層實(shí)現(xiàn):整數(shù)集合的每個(gè)元素都是 contents 數(shù)組的一個(gè)數(shù)組項(xiàng),各個(gè)項(xiàng)在數(shù)組中按值大小從小到大有序排列,并且數(shù)組中不包含重復(fù)項(xiàng)。

雖然 contents 屬性聲明為 int8_t 類型的數(shù)組,但實(shí)際上,contents 數(shù)組不保存任何 int8_t 類型的值,數(shù)組中真正保存的值類型取決于 encoding。

如果 encoding 屬性值為 INTSET_ENC_INT16,那么 contents 數(shù)組就是 int16_t 類型的數(shù)組,以此類推。

當(dāng)新插入元素的類型比整數(shù)集合現(xiàn)有類型元素的類型大時(shí),整數(shù)集合必須先升級(jí),然后才能將新元素添加進(jìn)來(lái)。這個(gè)過(guò)程分以下三步進(jìn)行:

根據(jù)新元素類型,擴(kuò)展整數(shù)集合底層數(shù)組空間大小;

將底層數(shù)組現(xiàn)有所有元素都轉(zhuǎn)換為與新元素相同的類型,并且維持底層數(shù)組的有序性;

將新元素添加到底層數(shù)組里面。

還有一點(diǎn)需要注意的是,整數(shù)集合不支持降級(jí)。一旦對(duì)數(shù)組進(jìn)行了升級(jí),編碼就會(huì)一直保持升級(jí)后的狀態(tài)。

舉個(gè)例子,當(dāng)執(zhí)行 SADD numbers 1 3 5 向集合對(duì)象插入數(shù)據(jù)時(shí),該集合對(duì)象在內(nèi)存的結(jié)構(gòu)如下:

4.2 set-hashtable

hashtable 編碼的集合對(duì)象使用字典作為底層實(shí)現(xiàn)。字典的每個(gè)鍵都是一個(gè)字符串對(duì)象,每個(gè)字符串對(duì)象對(duì)應(yīng)一個(gè)集合元素,字典的值都是 NULL。

當(dāng)我們執(zhí)行 SADD fruits "apple" "banana" "cherry" 向集合對(duì)象插入數(shù)據(jù)時(shí),該集合對(duì)象在內(nèi)存的結(jié)構(gòu)如下:

5. 有序集合對(duì)象

有序集合的編碼可以是 ziplist 或者 skiplist。當(dāng)有序集合保存的元素個(gè)數(shù)小于 128 個(gè),且所有元素成員長(zhǎng)度都小于 64 字節(jié)時(shí),使用 ziplist 編碼,否則使用 skiplist 編碼。

5.1 zset-ziplist

ziplist 編碼的有序集合使用壓縮列表作為底層實(shí)現(xiàn)。每個(gè)集合元素使用兩個(gè)緊挨著一起的兩個(gè)壓縮列表節(jié)點(diǎn)表示,第一個(gè)節(jié)點(diǎn)保存元素的成員(member),第二個(gè)節(jié)點(diǎn)保存元素的分值(score)。

壓縮列表內(nèi)的集合元素按照分值從小到大排列。如果我們執(zhí)行 ZADD price 8.5 apple 5.0 banana 6.0 cherry 命令向有序集合插入元素,該有序集合在內(nèi)存中的結(jié)構(gòu)如下:

5.2 zset-skiplist

skiplist 編碼的有序集合對(duì)象使用 zset 結(jié)構(gòu)作為底層實(shí)現(xiàn),一個(gè) zset 結(jié)構(gòu)同時(shí)包含一個(gè)字典和一個(gè)跳躍表。

typedef struct zset { zskiplist *zs1; dict *dict;}

繼續(xù)介紹之前,我們先了解一下什么是跳躍表。

跳躍表

跳躍表(skiplist)是一種有序的數(shù)據(jù)結(jié)構(gòu),它通過(guò)在每個(gè)節(jié)點(diǎn)中維持多個(gè)指向其他節(jié)點(diǎn)的指針,從而達(dá)到快速訪問(wèn)節(jié)點(diǎn)的目的。

Redis 的跳躍表由 zskiplistNode 和 zskiplist 兩個(gè)結(jié)構(gòu)定義。zskiplistNode 結(jié)構(gòu)表示跳躍表節(jié)點(diǎn),zskiplist 保存跳躍表節(jié)點(diǎn)相關(guān)信息,比如節(jié)點(diǎn)的數(shù)量,以及指向表頭和表尾節(jié)點(diǎn)的指針等。

跳躍表節(jié)點(diǎn) zskiplistNode

跳躍表節(jié)點(diǎn) zskiplistNode 結(jié)構(gòu)定義如下:

typedef struct zskiplistNode { // 后退指針 struct zskiplistNode *backward; // 分值 double score; // 成員對(duì)象 robj *obj; // 層 struct zskiplistLevel { // 前進(jìn)指針 struct zskiplistNode *forward; // 跨度 unsigned int span; } level[];} zskiplistNode;

下圖是一個(gè)層高為 5,包含 4 個(gè)跳躍表節(jié)點(diǎn)(1 個(gè)表頭節(jié)點(diǎn)和 3 個(gè)數(shù)據(jù)節(jié)點(diǎn))組成的跳躍表:

有序集合對(duì)象的 skiplist 實(shí)現(xiàn)

前面講過(guò),skiplist 編碼的有序集合對(duì)象使用 zset 結(jié)構(gòu)作為底層實(shí)現(xiàn)。一個(gè) zset 結(jié)構(gòu)同時(shí)包含一個(gè)字典和一個(gè)跳躍表。

typedef struct zset { zskiplist *zs1; dict *dict;}

zset 結(jié)構(gòu)中的 zs1 跳躍表按分值從小到大保存了所有集合元素,每個(gè)跳躍表節(jié)點(diǎn)都保存了一個(gè)集合元素。

通過(guò)跳躍表,可以對(duì)有序集合進(jìn)行基于 score 的快速范圍查找。zset 結(jié)構(gòu)中的 dict 字典為有序集合創(chuàng)建了從成員到分值的映射,字典的鍵保存了成員,字典的值保存了分值。通過(guò)字典,可以用O(1)復(fù)雜度查找給定成員的分值。

假如還是執(zhí)行 ZADD price 8.5 apple 5.0 banana 6.0 cherry 命令向 zset 保存數(shù)據(jù),如果采用 skiplist 編碼方式的話,該有序集合在內(nèi)存中的結(jié)構(gòu)如下:

6. 總結(jié)

總的來(lái)說(shuō),Redis 底層數(shù)據(jù)結(jié)構(gòu)主要包括簡(jiǎn)單動(dòng)態(tài)字符串(SDS)、鏈表、字典、跳躍表、整數(shù)集合和壓縮列表六種類型。并且基于這些基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)了字符串對(duì)象、列表對(duì)象、哈希對(duì)象、集合對(duì)象以及有序集合對(duì)象五種常見(jiàn)的對(duì)象類型。每一種對(duì)象類型都至少采用了 2 種數(shù)據(jù)編碼,不同的編碼使用的底層數(shù)據(jù)結(jié)構(gòu)也不同。

責(zé)任編輯:xj

原文標(biāo)題:一文讀懂 Redis 常見(jiàn)對(duì)象類型的底層數(shù)據(jù)結(jié)構(gòu)

文章出處:【微信公眾號(hào):數(shù)據(jù)分析與開(kāi)發(fā)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 數(shù)據(jù)結(jié)構(gòu)

    關(guān)注

    3

    文章

    573

    瀏覽量

    41340
  • Redis
    +關(guān)注

    關(guān)注

    0

    文章

    390

    瀏覽量

    12029

原文標(biāo)題:一文讀懂 Redis 常見(jiàn)對(duì)象類型的底層數(shù)據(jù)結(jié)構(gòu)

文章出處:【微信號(hào):DBDevs,微信公眾號(hào):數(shù)據(jù)分析與開(kāi)發(fā)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    HDMI接口類型介紹

    我們都知道USB接口有很多類型,然而熟悉的HDMI接口,它也有很多不一樣的接口,本文將圍繞HDMI的不同接口類型進(jìn)行解析。
    的頭像 發(fā)表于 10-28 16:11 ?3040次閱讀
    <b class='flag-5'>五</b><b class='flag-5'>種</b>HDMI接口<b class='flag-5'>類型</b>介紹

    【HZ-T536開(kāi)發(fā)板免費(fèi)體驗(yàn)】6、使用protoc-gen-gorm生成標(biāo)準(zhǔn)化的數(shù)據(jù)結(jié)構(gòu)

    在設(shè)計(jì)espnow協(xié)議的時(shí)候,考慮到我需要在esp32,Linux設(shè)備,web上使用相同的數(shù)據(jù)結(jié)構(gòu),那就需要考慮一下,是否使用一個(gè)通用的跨平臺(tái)序列化數(shù)據(jù)結(jié)構(gòu)。這時(shí)候我想起了protobuf,這個(gè)就是
    發(fā)表于 08-26 00:32

    Redis集群部署配置詳解

    Redis集群是一分布式Redis解決方案,通過(guò)數(shù)據(jù)分片和主從復(fù)制實(shí)現(xiàn)高可用性和橫向擴(kuò)展。集群將整個(gè)數(shù)據(jù)集分割成16384個(gè)哈希槽(has
    的頭像 發(fā)表于 07-17 11:04 ?577次閱讀

    【幸狐Omni3576邊緣計(jì)算套件試用體驗(yàn)】Redis最新8.0.2版本源碼安裝及性能測(cè)試

    的結(jié)果進(jìn)行對(duì)比。 一、Redis是什么 維基百科的介紹是: Redis是一個(gè)使用ANSI C編寫(xiě)的開(kāi)源、支持網(wǎng)絡(luò)、基于內(nèi)存、分布式、可選持久性的鍵值對(duì)存儲(chǔ)數(shù)據(jù)庫(kù)。 Redis官網(wǎng)的
    發(fā)表于 06-03 01:28

    程序設(shè)計(jì)與數(shù)據(jù)結(jié)構(gòu)

    《程序設(shè)計(jì)與數(shù)據(jù)結(jié)構(gòu)》重點(diǎn)闡述了三大方向內(nèi)容: 1. C語(yǔ)言學(xué)習(xí)中的痛點(diǎn):針對(duì)當(dāng)前工程師在C語(yǔ)言學(xué)習(xí)中的痛點(diǎn),如指針函數(shù)與函數(shù)指針,如何靈活應(yīng)用結(jié)構(gòu)體等。從變量的三要素(變量的類型,變量的值和變量
    發(fā)表于 05-13 16:45

    層數(shù)層疊結(jié)構(gòu)PCB的布線策略

    層數(shù) PCB 的布線策略豐富多樣,具體取決于 PCB 的功能。這類電路板可能涉及多種不同類型的信號(hào),從低速數(shù)字接口到具有不同信號(hào)完整性要求的多個(gè)高速數(shù)字接口。從布線規(guī)劃和為各接口分配信號(hào)層的角度來(lái)看,這無(wú)疑是一項(xiàng)極具挑戰(zhàn)性的任務(wù)。
    的頭像 發(fā)表于 05-07 14:50 ?1231次閱讀
    高<b class='flag-5'>層數(shù)</b>層疊<b class='flag-5'>結(jié)構(gòu)</b>PCB的布線策略

    請(qǐng)問(wèn)K230D怎么將攝像頭采集的視頻數(shù)據(jù)通過(guò)串口輸出?

    我連了個(gè)WiFi模塊,想要將攝像頭采集的視頻數(shù)據(jù)通過(guò)串口發(fā)送出去。之前都是用的STM32,不太會(huì)MicroPython,搞不懂對(duì)象數(shù)據(jù)結(jié)構(gòu),求教。
    發(fā)表于 04-28 06:16

    redis集群方案詳解

    Redis中提供的集群方案總共有三(一般一個(gè)redis節(jié)點(diǎn)不超過(guò)10G內(nèi)存)。
    的頭像 發(fā)表于 03-31 10:46 ?1258次閱讀
    <b class='flag-5'>redis</b>三<b class='flag-5'>種</b>集群方案詳解

    對(duì)象存儲(chǔ)是什么結(jié)構(gòu)類型?

    對(duì)象存儲(chǔ)屬于非結(jié)構(gòu)數(shù)據(jù)存儲(chǔ)架構(gòu),采用扁平化命名空間結(jié)構(gòu)。其核心通過(guò)唯一標(biāo)識(shí)符(ObjectID)定位數(shù)據(jù)
    的頭像 發(fā)表于 02-10 11:14 ?681次閱讀

    Redis實(shí)戰(zhàn)筆記

    在目前的技術(shù)選型中,Redis 儼然已經(jīng)成為了系統(tǒng)高性能緩存方案的事實(shí)標(biāo)準(zhǔn),因此現(xiàn)在?Redis 也成為了后端開(kāi)發(fā)的基本技能樹(shù)之一。 ? 基于上述情況,今天給大家分享一份?杰哥?親筆撰寫(xiě)的內(nèi)部
    的頭像 發(fā)表于 02-09 09:12 ?616次閱讀
    <b class='flag-5'>Redis</b>實(shí)戰(zhàn)筆記

    Redis Cluster之故障轉(zhuǎn)移

    1. Redis Cluster 簡(jiǎn)介 Redis Cluster 是 Redis 官方提供的 Redis 集群功能。 為什么要實(shí)現(xiàn) Redis
    的頭像 發(fā)表于 01-20 09:21 ?1238次閱讀
    <b class='flag-5'>Redis</b> Cluster之故障轉(zhuǎn)移

    AWG電纜的常見(jiàn)材料類型

    美國(guó)線規(guī)(AWG)是一用于指定電線直徑的標(biāo)準(zhǔn)化系統(tǒng),它在美國(guó)廣泛使用。AWG系統(tǒng)基于電線的直徑,數(shù)字越小,電線越粗。電線的材料類型對(duì)于其性能、成本和應(yīng)用至關(guān)重要。以下是一些AWG電纜的常見(jiàn)材料
    的頭像 發(fā)表于 01-13 17:19 ?1684次閱讀

    華為云Flexus X實(shí)例,Redis性能加速評(píng)測(cè)及對(duì)比

    隨著云計(jì)算技術(shù)的飛速發(fā)展,Redis 作為一高性能的內(nèi)存數(shù)據(jù)庫(kù),在各種應(yīng)用場(chǎng)景中發(fā)揮著越來(lái)越重要的作用。為了滿足不同用戶對(duì) Redis 性能的高要求,華為云推出了 Flexus X
    的頭像 發(fā)表于 12-29 15:47 ?820次閱讀
    華為云Flexus X實(shí)例,<b class='flag-5'>Redis</b>性能加速評(píng)測(cè)及對(duì)比

    Redis緩存與Memcached的比較

    關(guān)鍵特性和差異: 1. 數(shù)據(jù)存儲(chǔ) Redis: Redis是一個(gè)開(kāi)源的鍵值存儲(chǔ),支持多種數(shù)據(jù)結(jié)構(gòu),如字符串、列表、集合、有序集合、散列、位圖、超日志和地理空間索引。 它支持持久化,可以
    的頭像 發(fā)表于 12-18 09:33 ?897次閱讀

    常見(jiàn)的PCB表面處理技術(shù)

    PCB(印刷電路板)的表面處理技術(shù)對(duì)于保證電路板的焊接性能、電氣連接可靠性以及耐腐蝕性具有重要意義,不同的表面處理技術(shù)適用于不同的應(yīng)用場(chǎng)景和性能要求。本文將帶您詳細(xì)了解常見(jiàn)的PCB表面處理技術(shù)。
    的頭像 發(fā)表于 12-11 09:17 ?3881次閱讀