圖解redis五種數據結構底層實現(動圖哦)

2021-01-09 RyuGou的技術窩

redis有五種基本數據結構:字符串、hash、set、zset、list。但是你知道構成這五種結構的底層數據結構是怎樣的嗎? 今天我們來花費五分鐘的時間了解一下。 (目前redis版本為3.0.6)

動態字符串SDS

SDS是"simple dynamic string"的縮寫。 redis中所有場景中出現的字符串,基本都是由SDS來實現的

所有非數字的key。例如 setmsg"hello world" 中的key msg.字符串數據類型的值。例如`` set msg "hello world"中的msg的值"hello wolrd"非字符串數據類型中的「字符串值」。例如 RPUSH fruits"apple""banana""cherry"中的"apple" "banana" "cherry"SDS長這樣:

free:還剩多少空間 len:字符串長度 buf:存放的字符數組

空間預分配

為減少修改字符串帶來的內存重分配次數,sds採用了「一次管夠」的策略:

若修改之後sds長度小於1MB,則多分配現有len長度的空間若修改之後sds長度大於等於1MB,則擴充除了滿足修改之後的長度外,額外多1MB空間

惰性空間釋放

為避免縮短字符串時候的內存重分配操作,sds在數據減少時,並不立刻釋放空間。

int

就是redis中存放的各種數字 包括一下這種,故意加引號「」的

雙向鍊表

長這樣:

分兩部分,一部分是「統籌部分」:橘黃色,一部分是「具體實施方「:藍色。

主體」統籌部分「:

head指向具體雙向鍊表的頭tail指向具體雙向鍊表的尾len雙向鍊表的長度具體"實施方":一目了然的雙向鍊表結構,有前驅 pre有後繼 next

由 list和 listNode兩個數據結構構成。

ziplist

壓縮列表。 redis的列表鍵和哈希鍵的底層實現之一。此數據結構是為了節約內存而開發的。和各種語言的數組類似,它是由連續的內存塊組成的,這樣一來,由於內存是連續的,就減少了很多內存碎片和指針的內存佔用,進而節約了內存。

然後文中的 entry的結構是這樣的:

元素的遍歷

先找到列表尾部元素:

然後再根據ziplist節點元素中的 previous_entry_length屬性,來逐個遍歷:

連鎖更新

再次看看 entry元素的結構,有一個 previous_entry_length欄位,他的長度要麼都是1個字節,要麼都是5個字節:

前一節點的長度小於254位元組,則 previous_entry_length長度為1位元組前一節點的長度小於254位元組,則 previous_entry_length長度為5位元組假設現在存在一組壓縮列表,長度都在250位元組至253位元組之間,突然新增一新節點 new, 長度大於等於254位元組,會出現:

程序需要不斷的對壓縮列表進行空間重分配工作,直到結束。

除了增加操作,刪除操作也有可能帶來「連鎖更新」。 請看下圖,ziplist中所有entry節點的長度都在250位元組至253位元組之間,big節點長度大於254位元組,small節點小於254位元組。

哈希表

哈希表略微有點複雜。哈希表的製作方法一般有兩種,一種是: 開放尋址法,一種是 拉鏈法。redis的哈希表的製作使用的是 拉鏈法。

整體結構如下圖:

也是分為兩部分:左邊橘黃色部分和右邊藍色部分,同樣,也是」統籌「和」實施「的關係。 具體哈希表的實現,都是在藍色部分實現的。 先來看看藍色部分:

這也分為左右兩邊「統籌」和「實施」的兩部分。

右邊部分很容易理解:就是通常拉鍊表實現的哈希表的樣式;數組就是bucket,一般不同的key首先會定位到不同的bucket,若key重複,就用鍊表把衝突的key串起來。

新建key的過程:

假如重複了:

rehash

再來看看哈希表總體圖中左邊橘黃色的「統籌」部分,其中有兩個關鍵的屬性: ht和 rehashidx。 ht是一個數組,有且只有倆元素ht[0]和ht[1];其中,ht[0]存放的是redis中使用的哈希表,而ht[1]和rehashidx和哈希表的 rehash有關。

rehash指的是重新計算鍵的哈希值和索引值,然後將鍵值對重排的過程。

加載因子(load factor)=ht[0].used/ht[0].size。

擴容和收縮標準

擴容:

沒有執行BGSAVE和BGREWRITEAOF指令的情況下,哈希表的 加載因子大於等於1。正在執行BGSAVE和BGREWRITEAOF指令的情況下,哈希表的 加載因子大於等於5。收縮:

加載因子小於0.1時,程序自動開始對哈希表進行收縮操作。擴容和收縮的數量

擴容:

第一個大於等於 ht[0].used*2的 2^n(2的n次方冪)。收縮:

第一個大於等於 ht[0].used的 2^n(2的n次方冪)。(以下部分屬於細節分析,可以跳過直接看擴容步驟)

對於收縮,我當時陷入了疑慮:收縮標準是 加載因子小於0.1的時候,也就是說假如哈希表中有4個元素的話,哈希表的長度只要大於40,就會進行收縮,假如有一個長度大於40,但是存在的元素為4即( ht[0].used為4)的哈希表,進行收縮,那收縮後的值為多少?

我想了一下:按照前文所講的內容,應該是4。 但是,假如是4,存在和收縮後的長度相等,是不是又該擴容? 翻開源碼看看:

收縮具體函數:

int dictResize(dict *d) { int minimal; //如果dict_can_resize被設置成0,表示不能進行rehash,或正在進行rehash,返回出錯標誌DICT_ERR if (!dict_can_resize || dictIsRehashing(d)) return DICT_ERR; minimal = d->ht[0].used; //獲得已經有的節點數量作為最小限度minimal if (minimal < DICT_HT_INITIAL_SIZE)//但是minimal不能小於最低值DICT_HT_INITIAL_SIZE(4) minimal = DICT_HT_INITIAL_SIZE; return dictExpand(d, minimal); //用minimal調整字典d的大小} int dictExpand(dict *d, unsigned long size) { dictht n; unsigned long realsize = _dictNextPower(size); //獲得一個最接近2^n的realsize if (dictIsRehashing(d) || d->ht[0].used > size) //正在rehash或size不夠大返回出錯標誌 return DICT_ERR; if (realsize == d->ht[0].size) return DICT_ERR; //如果新的realsize和原本的size一樣則返回出錯標誌 /* Allocate the new hash table and initialize all pointers to NULL */ //初始化新的哈希表的成員 n.size = realsize; n.sizemask = realsize-1; n.table = zcalloc(realsize*sizeof(dictEntry*)); n.used = 0; /* Is this the first initialization? If so it's not really a rehashing * we just set the first hash table so that it can accept keys. */ if (d->ht[0].table == NULL) { //如果ht[0]哈希表為空,則將新的哈希表n設置為ht[0] d->ht[0] = n; return DICT_OK; } d->ht[1] = n; //如果ht[0]非空,則需要rehash d->rehashidx = 0; //設置rehash標誌位為0,開始漸進式rehash(incremental rehashing) return DICT_OK;} static unsigned long _dictNextPower(unsigned long size){ unsigned long i = DICT_HT_INITIAL_SIZE; //DICT_HT_INITIAL_SIZE 為 4 if (size >= LONG_MAX) return LONG_MAX + 1LU; while(1) { if (i >= size) return i; i *= 2; }}由代碼我們可以看到,假如收縮後長度為4,不僅不會收縮,甚至還會報錯。()

我們回過頭來再看看設定:題目可能成立嗎? 哈希表的擴容都是2倍增長的,最小是4, 4 ===》 8 ====》 16 =====》 32 ======》 64 ====》 128

也就是說:不存在長度為 40多的情況,只能是64。但是如果是64的話,64 X 0.1(收縮界限)= 6.4 ,也就是說在減少到6的時候,哈希表就會收縮,會縮小到多少呢?是8。此時,再繼續減少到4,也不會再收縮了。所以,根本不存在一個長度大於40,但是存在的元素為4的哈希表的。

擴容步驟

收縮步驟

漸進式refresh

在"擴容步驟"和"收縮步驟" 兩幅動圖中每幅圖的第四步驟「將ht[0]中的數據利用哈希函數重新計算,rehash到ht[1]」,並不是一步完成的,而是分成N多步,循序漸進的完成的。 因為hash中有可能存放幾千萬甚至上億個key,畢竟Redis中每個hash中可以存 2^32-1 鍵值對(40多億),假如一次性將這些鍵值rehash的話,可能會導致伺服器在一段時間內停止服務,畢竟哈希函數就得計算一陣子呢((#^.^#))。

哈希表的refresh是分多次、漸進式進行的。

漸進式refresh和下圖中左邊橘黃色的「統籌」部分中的 rehashidx密切相關:

rehashidx 的數值就是現在rehash的元素位置rehashidx 等於 -1 的時候說明沒有在進行refresh

甚至在進行期間,每次對哈希表的增刪改查操作,除了正常執行之外,還會順帶將ht[0]哈希表相關鍵值對rehash到ht[1]。

以擴容步驟為例:

intset

整數集合是集合鍵的底層實現方式之一。

跳表

跳表這種數據結構長這樣:

redis中把跳表抽象成如下所示:

看這個圖,左邊「統籌」,右邊實現。 統籌部分有以下幾點說明:

header: 跳表表頭tail:跳表表尾level:層數最大的那個節點的層數length:跳表的長度實現部分有以下幾點說明:

表頭:是鍊表的哨兵節點,不記錄主體數據。是個雙向鍊表分值是有順序的o1、o2、o3是節點所保存的成員,是一個指針,可以指向一個SDS值。層級高度最高是32。沒每次創建一個新的節點的時候,程序都會隨機生成一個介於1和32之間的值作為level數組的大小,這個大小就是「高度」redis五種數據結構的實現

redis對象

redis中並沒有直接使用以上所說的各種數據結構來實現鍵值資料庫,而是基於一種對象,對象底層再間接的引用上文所說的具體的數據結構。

結構如下圖:

字符串

其中:embstr和raw都是由SDS動態字符串構成的。唯一區別是:raw是分配內存的時候,redisobject和 sds 各分配一塊內存,而embstr是redisobject和raw在一塊兒內存中。

列表

hash

set

zset

相關焦點

  • 詳解Redis五種數據結構的底層原理
    1,redis有五種基本數據結構:string、hash、set、zset、list;底層redis是通過c語言來實現這w五種結構的,具體是如何實現的,我們具體看一下。2,SDS "simple dynamic string",redis中所有場景中出現的字符串,基本都是由SDS來實現的。
  • Redis底層數據結構詳解
    上一篇說了Redis有五種數據類型,今天就來聊一下Redis底層的數據結構是什麼樣的。是這一周看了《redis設計與實現》一書,現來總結一下。(看書總是非常煩躁的!)Redis底層數據結構有六種:1、簡單動態字符串
  • redis—底層數據結構詳解
    encoding:值的編碼方式,就是其底層的數據結構實現。lru:記錄了對象最後一次被訪問的時間,用於淘汰過期的鍵值對。refcount:記錄了對象的引用計數。*ptr:指向數據的指針。二、String的動態字符串在redis底層中對,string的表示主要有三種結構,分別是int編碼格式;embstr編碼格式;raw編碼格式。
  • 整理了一篇文章讓你快速了解Redis底層數據結構
    :String、Hash、List、Set、ZSet(sorted set)五種數據類型。下面就開門見山的介紹這五種數據類型的使用以及這五種數據類型的內部實現的原理。數據結構Redis中的五種數據類型都是由最基礎的數據結構組成的。
  • 最詳細的Redis五種數據結構詳解(理論+實戰),建議收藏
    這是關於Redis的第三篇文章,主要講解Redis的五種數據結構詳解,包括這五種的數據結構的底層原理實現。理論肯定是要用於實踐的,因此最重要的還是實戰部分,也就是這裡還會講解五種數據結構的應用場景。工作,還是的深入了解這五種數據結構的底層原理。
  • redis高並發利器:神奇的位操作,底層原理、數據結構剖析
    本文主要和大家分享一下redis的高級特性:bit位操作。力求讓大家徹底學會使用redis的bit位操作並掌握其底層實現原理!主要包含以下內容:redis位操作命令示例底層數據結構分析為什麼他的算法時間複雜度是O(1)?10億數據量需要多大的存儲空間?redis位操作適合哪些應用場景?
  • redis的5種對象與8種數據結構之字符串對象(上)
    redis的每種對象都由對象結構(redisObject)與對應編碼的數據結構組合而成,redis支持5種對象類型,分別是字符串(string)、列表(list)、哈希(hash)、集合(set)、有序集合(zset),而每種對象類型至少對應兩種編碼方式,不同的編碼方式所對應的底層數據結構是不同的。
  • redis數據類型使用場景_redis 五種數據類型的使用場景 - CSDN
    Redis 數據類型五種類型與類比java的模型string --> Stringhash --> Hashmaplist --> LinkListset --
  • Redis數據結構與對象編碼解析
    作者為每種數據結構提供了不同的實現方式,以適應特定應用場景。的數據結構實現的。,其底層實現之一是哈希表。redis 中並沒有顯式定義 ziplist 的數據結構,僅僅提供了一個描述結構 zlentry 用於操作數據。
  • Redis數據結構與對象編碼 |ObjectEncoding
    作者為每種數據結構提供了不同的實現方式,以適應特定應用場景。中 list 的底層實現之一是雙向鍊表,該結構支持順序訪問,並提供了高效的元素增刪功能。中使用 dict 來保存鍵值對,其底層實現之一是哈希表。
  • redis的5種對象與8種數據結構之字符串對象(下)
    引言本文是對《redis設計與實現(第二版)》中數據結構與對象相關內容的整理與說明。本篇文章只對對象結構,1種對象——字符串對象。以及字符串對象所對應的兩種編碼——raw和embstr,進行了詳細介紹。表達一些本人的想法與看法,也希望更多朋友一起來討論,分享交流。
  • redis 數據類型,string底層結構,穿透,擊穿,雪崩,數據一致性
    一,redis的數據類型 string Hash List Set zset,string的存儲結構是什麼?free記錄的是當前可用的空間,len記錄的當前數據的長度,buf記錄的是當前的數據,它是一個字符數據結構,因為是用C寫的所以後面有&34;。
  • Redis數據結構底層系列-SDS
    而Redis我在上面已經給大家看過結構了,他自己本身就保存了長度的信息,所以我們獲取長度的時間複雜度為0(1),是不是發現了Redis快的一點小細節了?還沒完,不止這些。減少修改字符串時帶來的內存重分配次數C語言字符串底層也是一個數組
  • redis的五種數據結構和應用場景:微博微信點讚+加購物車等
    Redis五種數據結構如下:>1.String 字符串類型是redis中最基本的數據類型,一個key對應一個value。String類型是二進位安全的,意思是 redis 的 string 可以包含任何數據。如數字,字符串,jpg圖片或者序列化的對象。
  • redis五種數據類型及使用場景
    而Redis的 value 支持五種數據類型,分別為string (字符串類型),hash (表類型),list (列表類型),set(集合類型) 和zset (有序集合類型)。1.String(字符串)類型 字符串類型是 Redis 中最為基礎的數據存儲類型,它在 Redis 中以二進位保存,沒有編碼和解碼的過程。
  • 小白也能看懂的Redis基礎:Redis基礎數據結構
    接下來就由小編我帶大家來揭秘Redis的五種基本數據結構。Redis是C語音編寫的基於內存的數據結構存儲系統。它可以當作資料庫、緩存和消息中間件。它支持多種類型的數據結構,如 字符串(strings),列表(lists), 字典(dictht),集合(sets), 有序集合(sorted sets)。通常我們在項目中可以用它來做緩存、記錄籤到數據、分布式鎖等等。要使用Redis首先我們來了解一下它的五種基礎數據結構。
  • 從底層告訴你數據結構原理
    前言今天來說下Redis中hash、set、zset的底層數據結構原理!Redis-哈希對象(hash)hash的底層存儲有兩種數據結構,一種是ziplist,另外一種是hashtable,這兩種數據結構我們之前都有講解,ziplist就是上文提到的結構,hashtable之前講解的redis結構,hash對象只有同時滿足以下條件,才會採用
  • Redis的String類型的數據結構
    很多用過redis的同事都知道redis的5種基本數據結構。String、List、Hash、Set、zset。少部分同事會知道Redis中的Bitmap、Geo、HyperLogLog以及布隆過濾器。Redis這些數據類型的數據結構是怎麼設計的呢?人人都說redis對內存優化到了極致,具體是怎麼優化的呢?
  • 緩存神器Redis的五種數據類型及使用
    今天先來說一說redis作為緩存使用,提供了5 種基礎數據結構,分別為:string (字符串)、list (列表)、set (集合)、hash (哈 希) 和 zset (有序集合)。Redis 所有的數據結構都是以唯一的 key 字符串作為名稱,然後通過這個唯一key值來獲取相應的 value 數據。不同類型的數據結構的差異就在於value 的結構不一樣。字符串結構使用非常廣泛,一個常見的用途就是緩存用戶信息。我們將用戶信息結構體 使用 JSON 序列化成字符串,然後將序列化後的字符串塞進 Redis 來緩存。同樣,取用戶 信息會經過一次反序列化的過程。
  • Redis源碼剖析 - Redis內置數據結構之字典dict
    哈希表在C++中對應的是map數據結構,但在Redis中稱作dict(字典)。Redis只是用了幾個簡單的結構體和幾種常見的哈希算法就實現了一個簡單的類似高級語言中的map結構。下面我們來具體分析一下dict的實現。