1. 程式人生 > 資料庫 >Redis底層資料結構——SDS

Redis底層資料結構——SDS

SDS 是什麼

Redis 底層的程式語言是由 C 語言編寫的,C 語言預設字串則是以空字元結尾的字元陣列(簡稱 C 字串)。但 Redis 預設的字串並非 C 字串,而是名為 SDS ( Simple Dynamic String )簡單動態字串的抽象結構。

Redis 採用一段連續的記憶體空間來儲存 SDS 結構,具體結構如下圖所示,free 代表指這個字串剩餘可用空間的長度,而 len 代表這個字串已佔用空間的長度,buf [] 就是字元陣列。

下面是一個字串為 ‘Redis’ 在 SDS 結構中的儲存例子:

在這裡插入圖片描述

圖中 SDS 保留了 C 字串以空字元結尾的傳統,但這個空字元的長度1位元組空間並不會計算在 SDS 的 len 屬性裡面,而是為這個空字元分配額外的1位元組空間,每次由 SDS 相關函式預設新增到字串末尾,Redis 這樣設計的好處是 SDS 可以直接重用一部分 C 字串相關函式。

這個 SDS 結構為字串 ‘Redis’ 分配了5位元組的已使用長度,也為其分配了5位元組的可用空間長度。

SDS 與 C 字串的區別

傳統的 C 字串長度為 n+1 且字元陣列的最後一個元素總是空字串 ‘\0’ 。

1.查詢字串長度的時間複雜度

假如查詢字串長度的時候,Redis 使用 C 字串的話,因為 C 字串不記錄本身的長度,這樣時間複雜度將為 O(N) ,而使用 SDS 查詢字串的長度的時候,直接獲取 len 屬性的值,這樣的時間複雜度為 O(1) 。SDS 結構的設計極大的提升了 Redis 查詢字串長度時候的效能。

2.緩衝區溢位

使用 C 字串儲存一個字串的時候,假如第一次為其分配的空間剛好被字串佔滿空間了,第二次修改的時候,新的字串長度已經超過其第一次分配的空間,然後執行修改的時候,此時超出的空間原本空間的內容將溢位到緊挨著的內容空間,導致下一個字串的內容被修改。這樣的緩衝區溢位是不友好的。

SDS 是不會發生緩衝區溢位的,它的空間分配策略拒絕了溢位。假如字串發生了修改,SDS 相關函式會先檢查本身的空間是否滿足新的修改空間,如果不滿足,函式將會自動擴容,再進行修改操作,所以 SDS 相對 C 字串既不用手動修改空間滿足正常儲存,也不會發生緩衝區溢位。

3. 修改字串時候的記憶體分配次數

因為 C 字串的最後一個字元總是空字串:’/0’ ,以及儲存的時候長度總是 N+1, 這種關係下,字串每次修改操作的時候,程式總要為其進行一次記憶體重新分配操作,修改的字串長度變長的話,要為其重新分配合理的記憶體空間,不然可能發生緩衝區溢位。而變短的話,不分配回收無用的記憶體空間,可能將發生記憶體洩露。

基於這樣的情況,Redis 這種是要求高效能的情況下,C 字串的一次記憶體分配是沒社麼問題的,一但在頻繁修改的情況下加上每次記憶體分配的耗時間,Redis 的效能是將極大降低的。

SDS 結構避免了以上這種缺點,採用了空間預分配以及惰性空間釋放的方式。

空間預分配

當 SDS 結構的字串變長的時候,SDS 會為其分配修改需要的空間以及未使用的空間。假如分配過同樣的未使用的空間,下次字串變長,需要增長的長度小於未使用空間,將直接使用未使用的空間,不需要執行記憶體分配。如果大於,則將會執行記憶體分配。

通過這樣的策略,是能夠減少字串連續增長操作帶來的記憶體空間分配次數的。

額外的空間分配規則是: 當長度( len 屬性)小於 1MB 的時候,將分配同 len 屬性一樣大小的未使用空間。而當 len 屬性大於 1MB 的時候,那麼將分配 1MB 的未使用空間。

惰性空間釋放

當 SDS 結構的字串變短的時候,程式上不會立即記憶體分配,收回多餘的空間,而是使用 free 屬性把縮短的空間儲存下來,以備後續使用。假如下次增長的字串長度,free 屬性值讓其有足夠的空間儲存,那麼下次不會發生記憶體分配,假如字串繼續變短的話,將繼續保留多出的空間。SDS 相關函式可以在我們需要的時候,真正地釋放SDS 的使用空間,不會造成記憶體浪費。

4. 二進位制安全

由於 C 字串要求儲存的字元必須符合某種編碼,並且除了字串的最後一個位元組為空字元之外,其他不能包含空字元。不然程式將認為第一個讀到的空字元為字串的末尾,之後的資料不會被識別。這樣的侷限性使得 C 字串只能儲存文字資料,而對平時常見的圖片、音訊等二進位制資料不能儲存。
而我們平時在使用 Redis 的時候,也需要儲存這樣的二進位制資料,所以 Redis 使用 SDS 結構的好處就是由於 SDS 的相關函式都是二進位制安全的,對於任何格式的資料都是以二進位制儲存到 buf 數組裡面的,不會像 C 字串那樣用特殊的格式儲存資料,讓Redis 正常儲存二進位制資料,以及正常讀取資料。

5. C 字串函式相容性

SDS 保留了 C 字串以空字元結尾的傳統,每次對 buf 陣列分配空間的時候,會為其多分配一個位元組的空間來儲存這個末尾的空字元。為了讓其能夠相容部分的 C 字串函式。而 C 字串是可以使用其全部的函式的。

通過相容部分的函式,直接複用一些功能,而不用去重新編寫函式實現某些特定的功能。

SDS 應用場景

Redis 中會使用 C 字串應用在不需要對字串值修改的地址,比如列印日誌。SDS 是被 Redis 應用在需要修改字串值的地方,比如 Redis 的資料庫裡面,包含字串值的鍵值對在底層都是 SDS 實現的。

除了這些,SDS 還被應用於 AOF 模組中的 AOF 緩衝區以及客戶端狀態中的輸入緩衝區。

參考:《 Redis設計與實現 》

更多Java後端開發相關技術,可以關注公眾號「 紅橙呀 」。