天天看點

Redis的字元串底層到底是什麼?為了速度和安全又做了什麼??

面試場景

面試官:Redis有哪些資料類型?

我:String,List,set,zset,hash

面試官:沒了?

我:哦哦哦,還有HyperLogLog,bitMap,GeoHash,BloomFilter

面試官:就這?回家等通知吧。

Redis的字元串底層到底是什麼?為了速度和安全又做了什麼??

前言

我敢肯定,第一個回答,100%的人都能說上來,但是第二個回答能回答上來的人可能就不多了,但是這也不是我今天探讨的話題。

我就從我自己的去面試的回答思路,以及作為一個面試官他想聽到的标準答案來給大家出一期,Redis基礎類型的文章(系列文章),寫這個的時候我還是很有心得的,不知道大家有多少人跟我最開始一樣,面試官問有哪些類型,就回答出那五種就結束了,如果你是這樣的可以在評論區留言,讓我看看有多少人是這樣的。

但是,一場面試少說都是半小時起步上不封頂,你這樣一句話就回答了這麼重要的五個知識點,這個結果是你想要的麼?是面試官想要的麼?

我再問你一個問題,你可能就懵逼了:String在Redis底層是怎麼存儲的?這些資料類型在Redis中是怎麼存放的?Redis快的原因就隻有單線程和基于記憶體麼?

Redis的字元串底層到底是什麼?為了速度和安全又做了什麼??

寶貝,觸及知識盲區沒?不慌,我以前也是這樣的,我以為我背出那五種就完事了,結果被面試官安排了一波,後面我苦心修煉,總算是好了一點,現在對緩存也是非常熟悉了,你不會沒事,有我嘛,乖。

正文

Redis是C語言開發的,C語言自己就有字元類型,但是Redis卻沒直接采用C語言的字元串類型,而是自己建構了

動态字元串(SDS)

的抽象類型。

Redis的字元串底層到底是什麼?為了速度和安全又做了什麼??

就好比這樣的一個指令,其實我是在Redis建立了兩個SDS,一個是名為

aobing

的Key SDS,另一個是名為

cool

的Value SDS,就算是字元類型的List,也是由很多的SDS構成的Key和Value罷了。

SDS在Redis中除了用作字元串,還用作緩沖區(buffer),那到這裡大家都還是有點疑惑的,C語言的字元串不好麼為啥用SDS?SDS長啥樣?有什麼優點呢?

為此我去找到了Redis的源碼,可以看到SDS值的結果大概是這樣的,源碼的在GitHub上是開源的大家一搜就有了。

struct sdshdr{
 int len;
 int free;
 char buf[];
}
           
Redis的字元串底層到底是什麼?為了速度和安全又做了什麼??

回到最初的問題,為什麼Redis用了自己新開發的SDS,而不用C語言的字元串?那好我們去看看他們的差別。

SDS與C字元串的差別

  1. 計數方式不同

C語言對字元串長度的統計,就完全來自周遊,從頭周遊到末尾,直到發現空字元就停止,以此統計出字元串的長度,這樣擷取長度的時間複雜度來說是0(n),大概就像下面這樣:

Redis的字元串底層到底是什麼?為了速度和安全又做了什麼??

但是這樣的計數方式會留下隐患,是以Redis沒有采用C的字元串,我後面會提到。

而Redis我在上面已經給大家看過結構了,他自己本身就儲存了長度的資訊,是以我們擷取長度的時間複雜度為0(1),是不是發現了Redis快的一點小細節了?還沒完,不止這些。

  1. 杜絕緩沖區溢出

字元串拼接是我們經常做的操作,在C和Redis中一樣,也是很常見的操作,但是問題就來了,C是不記錄字元串長度的,一旦我們調用了拼接的函數,如果沒有提前計算好記憶體,是會産生緩存區溢出的。

比如本來字元串長這樣:

Redis的字元串底層到底是什麼?為了速度和安全又做了什麼??

你現在需要在後面拼接 ,但是你沒計算好記憶體,結果就可能這樣了:

Redis的字元串底層到底是什麼?為了速度和安全又做了什麼??

這是你要的結果麼?很顯然,不是,你的結果意外的被修改了,這要是放線上上的系統,這不是完了?那Redis是怎麼避免這樣的情況的?

我們都知道,他結構存儲了目前長度,還有free未使用的長度,那簡單呀,你現在做了拼接操作,我去判斷一些是否可以放得下,如果長度夠就直接執行,如果不夠,那我就進行擴容。

這些大家在Redis源碼裡面都是可以看到對應的API的,後面我就不一一貼源碼了,有興趣的可以自己去看一波,需要一點C語言的基礎。

Redis的字元串底層到底是什麼?為了速度和安全又做了什麼??
  1. 減少修改字元串時帶來的記憶體重配置設定次數

C語言字元串底層也是一個數組,每次建立的時候就建立一個N+1長度的字元,多的那個1,就是為了儲存空字元的,這個空字元也是個坑,但是不是這個環節探讨的内容。

Redis是個高速緩存資料庫,如果我們需要對字元串進行頻繁的拼接和截斷操作,如果我們寫代碼忘記了重新配置設定記憶體,就可能造成緩沖區溢出,以及記憶體洩露。

記憶體配置設定算法很耗時,且不說你會不會忘記重新配置設定記憶體,就算你全部記得,對于一個高速緩存資料庫來說,這樣的開銷也是我們應該要避免的。

Redis為了避免C字元串這樣的缺陷,就分别采用了兩種解決方案,去達到性能最大化,空間利用最大化:

  • 空間預配置設定:當我們對SDS進行擴充操作的時候,Redis會為SDS配置設定好記憶體,并且根據特定的公式,配置設定多餘的free空間,還有多餘的1byte空間(這1byte也是為了存空字元),這樣就可以避免我們連續執行字元串添加所帶來的記憶體配置設定消耗。

    比如現在有這樣的一個字元:

Redis的字元串底層到底是什麼?為了速度和安全又做了什麼??

我們調用了拼接函數,字元串邊長了,Redis還會根據算法計算出一個free值給他備用:

Redis的字元串底層到底是什麼?為了速度和安全又做了什麼??

我們再繼續拼接,你會發現,備用的free用上了,省去了這次的記憶體重配置設定:

Redis的字元串底層到底是什麼?為了速度和安全又做了什麼??
  • 惰性空間釋放:剛才提到了會預配置設定多餘的空間,很多小夥伴會擔心帶來記憶體的洩露或者浪費,别擔心,Redis大佬一樣幫我們想到了,當我們執行完一個字元串縮減的操作,redis并不會馬上收回我們的空間,因為可以預防你繼續添加的操作,這樣可以減少配置設定空間帶來的消耗,但是當你再次操作還是沒用到多餘空間的時候,Redis也還是會收回對于的空間,防止記憶體的浪費的。

    還是一樣的字元串:

Redis的字元串底層到底是什麼?為了速度和安全又做了什麼??

當我們調用了删減的函數,并不會馬上釋放掉free空間:

Redis的字元串底層到底是什麼?為了速度和安全又做了什麼??

如果我們需要繼續添加這個空間就能用上了,減少了記憶體的重配置設定,如果空間不需要了,調用函數删掉就好了:

Redis的字元串底層到底是什麼?為了速度和安全又做了什麼??
  1. 二進制安全

仔細看的仔肯定看到上面我不止一次提到了空字元也就是’\0‘,C語言是判斷空字元去判斷一個字元的長度的,但是有很多資料結構經常會穿插空字元在中間,比如圖檔,音頻,視訊,壓縮檔案的二進制資料,就比如下面這個單詞,他隻能識别前面的 不能識别後面的字元,那對于我們開發者而言,這樣的結果顯然不是我們想要的對不對。

Redis的字元串底層到底是什麼?為了速度和安全又做了什麼??

Redis就不存在這個問題了,他不是儲存了字元串的長度嘛,他不判斷空字元,他就判斷長度對不對就好了,是以redis也經常被我們拿來儲存各種二進制資料,我反正是用的很high,經常用來儲存小檔案的二進制。

Redis的字元串底層到底是什麼?為了速度和安全又做了什麼??

資料參考:Redis設計與實作

總結

大家是不是發現,一個小小的SDS居然有這麼多道理在這?

以前就知道Redis快,最多說個Redis是單線程的,說個多路IO複用,說個基于記憶體的操作就完了,現在是不是還可以展開說說了?

本文是系列文的第一章,後續會陸續更新的,不知道這樣的類型大家是否喜歡,可以留言給我回報。

大家一同去面試,一樣的問題,就是有人能過,有人不能過,大家經常歸咎于自己學曆,自己過往經曆的原因,但是你可以問一下自己,底層的細節位元組是否有深究呢?細節往往才是最重要的,也是最少人知道的,如何和别的仔拉開差距拿到offer,我想就是這樣些細節決定的吧,背誰不會呢?

講到這裡,給大家推薦小編通過一些大廠的朋友要到了他們内部的Java面試題,資料難得,而且還是近一年的真實面試題,

關注我私信回複【資料】可以領取到一些個人收集的面試及電子書資料,或許對你有幫助!

Redis的字元串底層到底是什麼?為了速度和安全又做了什麼??