Redis設計與實現之String

2021-10-23 06:31:23 字數 3276 閱讀 1049

字串物件編碼

擴充套件redis(remote dictionary server ),即遠端字典服務,是乙個完全開源(遵守bsd協議)免費的使用c語言編寫、支援網路、可基於記憶體亦可持久化的日誌型、key-value資料庫,並提供多種語言的api。

redis 與其他 key - value 快取產品有以下三個特點:

sds全稱是簡單動態字串(****** dynamic string)。

c字串本身不記錄字串長度,每次獲取字串長度需要遍歷一次陣列。這個操作的複雜度為o(n)。

sds的len屬性記錄了本身的長度,所以獲取乙個sds長度的複雜度為o(1)。

c字串本身不記錄字串長度,導致的另外乙個問題是容易造成緩衝區溢位。

假設程式裡有2個記憶體中緊鄰的c字串s1和s2,其中s1儲存了字串「redis」,而s2則儲存了字串「mongodb」。

如果有人直接執行:strcat(s1, 「 cluster」);將s1的內容修改為「redis cluster」。由於沒有提前給s1分配足夠的空間,那麼在strcat函式執行後,s1資料將溢位到s2所在的空間,導致s2儲存的內容被意外修改。

與c字串不同,sds空間分配策略完全可以杜絕緩衝區溢位的問題:當sds api需要對sds進行修改時,api會先檢查sds的空間是否滿足修改所需要的要求,如果不滿足的話,api會自動將sds空間擴充套件至執行修改所需要的大小,然後才執行實際的修改操作,所以使用sds即不需要手動修改sds空間大小,有不會前面所說的緩衝區溢位問題。

如上圖執行strcat(s1, 「 cluster」)操作,那麼空間不夠拼接,會先擴充套件空間然後執行拼接「 cluster」操作,拼接的結果如下圖所示。

正如前面所說,由於c字串並不記錄自身的長度,所以對於乙個包含了n個字元的c字串來說,底層實現總數乙個n+1個字元長度的陣列。所以每次增長或者縮短乙個c字串,程式總要儲存這個c字串的陣列進行一次記憶體重新分配操作:

如果程式執行的是縮短字串操作,比如截斷操作(trim)那麼在執行這個操作之後,程式需要通過記憶體重分配來釋放不再使用的那部分空間,如果忘記了就會導致記憶體洩露。

為了避免c字串的頻繁分配記憶體的缺陷,sds通過未使用空間解除了字串長度和底層陣列長度的關聯:在sds中,buf陣列的長度不一定就是數量+1(如下圖),陣列裡面可以包含未使用位元組,而這些位元組的數量由sds的free屬性記錄。

通過未使用空間,sds實現了空間預分配惰性空間釋放兩種優化策略。

空間預分配

空間預分配用於優化sds的字串增長操作:當sds的api對乙個sds進行修改,並且需要對sds進行空間擴充套件的時候,程式不僅會為sds分配修改所必須的空間,還會為sds分配額外的未使用空間。

分配未使用空間數量的公式如下:

例子:如果修改後sds的len將變成13位元組,那麼程式會分配13位元組的未使用空間,sds的buf陣列的實際長度將變成13+13+1(儲存空字元)=27位元組。

例子:如果字串修改後,sds的len將變成30mb,那麼程式會分配1mb的未使用空間,sds的buf陣列的實際長度為30mb+1mb+1byte

在擴充套件sds空間之前,sds api會先檢查未使用空間是否足夠,如果足夠的話,api就會使用未使用空間,而無須執行記憶體重分配。

惰性空間釋放

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

例子:如果左圖的sds字串s執行了sdstrim(s, 「xy」);移除sds中所有的x和y。函式執行後,並沒有釋放多出來的8個位元組空間,而是將這8個位元組空間作為未使用空間保留在sds裡面,使用free屬性記錄。如果有增長操作可以直接使用,而不用重新分配記憶體空間。

字串物件編碼可以是int、raw或者embstr。

如果乙個字串物件儲存的是整數值,並且這個整數值可以用long型別來表示。那麼這個字串物件的編碼設定為int。

如果字串物件儲存的是乙個字串值,並且這個字串的長度大於32位元組,那麼字串物件使用乙個簡單動態字串(sds)來儲存這個值,並且將物件的編碼設定為raw。

如果字串物件儲存的是乙個字串值,並且這個字串的長度小於等於32位元組,那麼字串物件使用乙個簡單動態字串(sds)來儲存這個值,並且將物件的編碼設定為embstr。

embstr編碼是專門用於儲存短字串的一種編碼優化方式。

raw編碼會呼叫兩次記憶體分配函式分別建立redisobject結構和sdshdr結構,而embstr編碼則通過呼叫一次記憶體分配函式來建立一塊連續的記憶體空間,空間中依次包含了redisobject和sdshdr兩個結構。

物件編碼(資料結構)

列表物件

ziplist(壓縮列表)

linkedlist(雙端列表)

雜湊物件

ziplist(壓縮列表)

hashtable

集合物件

intset(整數集合)

hashtable

有序集合物件

ziplist(壓縮列表)

skiplist(跳躍表)

Redis設計與實現之跳躍表

一 定義 跳躍表 skiplist 是一種有序的資料結構,它通過在每個節點中維持多個指向其他節點的指標,從而達到快速訪問節點的目的 跳躍表支援平均o logn 最壞o n 複雜度的節點查詢,還可以通過順序性操作來批量處理節點。redis使用跳躍表作為有序集合鍵的底層實現之一,若乙個有序集合包含的元素...

Redis 設計與實現

本書的目標是以簡明易懂的方式講解 redis 的內部執行機制,通過閱讀本書,你可以了解到 redis 從資料結構到伺服器構造在內的幾乎所有知識。為了保證內容的簡潔性,本書會盡量以高抽象層次的角度來觀察 redis 並將 的細節留給讀者自己去考究。如果讀者只是對 redis 的內部運作機制感興趣,但並...

redis設計與實現

物件所使用的底層資料結構 編碼常量 object encoding 命令輸出 整數redis encoding int int embstr編碼的簡單動態字串 sds redis encoding embstr embstr 簡單動態字串 redis encoding raw raw 字典redis...