Redis 資料結構 之 SDS

black_monkey發表於2020-06-18

SDS(simple dynamic string),簡單動態字串。s同時它被稱為 Hacking String。hack 的地方就在 sds 儲存了字串的長度以及剩餘空間。sds 的實現在 sds.c 中。

C語言字串使用長度為n+1的字元陣列來表示長度為n的字串,並且字元陣列的最後一個元素總是空字元'\0',這樣的方式儲存,時存在安全隱患的,並且它不能滿足效率方面的需求。

因此Redis沒有使用C原生的string而是自己構建了SDS。在Redis裡,C語言字串只用於一些無須對字串值進行修改的地方,比如:日誌。

在Redis中,包含字串值的鍵值對都是使用SDS實現的,除此之外,SDS還被用於AOF緩衝區、客戶端狀態的輸入緩衝區。

SDS定義

struct sdshdr{
     //位元組陣列
     char buf[]; 
     //buf陣列中已使用位元組數量
     int len;
     //buf陣列中未使用位元組數量
     int free;
}

如上圖所示,len表示該SDS儲存了一個6位元組長度(不包含結束符)的字串,free表示該SDS還有6個位元組的未使用空間,buf是一個char型別的陣列 ,儲存了該SDS所儲存的字串值。

高效

相比C語言字串,使獲取字串長度時間複雜度降為O(1)而C原生的獲取長度為O(N) 遍歷整個陣列。

安全

同時SDS杜絕緩衝區溢位,不會像C那樣造成陣列資料不安全,絕對不會越界。

當需要對SDS進行修改時,API會先檢查SDS當前剩餘空間是否滿足修改之後所需的空間,如果不滿足的話API會自動將SDS的空間擴充套件至足夠用的空間然後才進行下一步操作,所以SDS不會出現緩衝區溢位問題。

減少記憶體分配

C語言字原生符串底層是使用一個n+1個字元長度的char型別資料實現的,所以每次增長或縮短一個原生字串,程式都要對這個字串陣列進行一次記憶體重分配操作:

同時因為記憶體重分配涉及複雜的演算法,並且可能需要執行系統呼叫,所以它通常是一個比較耗時的操作。Redis經常被用於速度要求嚴苛、資料被頻繁修改的場合,如果每次修改字串都需要執行一次記憶體重分配的話,那麼對於效能會造成很大影響。

SDS 在分配了記憶體之後(往往空間會存在盈餘,也就是空間的預分配),然後自己通過len 和 free 來維護已使用的和未使用的記憶體,不再依賴系統來重新劃分,這樣能有效的提升效能。

空間預分配

用於字串增長操作,當字串增長時,程式會先檢查需不需要對SDS空間進行擴充套件,如果需要擴充套件,程式不僅會為SDS分配修改所必要的空間,還會為SDS分配額外的未使用空間,額外分配的未使用空間公式如下:

SDS空間 < 1MB

如果對SDS修改之後,SDS的長度(修改之後len屬性的值)小於1MB,那麼則分配和len屬性同樣大小的未使用空間,這時SDS的len屬性和free屬性的值相同。如:如果修改之後SDS的len將變為10位元組,那麼程式也會分配10位元組的未使用空間,SDS的buf陣列實際長度變為10 + 10 + 1 = 21(額外一個位元組用於儲存結束符\n)

SDS空間 > 1MB

如果對SDS修改之後,SDS的長度大於等於1MB,那麼程式會分配1MB的未使用空間。如:修改之後的len將變為10MB,那麼程式會分配1MB的未使用空間,SDS的bug陣列長度為10MB + 1MB + 1byte

SDS空間 > 512MB

Game over~ 報錯!

惰性空間釋放

用於優化SDS的字串收縮操作,當字串收縮時,程式不會立即執行記憶體重分配來回收收縮後記憶體多出來的空間,而是使用free屬性記錄下來,以備將來使用。

通過空間預分配,Redis可以減少連續執行字串增長操作所需的記憶體重分配次數,通過惰性空間釋放,SDS避免了縮短字串時所需的記憶體重分配操作,併為將來由可能的增長操作提供了優化。

相關文章