聊一聊Redis的資料結構

2022-07-04 18:06:17 字數 4531 閱讀 9366

如果沒有記錯的話,應該是在兩個月前把 《redis設計與實踐》 這本書啃完了,確實是一本講redis的不可多得的好書,但是一直遲遲沒有寫自己的一些總結。一來是因為沒有時間,二來是沒有找到乙個合適的思考點。

redis本身支援很多種不同的型別,能讓我們在不同的複雜的業務邏輯中游刃有餘。redis也可以說是萬物皆物件,他就是乙個個的鍵值對所組成,但是我們都知道,作為一款nosql,雖然通過o(1)的方式能很快獲取到資料,但是這也就暴露了他的乙個弊端,對於範圍查詢,或者排序之類的無法很好的處理。

然而我們都知道,redis有乙個sorted set的有序集合物件,這能幫助我們實現範圍查詢,所以,為什麼他能實現mysql此類關係型資料庫才有的特性?而這也是我想寫這篇文章的原因,也是作為自己對於《redis設計與實踐》這本書的乙個總結。

我們經常看到此類的文章:

redis的五種資料結構

redis的資料結構以及對應的使用場景

其實以資料結構這個詞去說明redisstringhashlistsetsorted set是不夠嚴謹和準確的,準確的來講,這是redis的五個物件,所以這也是我在上文為什麼把有序集合稱之為物件的原因。

首先要介紹的就是sds,簡單動態字串是redis中大多數物件所需要使用的乙個結構,比如我們設定乙個簡單的string:

set hello nine
此時其實string會建立兩個sds,分別用於報告鍵和值。

再比如:

rpush nine 18 male
此時會建立乙個列表物件,而列表物件中的兩個值又會使用兩個sds

sds中有兩個特點需要闡述一下:

sds的結構包括三個值:len用於記錄長度,free用於記錄所剩空間,buf用於記錄內容。所以,這也能幫助我們很快的去獲取到乙個string的長度;

空間分配:我們每次給sds新增值或者攔截值的時候,他會重新判斷是否長度,如果是concat值,分配的空間是:

min(2 * len , 1m)
如果是擷取字段,那麼空間還保留,以便下次使用。

鍊錶是redislist的底層實現,其實和我們經常所看到的雙向鍊錶一致,不過redis的鍊錶中有封裝乙個headtail用於指向表頭和表尾,能幫助我們很快的找到頭和尾。

字典相對而言結構會複雜一些,下圖是該書的作者所畫的字典的結構圖:

dict中有兩個較為重要的,乙個是ht陣列,包含了兩個dictht結構,為什麼有兩個,這樣豈不是很佔記憶體嗎?的確,不過第二個dictht其實是為了減少我們在rehash中所耗費的時間而採用的一種解決方式。

rehashidx代表此字典是否在rehash的過程中。

dictht中的table用來管理資料,其中的0-n代表的是指向鍵值對的索引,而對應的keyvalue結構中還有乙個next,這個可以幫助我們找到這個索引下面的另外的鍵值對。

size代表這個table的長度,sizemask代表雜湊表的大小掩碼,用於計算索引值,總是等於size - 1

其實前面也提到了乙個問題,就是雜湊表是比較占用空間的,所以我們應該有乙個工具來定期給雜湊表**,這也就是我們前面提到的rehash,而前面我們所提到的ht中的另外乙個dictht,就是用來做這個事情的:

當我們對資料進行crud的時候,我們在操作之後,會把這個鍵值對記錄到第二個dictht中,然後刪掉ht[0]中所對應的鍵值對,一旦當我們的ht[0]的長度為空,那麼也就意味著我們的rehash已經完成,此時交換二者即可(這裡其實有點像node v8引擎中的新生代的過程)。

但是,這裡就有乙個問題了,我在刪除的過程中,如果我刪掉了,但是還沒完成rehash,那麼我怎麼獲取資料呢?其實redis也已經幫我們做完了這份工作,當我們在ht[0]中獲取不到我們的值時,我們會到ht[1]中去查詢。此外,redis的字典還有乙個非常智慧型的方式,當我們開始rehash之後,那麼我們就不會再往ht[0]中新增任何元素,都會直接新增到ht[1]中,而這也就加快了我們rehash的過程。

跳躍表作為有序集合的實現方式之一,在redis中也同樣扮演者重要的角色。在網上沒有找到很好的圖示,所以自己根據書中的圖花了乙個對應的結構圖:

其中的o1,o2...代表所繫結的成員物件。而上面的1.0之類的就代表對應的分值了,我們可以看到,跳躍表引入了層級的概念,這會幫助我們系統在查詢範圍時更加快捷和方便(為什麼會更快可以參考這篇文章)。

上面這張圖大概就是描述redis裡面的乙個跳躍表結構,score就是分數也就是節點的value值,查詢的時候就是利用每一層的跨度從高到低去比較value的大小最終確定結果,也可以理解成從高到低不斷的縮小查詢範圍,跟二分查詢有點類似,這也就是我們上面說的跳躍表就是乙個可以二分的鍊錶,但是可以看到除了score還有乙個span欄位,正常的跳躍表是沒有這個span欄位的,而作者為了可以實現範圍查詢(分頁)擴充套件了乙個這個字段用來記錄從高到低每個節點距離下乙個節點之間的跨度。

同時,我們可以看到,在zskiplist中還有乙個bw元素,代表後退指標,這個可以幫助我們倒序,也就是zset中用到的rev

整數集合是集合的底層實現之一,當集合只包含整數並且個數較少時,會使用到整數集合,是乙個無重複且按照大小排列的陣列。

redis提供了乙個方法幫助我們去識別所使用的資料結構:

sadd test 1 2 3

object encoding test

//intset

整數集合非常簡單,不過有乙個公升級和降級的過程有點意思,可以稍微留意一下。在整數集合的結構中,有乙個encoding用於記錄集合的型別,取決於最大的數所佔的空間,每次新增值時,會對值進行判斷,如果超過了目前的encoding,那麼就需要對整個陣列進行公升級,增加了靈活度,同時也節約了空間。降級同理。

壓縮列表也是listhash的實現方式之一,他的主要特點是資料緊湊在一起,放在一塊連續的空間中,當我們使用時,如果字段本身沒有超過一定的範圍,同時整體的長度沒有超過限制,那麼就會使用壓縮列表。比如:

rpush nine 18 male

object encoding nine

// ziplist

壓縮列表有以下幾個較為重要的屬性:

zlbytes:記錄整個壓縮列表所佔的記憶體位元組數

zltail:尾部距離首部的距離

zllen:位元組數量

zlend:標記末端

entryx:列表中所對應的節點

entryx結構中有乙個重要屬性:previous_entry_length代表前面乙個節點的長度,所以當我們知道了某乙個節點所對應的位置時,那麼用這個位置減去previous_entry_length即可得到他的前乙個節點的位置,這樣就完成了遍歷。

前面已經講完了我們所需要用到的redis的幾種資料結構,我們所使用的五種型別,正是基於這幾種資料結構來實現的,但是前面我們也說過了,一種型別可以由不同的資料結構來實現,或者由幾個資料結構來共同實現。下面我整理了乙個**,關於這五種型別的實現方式以及細節:

這是我的個人部落格位址。

redis設計與實現 online

聊一聊資料結構

資料結構 2.棧和佇列 二 樹三 圖 線性結構是一種基本的資料結構,主要用於對客觀世界中具有單一前驅和後繼的資料關係進行描述。線性結構的特點是資料元素之間呈現一種線性關係,即元素 乙個接乙個排列 線性表常採用順序儲存和鏈式儲存,主要的操作是 插入 刪除 查詢 線性表的順序儲存是指用一組位址連續的儲存...

聊一聊元資料

這個話題來自我的msn space。這是原文 元資料 metadata 這個詞現在到處氾濫。其實我對元資料充其量只能說有自己的理解而已,並不能確信這個理解是正確的。我認為,資料結構分為三個層次 uml可是四層哦 例項層 直接描述特異化的資料場景 元資料層 描述例項的結構的一組資料 元資料的元資料層 ...

聊一聊hive資料傾斜

info基本資訊表 user id name agegender 1henry16男 2jack17男 3anny18女 4candy19女 5kate20女 burke 21frank 22ellen 23ken 24mili 25.score成績表 user id subject id scor...