Redis原始碼分析二 Redis簡單動態字串

2021-07-09 13:40:24 字數 3231 閱讀 8533

redis簡單動態字串

redis沒有直接使用c語言傳統的字串表示(以空字元結尾的字元陣列,以下簡稱c字串),而是自己構建了一種名為簡單動態字串(****** dynamic string, sds)的抽象型別,並將sds用作redis的預設字串表示。

在redis裡面,c字串只會作為字串字面量(string literal)用在一些無須對字串值進行修改的地方,比如列印日誌等:

redislog(redis_waring, "redis is now ready to exit, bye bye...");

當reids需要的不僅僅是乙個字串字面量,而是乙個可以被修改的字串值時,redis就會用sds來表示字串值,比如在redis的資料庫裡面,包含字串值的鍵值對在底層都是由sds實現的。

舉個例子:redis> set msg "hello world" ok

那麼redis將子啊資料庫中建立乙個新的鍵值對,其中:

又比如,如果客戶端執行命令:

(integer)3

那麼redis將在資料庫中建立乙個新的鍵值對,其中:

除了用來儲存資料庫中的字串值之外,sds還用作緩衝區(buffer):aof模組中的aof緩衝區,以及客戶端狀態中的輸入緩衝區,都是由sds實現的,在之後介紹aof持久化和客戶端狀態的時候,我們會看到sds在這兩個模組中的應用。

每個sds.h/sdshdr結構表示乙個sds值:

sds遵循c字串以空字元結尾的慣例,儲存空字元的1位元組空間不計算在sds的len屬性裡面,並且為空字元分配

額外的1位元組空間,以及新增空字元到字串末尾等操作,都是由sds函式自動完成的,所以這個空字元對於sds

的使用者來說是完全透明的。遵循空字元結尾這一慣例的好處是,sds可以直接重用一部分c字串函式庫裡面的

函式。

根據傳統,c語言使用長度為n+1的字元陣列來表示長度為n的字串,並且字元陣列的最後乙個元素總是空字元

『\0』

c語言使用的這種簡單的字串表示方式,並不能滿足redis對字串的安全性、效率以及功能方面的要求,本節

接下來的內容將詳細對比c字串和sds之間的區別,並說明sds比c字串更使用與redis的原因。

因為c字串並不記錄自身的長度資訊,所以為了獲取乙個c字串的長度,程式必須遍歷整個字串,

對遇到的每個字元進行計數,直到遇到代表字串結尾的空字元為止,這個操作的複雜度為o(n)。 和

c字串不同,因為sds在len屬性中記錄了sds本身的長度,所以獲取乙個sds長度的複雜度僅為o(1)。

通過使用sds而不是c字串,redis將獲取字串長度所需的複雜度從o(n)降低到了o(1),這確保了獲

取字串長度的工作不會成為redis的效能瓶頸。例如,因為字串鍵在底層使用sds來實現,所以即使我 們

對乙個非常長的字串鍵反覆執行strlen命令,也不會對系統效能造成任何影響,因為strlen命令的複雜 度

僅為o(1)。

除了獲取字串長度的複雜度高之外,c字串不記錄自身長度帶來的另乙個問題是容易造成緩衝區溢位

(buffer over-flow)。舉個例子,/strcat函式可以將src字串中的內容拼接到dest字串的末尾:

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

因為c字串不記錄自身的長度,所以strcat假定使用者在執行這個函式時,已經為dest分配了足夠多的記憶體,可以

容納src字串中的所有內容,而一旦這個假定不成立時,就會產生緩衝區溢位。

與c字串不同,sds的空間分配策略完全杜絕了發生緩衝區溢位的可能性:當sds api需要對sds進行修改時,

api會先檢查sds的空間是否滿足修改所需的要求,如果不滿足的話,api會自動將sds的空間擴充套件至執行修改

所需的大小,然後才執行實際的修改操作,所以使用sds既不需要手動修改sds的空間大小,也不會出現前面所

說的緩衝區溢位問題。

正如前兩個小節所說,因為c字串並不記錄自身的長度,所以對於乙個包含了n個字元的c字串來說,這個c

字串的底層實現總是乙個n+1個字元長的陣列(額外的乙個字元空間用於儲存空字元)。因為c字串 的

長度和底層陣列的長度之間存在著這種關聯性,所以每次增長或者縮短乙個c字串,程式都總要對儲存這 個

c字串的陣列進行一次記憶體重分配操作:

在一般程式中,如果修改字串長度的情況不太常出現,那麼每次修改都執行一次記憶體重分配是可以接受的。

但是redis作為資料庫,經常被用於速度要求嚴苛、資料被頻繁修改的場合,如果每次修改字串的長度都 需要

執一次記憶體重分配的話,

那麼光是執行記憶體重分配的時間就會占去修改字串所用時間的一大部分,如果

這種修改頻繁地發生的話,可能

還會對效能造成影響。

通過未使用空間,sds實現了空間預分配和惰性空間釋放

兩種優化策略。

空間預分配用於優化sds的字串增長操作:當sds的api對乙個sds進行修改,並且需要對sds進行空間擴充套件

的時候,程式不僅會為sds分配修改所必須要的空間,還會為sds分配額外的未使用空間。

其中,額外分配的未使用空間數量由以下公式決定:

通過這種預分配策略,sds將連續增長n次字串所需的記憶體重分配次數從必定n次降低為最多n次。

惰性空間釋放用於優化sds的字串縮短操作:當sds的api需要縮短sds儲存的字串時,程式並不立即使用記憶體重分配來**縮短後多出來的位元組,而是使用free屬性將這些位元組的數量記錄起來,並等待將來使用。

通過惰性空間釋放策略,sds避免了縮短字串時所需的記憶體重分配操作,並為將來可能有的增長操作提供了優化。

與此同時,sds也提供了相應的api,讓我們可以在有需要時,真正地釋放sds的未使用空間,所以不用擔心惰性空間釋放策略會造成記憶體浪費。

雖然資料庫一般用來儲存文字資料,但使用資料庫來儲存二進位制資料的場景也不少見,因此,為了確保redis可以適用於各種不同的使用場景,sds的api都是二進位制安全的(binary-safe),所有sds api都會以處理二進位制的方式來處理sds存放在buf陣列裡的資料,程式不會對其中的資料做任何限制,過濾或者假設,資料在寫入時是什麼樣的,它被讀取時就是什麼樣的。

這也是我們將sds的buf屬性稱為位元組陣列的原因——redis不是用這個陣列來儲存字元,而是用它來儲存一系列的二進位制資料。

redis原始碼分析二 sds實現

1 sds頭部的結構struct attribute packed sdshdr8 2 redis中sds的優勢 1.快速獲取字串長度 這個在於sds的首部中記錄了len表示當前buf的長度,這樣獲取乙個字串的長度的複雜度變成了o 1 這也是用空間換效率的有效方法,因為暫用的空間非常小且現在我們電腦...

Redis原始碼分析 intset h c

intset.h c 是redis 的整數set實現,intset的結構體如下 基本結構 typedef struct intset intset intset的第乙個成員encoding,表明contents中的儲存資料的資料長度,可以是16bits,32bits,64bits。第二個成員leng...

Redis原始碼分析系列

redis目前熱門nosql記憶體資料庫,量不是很大,本系列是本人閱讀redis原始碼時記錄的筆記,由於時間倉促和水平有限,文中難免會有錯誤之處,歡迎讀者指出,共同學習進步,本文使用的redis版本是2.8.19。redis之hash資料結構 redis之intset資料結構 redis之skipl...