Redis字符串鍵的底層原理

before

  • C語言基礎
  • Redis基礎

導入

redis的命令以下:redis

            set x "hello";
            get x;
            hello    

  Redis做爲一種存儲字符串的緩存結構,其具體實現是由C語言完成,在C語言中,字符串是經過字符數組實現的,即char[],那麼Redis對於字符串的實現是否是也是基於字符數組嗎?不是的,Redis對字符串的處理是經過SDS(Simple Dynamic String)實現的。算法

SDS介紹

  SDS(Simple Dynamic String)簡單動態字符串,它是由C語言完成,以下是其具體實現segmentfault

Redis做爲一種存儲字符串的緩存結構,其具體實現是由C語言完成,在C語言中,字符串是經過字符數組實現的,即char[],那麼Redis對於字符串的實現是否是也是基於字符數組嗎?不是的,Redis對字符串的處理是經過SDS(Simple Dynamic String)實現的。數組

struct sdshdr{
    //記錄buf數組已使用字節的數量
    //等於SDS所保存字符串的長度
    int length; 
    //記錄buf數組未使用字節的數量
    int free;
    //buf數組
    char[] buf;
};
 
看看redis的示例:
    sdshdr
    free 0
    length 5
    buf     -->|'R'|'e'|'d'|'i'|'s'|'\0'|
解釋:
        - free爲0,表示這個SDS沒有分配任何未使用的空間
        - length爲5,表示這個SDS保存了一個長度爲5的字符串    
        - buf數組中保存着「Redis」字符串

  SDS遵循C字符串以空字符串結尾的慣例,保存空字符串的1字節空間不計算在SDS的len屬性之中。緩存

  再看看SDS的free不爲0的狀況:安全

   sdshdr
    free 3
    length 5
    buf      -->|'R'|'e'|'d'|'i'|'s'| | | |

  free的值爲3,表示這個SDS分配了三個空閒的空間函數

SDS與字符串的區別

  C語言使用簡單的字符串表示方式,並不能知足Redis對字符串在安全性,效率,以及功能方面的要求,SDS更使用Redis。性能

@1 常數複雜度獲取字符串長度

C字符串:
  由於C語言並不記錄自身的長度信息,因此獲取一個C字符串的長度,程序必須遍歷整個字符串,對遇到的,每一個字符進行計數,直到遇到表明字符串結尾的空字符串爲止,這個操做的複雜度爲O(n)。優化

SDS:
  與C語言不一樣的是,SDS結構中的屬性length記錄了SDS自己的長度,因此獲取一個SDS長度的複雜度爲O(1)。有人疑問那麼SDS的length值是哪來的?這裏的length值是SDS API在設置和更新SDS時自動完成的。編碼

總結1:經過使用SDS而不是C字符串,Redis獲取字符串長度的複雜度由O(N)降爲O(1),這確保了字符串長度的獲取的工做不會成爲Redis的性能瓶頸。

@ 2杜絕緩衝區溢出

C字符串:
  因爲C自身不記錄字符串的長度帶來一個問題是容易形成緩衝區溢出(buffer overflow)。在<string.h>/strcat函數中,能夠將一個字符串拼接到另一個字符串的末尾。

`char *strcat(char *dest,const char *src)`

  理想狀態下,用戶在使用這個函數時,假定C爲dest分配了足夠多的內存,能夠容納src字符串中的全部內容,而一旦這個假定不成立,就會產生緩衝區溢出。舉個例子,假定內存中有相鄰的兩個字符串s1,s2,如圖:

    s1                        s2
     |                         |
...|'R'|'e'|'d'|'i'|'s'|'\0'||'g'|'o'|'o'|'d'|'\0'|...

  若是執行strcat(s1," cluster");將Redis改成」Redis cluster「,可是粗心的卻忘了在執行這句以前爲s1分配足夠的空間,那麼在執行以後,s1的數據將會溢出到s2所在的空間,致使s2保存的內容意外的被修改。

SDS:

  與C語言不一樣的是,SDS空間分配政策徹底杜絕了發生緩衝區溢出的可能性:當SDS API須要對字符串進行修改時,首先會檢查SDS的空間是否知足修改所需的要求,由於SDS自身有對字符串長度記錄的屬性length和空閒空間屬性free,能夠藉助這兩個參數進行檢查。SDS會在執行動做以前判斷SDS的空間大小,再去執行操做,若是空間不夠的話,SDS API會自動擴展空間。

  

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

C字符串:

  由於C字符串不記錄自身長度,每次增加或者縮短字符串長度時,程序都要對這個C字符串數組進行一次內存從新分配操做,否則容易形成內存益出。由於內存,分配設計複雜的算法,而且可能須要執行系統調用,因此它一般是一個比較耗時和耗能的操做。可是Redis做爲緩存,追求速度,因此不能常常發生內存分配操做。

SDS:

  SDS數組中的未使用空間字節數量由SDS的屬性free記錄,經過free記錄,SDS實現了空間預分配和惰性釋放兩種優化策略。
1. 空間預分配
  空間預分配用於優化SDS的字符串增加操做:當SDS的API對一個SDS進行修改,而且須要對SDS的空間進行擴展時,程序不只會爲SDS分配修改所須要的空間,並且還會爲SDS分配額外的空間。額外的空間分配規則以下:

(1)若是修改SDS以後,SDS的長度小於1MB,那麼程序會給SDS分配和length同樣大的額外空間,這是SDSlength和free的值相等。舉個例子,若是修改後的字符串長度爲13k,那麼SDS的空間將會佔據13+13+1=27k(額外的一個字節用於保存空字符串)。

(2)若是修改SDS以後,SDS的長度大於1MB,那麼程序會給SDS分配額外的1MB空間,舉個例子,好比修改後的SDS有30MB的大小,那麼程序會分配1MB的未使用空間,SDS的buf數組實際大小將是30MB+1MB+1byte。

2.惰性釋放

  惰性釋放用於優化SDS的字符串縮短操做:當SDS的API要縮短SDS保存的字符串時,程序並不須要當即使用內存重分配策略來回收縮短後多出來的字節,而是使用free屬性將這些字節記錄起來,並等待使用。

@4 二進制安全

  C字符串中的字符必須符合某種編碼(好比ASCII),而且除了字符串末尾以外,字符串裏面不能包含空字符串,不然最早被程序讀入的空字符串將被誤認爲是字符串結尾。

SDS API都是二進制安全的,全部SDS API都會以處理二進制的方式來處理存放在SDS buf中的數據,數據寫什麼樣,它被讀取時就是什麼樣子。

@5 兼容部分C字符串函數

  SDS的API總會以SDS保存的數據的末尾設置爲空字符串,而且在分配SDS空間時會多分配一個字節的空間來容納空字符串,這是爲了那些保存的數據能夠重用一部分<string.h>庫中的函數。

總結

字符串和SDS之間的區別總結以下:

 

 

 

原文連接:Redis字符串鍵的底層原理

相關文章
相關標籤/搜索