不要遺忘最初的目標。 –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...