1. 程式人生 > 實用技巧 >redis 內部是怎麼實現它的字串的

redis 內部是怎麼實現它的字串的

Redis字串的實現

Redis雖然是用C語言寫的,但卻沒有直接用C語言的字串,而是自己實現了一套字串。目的就是為了提升速度,提升效能,可以看出Redis為了高效能也是煞費苦心。

Redis構建了一個叫做簡單動態字串(Simple Dynamic String),簡稱SDS

1.SDS 程式碼結構

struct sdshdr{
    //  記錄已使用長度
    int len;
    // 記錄空閒未使用的長度
    int free;
    // 字元陣列
    char[] buf;
};

SDS ?什麼鬼?可能對此陌生的朋友對這個名稱有疑惑。只是個名次2而已不必在意,我們要重點欣賞借鑑Redis的設計思路。更多面試題,歡迎關注公眾號 Java面試題精選

下面畫個圖來說明,一目瞭然。

Redis的字串也會遵守C語言的字串的實現規則,即最後一個字元為空字元。然而這個空字元不會被計算在len裡頭。

2.SDS 動態擴充套件特點

SDS的最厲害最奇妙之處在於它的Dynamic。動態變化長度。舉個例子

如上圖所示剛開始s1 只有5個空閒位子,後面需要追加' world' 6個字元,很明顯是不夠的。

那咋辦?Redis會做一下三個操作:

  1. 計算出大小是否足夠
  2. 開闢空間至滿足所需大小
  3. 開闢與已使用大小len相同長度的空閒free空間(如果len < 1M)開闢1M長度的空閒free空間(如果len >= 1M)

看到這兒為止有沒有朋友覺得這個實現跟Java的列表List實現有點類似呢?看完後面的會覺得更像了。往期:

100期面試題彙總

Redis字串的效能優勢

  • 快速獲取字串長度
  • 避免緩衝區溢位
  • 降低空間分配次數提升記憶體使用效率

1.快速獲取字串長度

在看下上面的SDS結構體:

struct sdshdr{
    //  記錄已使用長度
    int len;
    // 記錄空閒未使用的長度
    int free;
    // 字元陣列
    char[] buf;
};

  

由於在SDS裡存了已使用字元長度len,所以當想獲取字串長度時直接返回len即可,時間複雜度為O(1)。

如果使用C語言的字串的話它的字串長度獲取函式時間複雜度為O(n),n為字元個數,因為他是從頭到尾(到空字元'\0')遍歷相加。往期:

100期面試題彙總

2.避免緩衝區溢位

對一個C語言字串進行strcat追加字串的時候需要提前開闢需要的空間,如果不開闢空間的話可能會造成緩衝區溢位,而影響程式其他程式碼。

如下圖,有一個字串s1="hello" 和 字串s2="baby",現在要執行strcat(s1,"world"),並且執行前未給s1開闢空間,所以造成了緩衝區溢位。

而對於Redis而言由於每次追加字串時都會檢查空間是否夠用,所以不會存在緩衝區溢位問題。每次追加操作前都會做如下操作:

  1. 計算出大小是否足夠
  2. 開闢空間至滿足所需大小
  3. 降低空間分配次數提升記憶體使用效率

字串的追加操作會涉及到記憶體分配問題,然而記憶體分配問題會牽扯記憶體劃分演算法以及系統呼叫所以如果頻繁發生的話影響效能,所以對於效能至上的Redis來說這是萬萬不能忍受的。所以採取了一下兩種優化措施

  • 空間與分配
  • 惰性空間回收

1. 空間預分配

對於追加操作來說,Redis不僅會開闢空間至夠用而且還會預分配未使用的空間(free)來用於下一次操作。至於未使用的空間(free)的大小則由修改後的字串長度決定。

  • 當修改後的字串長度len < 1M,則會分配與len相同長度的未使用的空間(free)
  • 當修改後的字串長度len >= 1M,則會分配1M長度的未使用的空間(free)

有了這個預分配策略之後會減少記憶體分配次數,因為分配之前會檢查已有的free空間是否夠,如果夠則不開闢了~

2. 惰性空間回收

與上面情況相反,惰性空間回收適用於字串縮減操作。比如有個字串s1="hello world",對s1進行sdstrim(s1," world")操作,執行完該操作之後Redis不會立即回收減少的部分,而是會分配給下一個需要記憶體的程式。

當然,Redis也提供了回收記憶體的api,可以自己手動呼叫來回收縮減部分的記憶體。

到此為止結束了~

下次在遇到這個問題可以侃侃而談了,哈哈哈