Redis 已是你們耳熟能詳的東西了,平常工做也都在使用,面試中也是高頻的會涉及到,那麼咱們對它究竟瞭解有多深入呢?html
我讀了幾本 Redis 相關的書籍,嘗試去了解它的具體實現,將一些底層的數據結構及實現原理記錄下來。面試
本文將介紹 Redis 中 五種基礎數據類型 的實現方法。 這五種基本類型基本覆蓋了咱們業務中使用的 80%的場景,對面試也覆蓋至少 90%.(其中重點固然是有序集合以及散列結構咯).redis
在前面的八篇文章中,咱們詳細的介紹了 Redis 中的 8 種基本數據結構,可是衆所周知,Redis 經常使用的數據類型有五種。包括,字符串,列表,集合,有序集合,哈希。數據庫
而這五種數據類型,底層就是用前面介紹的數據結構實現的,固然,並非直接一對一的綁定關係,而是採用了精妙的設計,構建了一個對象系統。編程
熟悉 OOP 編程的讀者,可能很快就能想到爲何要這麼設計了,對象系統帶來的好處是很是多的,可是並不在這一篇文章中講。這裏只是提到對象系統,讓你們對於五種數據類型爲何能夠用一些花裏胡哨的方法來實現,有一個初步的瞭解。後端
接下來將逐一分析五種數據類型的底層實現數據結構,及實現方式(編碼)之間的切換條件。微信
注:後續提到五種數據類型,用 xx 對象來指代。好比 字符串對象,列表對象。提到的底層數據結構,用全稱來說。markdown
涉及到的數據結構,SDS
, 強烈建議閱讀本系列第一篇文章。數據結構
字符串對象的底層實現有三種可能:int, raw, embstr.oop
若是一個字符串對象,保存的值是一個整數值,而且這個整數值在 long 的範圍內,那麼 redis 用整數值來保存這個信息,而且將字符串編碼設置爲 int
.
好比:
若是字符串對象保存的是一個字符串, 而且長度大於 32 個字節,它就會使用前面講過的SDS(簡單動態字符串)
數據結構來保存這個字符串值,而且將字符串對象的編碼設置爲raw
.
若是字符串對象保存的是一個字符串, 可是長度小於 32 個字節,它就會使用embstr
來保存了,embstr
編碼不是一個數據結構,而是對 SDS 的一個小優化,當使用 SDS 的時候,程序須要調用兩次內存分配,來給 字符串對象 和 SDS 各自分配一塊空間,而embstr
只須要一次內存分配,由於他須要的空間不多,因此採用 連續的空間保存,即將 SDS 的值和 字符串對象的值放在一塊連續的內存空間上。這樣能在短字符串的時候提升一些效率。
好比:
redis 的字符串數據類型是支持保存浮點數,而且支持對浮點數進行加減操做,可是 redis 在底層是把浮點數轉換成字符串值,以後走上面三種編碼的規則的。對浮點數進行操做時,也是從字符串轉換成浮點數進行計算,而後再轉換成字符串進行保存的。
這塊的知識實際上是很符合咱們的認知的,好比 int
編碼只能夠保存整數,那麼當咱們對一個 int 編碼的字符串對象,修改它的值,它天然就會使用 raw 編碼了。
可是有一個特性,Redis 沒有爲embstr
編碼提供任何的修改操做,這也就是爲何它只是個編碼而不是一個數據結構的緣由。
因此在 Redis 中,embstr
編碼的值實際上是 只讀的,只要發生修改,馬上將編碼轉換成 raw
.
字符串對象底層的數據結構或者說編碼有三種,分別是 int
, raw
, embstr
. 他們之間的使用條件以下:
編碼 | 使用條件 |
---|---|
int | 能夠用 long 保存的整數 |
embstr | 字符串長度小於 32 字節(或者浮點數轉換後知足) |
raw | 長度大於 32 的字符串 |
涉及到的數據結構,壓縮列表
, 雙向鏈表
, 快速列表
, 強烈建議閱讀本系列的第 二,三,四 篇文章。
在 Redis 3.2 版本以前,列表對象底層由 壓縮列表和雙向鏈表配合實現,當元素數量較少的時候,使用壓縮列表,當元素數量增多,就開始使用普通的雙向鏈表保存數據。
可是這種實現方式不夠好,雙向鏈表中的每一個節點,都須要保存先後指針,這個內存的使用量 對於 Redis 這個內存數據庫來講極其不友好。
所以在 3.2 以後的版本,做者新實現了一個數據結構,叫作 quicklist. 全部列表的底層實現都是這個數據結構了。它的底層實現基本上就是將 雙向鏈表和壓縮列表進行告終合,用雙向的指針將壓縮列表進行鏈接,這樣不只避免了壓縮列表存儲大量元素的性能壓力,同時避免了雙向鏈表鏈接指針佔用空間過多的問題。
具體的原理講解請 閱讀對應的文章,這裏再也不贅述。
編碼 | 使用條件 |
---|---|
quicklist | 全部數據 |
涉及到的數據結構:intset
, dict(hashtable)
, 強烈建議閱讀本系列第五,第六篇文章。
集合對象的編碼能夠是 intset 或者 hashtable(字典) .
當集合中的全部元素都是整數,且元素的數量不大於 512 個的時候,使用 intset 編碼。
intset 編碼時,底層使用 intset
數據結構。
當元素不符合所有爲整數值且元素個數小於 512
時,集合對象使用的編碼方式爲** hashtable**.
字典的每個鍵都是一個字符串對象,其中保存了集合裏的一個元素,字典的值所有被設置爲 NULL.
編碼 | 使用條件 |
---|---|
intset | 全部元素都是整數且元素個數小於 512 |
hashtable | 其餘數據 |
涉及到的數據結構,壓縮列表
, 跳躍表
, 字典
, 強烈建議閱讀本系列 第三篇,第六篇,第七篇文章。
有序集合對象的編碼能夠是 ziplist
以及skiplist
.
當使用 ziplist 編碼時,有序集合對象的實現數據結構爲ziplist
(聽起來像句廢話), 每一個集合的元素 (key-value), 使用兩個緊挨着的壓縮列表的節點來表示,第一個節點保存集合元素的成員 (member), 第二個節點保存集合元素的分支 (score).
在壓縮列表的內部,集合元素按照分值從小到大進行排序。
當使用 skiplist 編碼的時候,內部使用zset
來實現數據的保存,zset
的定義以下:
typedef struct zset{ zskiplist *zsl; dict *dict; }zset; 複製代碼
爲何須要同時使用跳躍表以及字典呢?
其實若是咱們細想,單獨使用字典或者跳躍表,都是能夠實現有序集合的全部功能的,可是性能太差勁了。
所以,將字典和跳躍表結合進行使用,能夠在 O(1) 的時間複雜度下完成查詢分值操做,而對一些範圍操做,使用跳躍表能夠達到 O(logn) 的是纏綿複雜度。
能夠看到,我在上一次的例子中,添加了一個很長的 key 以後,有序集合的編碼方式就成爲了skiplist
.
編碼 | 使用條件 |
---|---|
ziplist | 元素數量少於 128 且 全部元素成員的長度小於 64 字節 |
skiplist | 不知足上述條件的其餘狀況 |
涉及到的數據結構,壓縮列表
, 字典
, 強烈建議閱讀本系列 第三篇,第六篇文章。
哈希對象的編碼能夠是ziplist
或者hashtable
.
ziplist 編碼下的哈希對象,使用了壓縮列表做爲底層實現數據結構,用兩個連續的壓縮列表節點來表示哈希對象中的一個鍵值對。實現方式相似於上面的有序集合的場景。
如圖中所示,當我放入了兩個簡單的鍵值對,此時哈希對象的編碼爲 ziplist.
這是對 hashtable 最直觀的應用了~
哈希結構自己在結構上和字典 (hashtable) 就頗爲類似,所以哈希對象中的每個鍵值對都是字典中的一個鍵值對。
如圖中所示,當我在上一個示例中額外加入一個很長的值,那麼編碼方式就來到了hashtable
.
編碼 | 使用條件 |
---|---|
ziplist | 鍵值對的鍵和值的長度都小於 64 字節,且 鍵值對個數小於 512. |
hastable | 不知足上述條件的其餘條件 |
其實在前面的幾篇文章寫完以後,也就是在全部的底層數據結構介紹完以後,所謂的Redis 的五種基礎數據類型的底層實現原理
就已經沒有了難度。
全部用到的底層數據結構都知道了,剩下的無非是個排列組合問題以及各類實現方式之間的切換條件,而後這個條件也僅僅是瞭解性知識,強行記住也沒有必要。
這裏把五種基礎數據類型的可能的編碼列出來方便理解及記憶。
基礎數據類型 | 可能的編碼方式 |
---|---|
字符串 | int, raw, embstr |
列表 | 以前是 ziplist 和 linkedlist, 如今全是 quicklist 了。 |
集合 | intset 或者 hashtable |
有序集合 | ziplist 或者 skiplist , skiplist 編碼中使用了跳躍表+字典 |
散列 | ziplist 或者 hashtable |
至於他們的轉換條件,因爲我不會用 markdown 畫多維表格,可是又不想寫 html, 就不作總結了,須要的讀者能夠點擊目錄跳轉至每個小結的總結~.
《Redis 的設計與實現(第二版)》
《Redis 深度歷險:核心原理和應用實踐》
完。
最後,歡迎關注個人我的公衆號【 呼延十 】,會不按期更新不少後端工程師的學習筆記。 也歡迎直接公衆號私信或者郵箱聯繫我,必定知無不言,言無不盡。
以上皆爲我的所思所得,若有錯誤歡迎評論區指正。
歡迎轉載,煩請署名並保留原文連接。
聯繫郵箱:huyanshi2580@gmail.com
更多學習筆記見我的博客或關注微信公衆號 < 呼延十 >------>呼延十