Redis SDS 深入一點,看到更多!

1、什麼是SDS?

Redis 自定的字元串存儲結構,關於redis,你需要了解的幾點!中我們對此有過簡要說明。

Redis 底層是用C語言編寫的,可是在字元存儲上,並未使用C原生的String類型,而是定義了自己的字元串結構 Simple Dynamic Stirng,簡稱SDS。

SDS基本結構如下:

struct sdshdr {
int len; // 記錄buf數組中已使用位元組的數量,等於SDS所保存字元串的長度
int free; // 記錄buf數組中未使用位元組的數量
char buf[];// 位元組數組,用於保存字元串
};

例如,字元串「Redis」存儲示意圖為:

圖1

當前存儲字元串長度為5,未使用長度為0,位元組數組存儲的字元為「Redis\0」。

這裡需要注意的是:內部數據數組存儲字元串形式符合C語言要求,以『\0』結尾。且len字元串長度不包含結尾標識符『\0』。

buf[]的這種遵循C語言形式的存儲,使得Redis可以直接使用C語言的相關字元串函數進行SDS對象的操作。

二、SDS的優勢

1、O(1)時間複雜度獲取字元串長度

SDS內部維護著一個字元串長度的len變數,可以直接讀取,時間複雜度為O(1)。

對於傳統的C字元串:字元+「\0」,想要獲取字元長度,則需要遍歷整個字元串,直到遇到結束字元,時間複雜度為O(n)。

2、緩衝區溢出規避

所謂緩衝區溢出即所需要的記憶體超出了實際的記憶體。因此對於C字元串來說,要特別注意記憶體分配,回收使用問題。

比如,向一個現有字元串內添加特定字元時,需要保證當前已經分配了這足夠的記憶體。 

圖2

與C不同的是,SDS的空間預分配策略可以避免緩衝區溢出發生,

當需要對SDS進行操作時,首先會檢查當前空間是否滿足需求,不足則擴展當前分配空間。記憶體檢查相對於C變成了內部預置操作。

3、減少記憶體重分配次數

上面我們講到過,C字元操作前都需要進行記憶體的分配操作,同時,操作完成後,也需要進行相應的記憶體回收操作。一次操作至少涉及一次記憶體分配操作。

大家都知道記憶體的重分配是一個比較複雜且需精細控制的過程,耗時耗資源。針對此弊端,Redis 在SDS記憶體配置策略上採用了空間預分配+惰性刪除相結合的策略。

a)空間預分配:

空間預分配用於優化SDS字元擴展操作。

所謂預分配,也即是說在一次擴展操作中,擴展的空間大小會大於實際需要的空間大小。
如下,圖1執行圖2操作後SDS變更為:

圖3 

預分配空間的大小基於以下規則計算:

SDS len<1M:分配len長度空間作為預分配空間;

SDS len>=1M:分配1M空間作為預分配空間;

這樣,在下次進行字元操作的時候,如果所需要的空間小於當前SDS free空間,則可以直接行操作,而不需要再執行記憶體擴展,重分配操作。

SDS的預分配機制使得一次擴展操作所需的記憶體重分配次數變為<=1。

b)惰性刪除機制

所謂惰性刪除,即調整刪除SDS中部分數據時,不會立刻執行記憶體重分配,而是會保留空出來記憶體,並更新內部free屬性。以備將來有字元擴展需求,可以直接使用。

當然,Redis也提供了主動釋放未使用記憶體的方法。

如下,刪除「ent」之後的SDS結構:

 圖4

SDS的記憶體分配機制,尤其對於以寫為主的應用場景,能夠提供更加優異的性能表現。

3、二進位安全

C字元串由於特殊的編碼要求只能保存文本數據。

SDS相關的功能方法會以二進位的形式來操作SDS存儲的數據,沒有任何中間操作,存儲最原始的數據,因此不會有字元層面的因素影響。

SDS可以保存任何源的二進位數據,字元、圖片、文件或者序列化的對象等等。