Redis的底層實現 字串章節

2021-08-26 05:47:16 字數 3631 閱讀 5506

不要遺忘最初的目標。 –ruider 總結

about me

redis的命令如下:

set x "hello";

get x;

hello

redis作為一種儲存字串的快取結構,其具體實現是由c語言完成,在c語言中,字串是通過字元陣列實現的,即char,那麼redis對於字串的實現是不是也是基於字元陣列嗎?不是的,redis對字串的處理是通過sds(****** dynamic string)實現的。

sds(****** dynamic string)簡單動態字串,它是由c語言完成,如下是其具體實現

struct sdshdr;

看看redis的示例:

sdshdr

free 0

length 5

buf -->|'r'|'e'|'d'|'i'|'s'|'\0'|

解釋: - free為0,表示這個sds沒有分配任何未使用的空間

- length為5,表示這個sds儲存了乙個長度為5的字串

- buf陣列中儲存著「redis」字串

sds遵循c字串以空字串結尾的慣例,儲存空字串的1位元組空間不計算在sds的len屬性之中。

再看看sds的free不為0的情況:

sdshdr

free 3

length 5

buf --

>

|'r'

|'e'

|'d'

|'i'

|'s'||

||

free的值為3,表示這個sds分配了三個空閒的空間

c語言使用簡單的字串表示方式,並不能滿足redis對字串在安全性,效率,以及功能方面的要求,sds更使用redis。

#####@1 常數複雜度獲取字串長度

c字串:

因為c語言並不記錄自身的長度資訊,所以獲取乙個c字串的長度,程式必須遍歷整個字串,對遇到的,每個字元進行計數,直到遇到代表字串結尾的空字串為止,這個操作的複雜度為o(n)。

sds:

與c語言不同的是,sds結構中的屬性length記錄了sds本身的長度,所以獲取乙個sds長度的複雜度為o(1)。有人疑問那麼sds的length值是哪來的?這裡的length值是sds api在設定和更新sds時自動完成的。

總結1:通過使用sds而不是c字串,redis獲取字串長度的複雜度由o(n)降為o(1),這確保了字串長度的獲取的工作不會成為redis的效能瓶頸。

@ 2杜絕緩衝區溢位

c字串:

由於c自身不記錄字串的長度帶來乙個問題是容易造成緩衝區溢位(buffer overflow)。在/strcat函式中,可以將乙個字串拼接到另外乙個字串的末尾。

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

理想狀態下,使用者在使用這個函式時,假定c為dest分配了足夠多的記憶體,可以容納src字串中的所有內容,而一旦這個假定不成立,就會產生緩衝區溢位。舉個例子,假定記憶體中有相鄰的兩個字串s1,s2,如圖:

s1                        s2

| |

...|'r'|'e'|'d'|'i'|'s'|'\0'||'g'|'o'|'o'|'d'|'\0'|...

如果執行strcat(s1," cluster");將redis改為」redis cluster「,但是粗心的卻忘了在執行這句之前為s1分配足夠的空間,那麼在執行之後,s1的資料將會溢位到s2所在的空間,導致s2儲存的內容意外的被修改。

sds:

與c語言不同的是,sds空間分配政策完全杜絕了發生緩衝區溢位的可能性:當sds api需要對字串進行修改時,首先會檢查sds的空間是否滿足修改所需的要求,因為sds自身有對字串長度記錄的屬性length和空閒空間屬性free,可以借助這兩個引數進行檢查。sds會在執行動作之前判斷sds的空間大小,再去執行操作,如果空間不夠的話,sds api會自動擴充套件空間。

@ 3減少修改字串時帶來的記憶體重分配次數

c字串:

因為c字串不記錄自身長度,每次增長或者縮短字串長度時,程式都要對這個c字串陣列進行一次記憶體重新分配操作,不然容易造成記憶體益出。因為記憶體,分配設計複雜的演算法,並且可能需要執行系統呼叫,所以它通常是乙個比較耗時和耗能的操作。但是redis作為快取,追求速度,所以不能經常發生記憶體分配操作。

sds:

sds陣列中的未使用空間位元組數量由sds的屬性free記錄,通過free記錄,sds實現了空間預分配和惰性釋放兩種優化策略。

1. 空間預分配

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

(1)如果修改sds之後,sds的長度小於1mb,那麼程式會給sds分配和length一樣大的額外空間,這是sdslength和free的值相等。舉個例子,如果修改後的字串長度為13k,那麼sds的空間將會佔據13+13+1=27k(額外的乙個位元組用於儲存空字串)。

(2)如果修改sds之後,sds的長度大於1mb,那麼程式會給sds分配額外的1mb空間,舉個例子,比如修改後的sds有30mb的大小,那麼程式會分配1mb的未使用空間,sds的buf陣列實際大小將是30mb+1mb+1byte。

2.惰性釋放

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

@4 二進位制安全

c字串中的字元必須符合某種編碼(比如ascii),並且除了字串末尾之外,字串裡面不能包含空字串,

否則最先被程式讀入的空字串將被誤認為是字串結尾。

sds api都是二進位制安全的,所有sds api都會以處理二進位制的方式來處理存放在sds buf中的資料,資料寫什麼樣,它被讀取時就是什麼樣子。

@5 相容部分c字串函式

sds的api總會以sds儲存的資料的末尾設定為空字串,並且在分配sds空間時會多分配乙個位元組的空間來容納空字串,這是為了那些儲存的資料可以重用一部分庫中的函式。

字串和sds之間的區別總結如下:

c字串

sdsc字串獲取長度複雜度o(n)

sds獲取字串長度複雜度o(1)

api不是安全的,會出現緩衝區溢位

api是安全的,不會出現緩衝區溢位

修改字串長度n次必然執行n此記憶體重分配

修改字串長度n次必然執行最多n此記憶體重分配

只儲存文字資料

可以儲存文字資料或者二進位制資料

可以使用庫中的函式

可以使用一部分庫中的函式

歡迎交流

我的github

個人部落格

Redis字串的底層設計

redis的底層是用c語言來實現的,在c語言中字串的預設是以 0 標識結束,而redis並沒有採用這種傳統的方法表示,而是自己構建了一種簡單動態字串 sds 來表示。下面看一下什麼是sds,sds的 定義,以及在儲存乙個字串 redis 時sds和傳統c的表示分別是怎麼樣,使用sds的好處是什麼 s...

Redis的底層字串儲存 SDS

我們知道redis資料庫是使用c語言寫的,然而其內部的字串的儲存卻並不是使用傳統的c語言字串表示,而是使用一種名為簡單動態字串 dynamic string,sds 的抽象資料型別。首先我們來對sds有乙個大概的認識 如果我們客戶端執行如下命令 127.0.0.1 6381 set msg hell...

redis 底層原理之動態字串SDS

既然c語言支援字串,為啥要有sds的出現?c語言的字串的缺點 獲取欄位串的長度為0 n 因為每次都遍歷獲取欄位串的長度大小。strcat 函式來進行兩個字串的拼接,一旦沒有分配足夠長度的記憶體空間,就會造成緩衝區溢位。c字元不能有空字串,否則認為空字串結尾,如 i am boy c字元只能識別到 i...