【redis前傳】爲何set底層hashtable+intset兩種數據結構 | 超出長度升級解決

「本文已參與好文召集令活動,點擊查看:後端、大前端雙賽道投稿,2萬元獎池等你挑戰!html

redis的整數集是什麼?當咱們想set集合中添加整數時內部又是什麼結構?整數集默認是多少範圍?超出了範圍的數據是如何存儲的?刪除最長元素後會不會發生降級的變化? 今天,咱們就來對整數集一探究竟前端

爲何整數集升級後不能在進行降級操做 | intset位升級頻率java

往期回顧

【redis前傳】redis字典快速映射+hash釜底抽薪 | 單線程不影響後臺工做之漸進式rehashredis

【redis正傳】redis淘汰+過時雙向保證高可用 | 單線程如何作到快速響應後端

【redis前傳】 redis五大天王值list基本數據如何成長 | 由內以外深刻學習數組

【redis前傳】本身手寫一個LRU策略 | 抓住時間的尾巴markdown

基於redis實現分佈式鎖數據結構

【redis前傳】zset如何解決內部鏈表查找效率低下|跳錶構建app

前言

整數集合相信有的同窗沒有據說過,由於redis對外提供的只有封裝的五大對象!而咱們本系列主旨是學習redis內部結構。內部結構是redis五大結構重要支撐!分佈式

前面咱們分別從redis內部結構分析了redis的List、Hash、Zset三種數據結構了。今天咱們再來分析set數據結構內部是如何存儲的

基本結構

  • 在src/t_set.c中咱們發現這樣一段代碼

image-20210706105819274

  • 由此咱們可知在set中是由兩種數據結構構成的: hashtable+intset 。關於redis內部其餘的結構我專門在【redis專欄中有介紹】。hashtable不是咱們今天的主角,咱們今天先分析intset俗稱整數集合。

image-20210706110151754

  • 從上圖中咱們能夠看出,我構造了兩個set集合分別爲【commonset】、【cs】。兩個集合前者存儲字符串、後者專門存儲數字。

  • 咱們在經過object encoding key 來查看下兩個集合的底層數據結構,發現一個是hashtable 一個是intset 。這也驗證了咱們上面對set基本結構的描述。

  • 在redis中對外提供五大類型實際上都是redis的一個抽象對象叫作redisobject。在內部映射了咱們redis內部的數據結構

image-20210706111432308

  • 針對commonset和cs兩個集合在內部數據結構大概能夠這麼理解

image-20210706112349968

什麼時候使用intset

  • 你能夠單純的認爲只要是數字就會使用intset結構來存儲,我恐怕要給你當頭一棒了。實際上並非這樣

  • 須要同時知足如下兩個條件:

image.png

intset

image-20210706133736601

  • 圖中表示的很清楚了,在intset中的encoding有三種取值分別表明contents保存數據類型。這裏有人可能會有疑問了contents的類型不就是int8_t嗎?爲何還須要encoding呢?這裏經過源碼跟蹤內部的確跟int8_t沒啥關係。並且數據的默認類型就是int16_t 。關於length這裏無需太多解釋,記住一點表示contents元素的個數並不是表示contents數組的長度!
  • 瞭解intset的同窗都知道在encoding三種取值範圍中涉及了升級的操做!在講升級以前咱們先來了解下C、C++中int的取值範圍是如何定義的
  • int8_t的取值範圍是【-128,127】 。 相似於java中byte佔1個字節也就是8位。他的取值範圍是
2 7 2 7 1 128 127 -2^{7} \sim 2^{7}-1 \\ 即 \\ -128 \sim 127

image-20210706135925132

添加元素

sadd juejin -123
sadd juejin -6
sadd juejin 12
sadd juejin 56
sadd juejin 321	
複製代碼
  • juejin這個key內部就是intset 。

image-20210706162521929

  • 上面咱們添加了5個元素且這五個元素的長度都在16以內!因此當前的intset的encoding=INTSET_ENC_INT16。-123在contents中佔前16位。

  • 因此當前五個元素佔contents的長度是16*5=80 ;

  • 注意set在存儲int類型數據時,內部是按照從小到大的順序存儲的。

類型變更

image-20210706164922957

  • 上面的問題不知道你有沒有考慮過,或者說有沒有遇到過!intset默認是int16位,正如咱們上面添加的五個元素。加入此時咱們添加第6個元素是65535(32位)。那麼此時16位的長度就不夠存儲了這個時候intset會怎麼作!
  • 另外當咱們添加第6個元素後又將65535刪除了以後,結構和添加以前是否同樣!下面咱們帶着這兩個問題來一探究竟!!!

升級

  • 首先咱們針對第一問題來看看。原來五個元素都是16位就能夠知足了,這個時候添加的65535是32位長度的。那麼是否是能夠直接追加32位分配給65535呢?
  • 答案是確定不行,首先直接追加沒法保證數組元素的大小順序!其次若是前五個分別是16位,第6個是32位那麼在intset結構中沒有多餘的字段來進行標記。也就是說在解析的時候就沒法判斷應該解析16位仍是32位了.
  • redis爲了方便解析因此在有高長度加入時會將整個contents進行升級。意思就是將整個contents先進行擴容,而後在從新填充數據

image-20210706171505334

加入65535

  • 首先根據length能夠肯定擴容後元素個數爲6 , 每一個佔位32,因此contents長度爲32*6=192 。 此時前80位內容保持不變

image-20210706171605386

舊數據移位

  • 開闢了足夠的空間後,咱們就能夠對舊數據進行移位了這裏咱們從原數組的末尾開始移動,在移動以前須要明確在新數組中的排序位置。
  • 此時咱們首先將321進行比對肯定在新數組中他的排名是第五名,那麼他將佔用新contents中128~159區間。

image-20210706172455270

  • 最終前5 個元素就會被移動好 。

image-20210706172652958

  • 最後將新加入的元素填充進去。當發生升級時確定是由於新元素的長度大於原有長度了。那麼他的值必定會是在新數組的兩端。負數在最左側,正數在最右側

image-20210706172836896

降級

  • 接下來就是第二個問題當新加入的65535又被刪除了redis該怎麼辦,這個時候元素長度實際16位就能夠知足了,可是此時encoding倒是32位的。按照個人見解應該在實現降級!
  • 可是遺憾的是redis並無,那麼請思考爲何沒有?若是讓你實現你將如何實現

爲何不實現降級

  • 當加入元素超過當前長度咱們很容易就知道此時須要進行升級操做,可是當咱們刪除一個數據時咱們如何判斷是否須要降級卻很困難,咱們須要從新遍歷一遍剩下的元素是否小於當前長度,實現複雜度O(N) 。這就是爲何不進行降級緣由之一
  • 你可能會說從新遍歷一遍很快的反正在內存中,那麼你有沒有想過若是降級以後又遇到升級狀況,這樣來回的升級降級就下降了咱們程序的性能了。咱們知道升級是必須的因此這裏降級redis採起的是忽略的策略

小結

image-20210707135328472

參考資料:內存升級優化內存降級

相關文章
相關標籤/搜索