帶你看Redis數據結構底層系列-SDS

面試場景

面試官:Redis有哪些數據類型?面試

我:String,List,set,zset,hashredis

面試官:沒了?算法

我:哦哦哦,還有HyperLogLog,bitMap,GeoHash,BloomFilter數據庫

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


前言

我敢確定,第一個回答,100%的人都能說上來,可是第二個回答能回答上來的人可能就很少了,可是這也不是我今天探討的話題。緩存

我就從我本身的去面試的回答思路,以及做爲一個面試官他想聽到的標準答案來給你們出一期,Redis基礎類型的文章(系列文章),寫這個的時候我仍是頗有心得的,不知道你們有多少人跟我最開始同樣,面試官問有哪些類型,就回答出那五種就結束了,若是你是這樣的能夠在評論區留言,讓我看看有多少人是這樣的。安全

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

我再問你一個問題,你可能就懵逼了:String在Redis底層是怎麼存儲的?這些數據類型在Redis中是怎麼存放的?Redis快的緣由就只有單線程和基於內存麼?數據結構

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

正文

Redis是C語言開發的,C語言本身就有字符類型,可是Redis卻沒直接採用C語言的字符串類型,而是本身構建了動態字符串(SDS)的抽象類型。

就比如這樣的一個命令,其實我是在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用了本身新開發的SDS,而不用C語言的字符串?那好咱們去看看他們的區別。

SDS與C字符串的區別

  1. 計數方式不一樣

C語言對字符串長度的統計,就徹底來自遍歷,從頭遍歷到末尾,直到發現空字符就中止,以此統計出字符串的長度,這樣獲取長度的時間複雜度來講是0(n),大概就像下面這樣:

可是這樣的計數方式會留下隱患,因此Redis沒有采用C的字符串,我後面會提到。

而Redis我在上面已經給你們看過結構了,他本身自己就保存了長度的信息,因此咱們獲取長度的時間複雜度爲0(1),是否是發現了Redis快的一點小細節了?還沒完,不止這些。

  1. 杜絕緩衝區溢出

字符串拼接是咱們常常作的操做,在C和Redis中同樣,也是很常見的操做,可是問題就來了,C是不記錄字符串長度的,一旦咱們調用了拼接的函數,若是沒有提早計算好內存,是會產生緩存區溢出的。

好比原本字符串長這樣:

你如今須要在後面拼接 ,可是你沒計算好內存,結果就可能這樣了:

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

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

這些你們在Redis源碼裏面都是能夠看到對應的API的,後面我就不一一貼源碼了,有興趣的能夠本身去看一波,須要一點C語言的基礎。

  1. 減小修改字符串時帶來的內存重分配次數

C語言字符串底層也是一個數組,每次建立的時候就建立一個N+1長度的字符,多的那個1,就是爲了保存空字符的,這個空字符也是個坑,可是不是這個環節探討的內容。

Redis是個高速緩存數據庫,若是咱們須要對字符串進行頻繁的拼接和截斷操做,若是咱們寫代碼忘記了從新分配內存,就可能形成緩衝區溢出,以及內存泄露。

內存分配算法很耗時,且不說你會不會忘記從新分配內存,就算你所有記得,對於一個高速緩存數據庫來講,這樣的開銷也是咱們應該要避免的。

Redis爲了不C字符串這樣的缺陷,就分別採用了兩種解決方案,去達到性能最大化,空間利用最大化:

  • 空間預分配:當咱們對SDS進行擴展操做的時候,Redis會爲SDS分配好內存,而且根據特定的公式,分配多餘的free空間,還有多餘的1byte空間(這1byte也是爲了存空字符),這樣就能夠避免咱們連續執行字符串添加所帶來的內存分配消耗。

    好比如今有這樣的一個字符:

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

咱們再繼續拼接,你會發現,備用的free用上了,省去了此次的內存重分配:

  • 惰性空間釋放:剛纔提到了會預分配多餘的空間,不少小夥伴會擔憂帶來內存的泄露或者浪費,別擔憂,Redis大佬同樣幫咱們想到了,當咱們執行完一個字符串縮減的操做,redis並不會立刻收回咱們的空間,由於能夠預防你繼續添加的操做,這樣能夠減小分配空間帶來的消耗,可是當你再次操做仍是沒用到多餘空間的時候,Redis也仍是會收回對於的空間,防止內存的浪費的。

    仍是同樣的字符串:

當咱們調用了刪減的函數,並不會立刻釋放掉free空間:

若是咱們須要繼續添加這個空間就能用上了,減小了內存的重分配,若是空間不須要了,調用函數刪掉就行了:

  1. 二進制安全

仔細看的仔確定看到上面我不止一次提到了空字符也就是’\0‘,C語言是判斷空字符去判斷一個字符的長度的,可是有不少數據結構常常會穿插空字符在中間,好比圖片,音頻,視頻,壓縮文件的二進制數據,就好比下面這個單詞,他只能識別前面的 不能識別後面的字符,那對於咱們開發者而言,這樣的結果顯然不是咱們想要的對不對。

Redis就不存在這個問題了,他不是保存了字符串的長度嘛,他不判斷空字符,他就判斷長度對不對就行了,因此redis也常常被咱們拿來保存各類二進制數據,我反正是用的很high,常常用來保存小文件的二進制。

資料參考:Redis設計與實現

總結

你們是否是發現,一個小小的SDS竟然有這麼多道理在這?

之前就知道Redis快,最多說個Redis是單線程的,說個多路IO複用,說個基於內存的操做就完了,如今是否是還能夠展開說說了?

本文是系列文的第一章,後續會陸續更新的,不知道這樣的類型你們是否喜歡,能夠留言給我反饋。

你們一同去面試,同樣的問題,就是有人能過,有人不能過,你們常常歸咎於本身學歷,本身過往經歷的緣由,可是你能夠問一下本身,底層的細節字節是否有深究呢?細節每每纔是最重要的,也是最少人知道的,如何和別的仔拉開差距拿到offer,我想就是這樣些細節決定的吧,背誰不會呢?

相關文章
相關標籤/搜索