redis 原理系列之--字符串存儲的實現原理(1)


背景

redis功能強大,幾乎已經成了現代大中型服務必備的緩存技術了。 除了十分給力的緩存功能,redis當做消息隊列,數據庫也有着不錯的表現。

我們都知道,redis 有五種數據類型,string,list, hash, set 和zset。 其中 最基本的,同時也是最常用的 就是string了。 本文就來談談 redis內部,string 的實現原理:SDS(simple dynamic string)。

redis簡單動態字符竄:SDS

  • 在redis里,C語言的字符竄只用來放字符串字面量,即只有當無序對字符串修改的時候才用C的字符串,例如打印日志的時候。
  • 除了基本的字符串存儲之外,sds還用做緩沖區。AOF模塊的緩沖區,和客戶端狀態的輸入緩沖區,都是sds實現的。
SDS 定義
struct sdshdr {
    
    // buf 中已占用空間的長度
    int len;

    // buf 中剩余可用空間的長度
    int free;

    // 數據空間
    char buf[];
};

圖示如下:

sds結構

  • 簡單解釋一下: buf是一個字節數組,是用來放具體數據的。其長度是按一定策略伸縮的,具體解釋在下面。 len 表示buf 中已經使用掉的長度,free表示 buf中尚未使用的長度。

  • buf內 sds 的字符串,總是以空字符結尾,這一點同c字符串一致。 因此sds 可以直接重用一部分c字符串函數庫的函數。

SDS 與C字符串的對比 和優點

1,O(1) 獲取字符串長度

  • 因為sds已經存了數據的長度,所以獲取字符串長度復雜度為O(1),而C字符串獲取長度為O(n)。

2,杜絕緩沖區溢出導致的內存問題

  • 假設內存區域有s1:“hi”,s2: “redis” 兩個字符串,位置緊鄰,如下圖:

緊鄰字符串被覆蓋

  • 此時需要給s1 追加一個“boy”, 如果是C字符串,忘記了在追加之前先給s1 分配空間,此時追加將導致 s2的值被意外的修改。 而使用 sds則不會有這個問題。 因為其封裝好的函數,會在追加數據之前先檢查 空間是否夠用,如果不夠用就擴容。

3,通過空間預分配和空間惰性釋放 減少內存分配問題

  • 當給sds的值追加一個字符串,而當前的剩余空間不夠時,就會觸發sds的擴容機制。擴容采用了空間預分配的優化策略,即分配空間的時候:如果sds 值大小< 1M ,則增加一倍; 反之如果>1M , 則當前空間加1M作為新的空間。

  • 當sds的字符竄縮短了,sds的buf內會多出來一些空間,這個空間並不會馬上被回收,而是暫時留着以防再用的時候進行多余的內存分配。這個是惰性空間釋放的策略

4, 二進制安全

  • c字符串必須符合某種編碼(例如ASCII),且不能包含空字符。 這些限制使得 c字符竄不能保存圖片,音頻等二進制文件。 而sds的api 都是二進制安全的,其所有api 都會以處理二進制的方式來處理buf內的數據,所以不會有任何的限制。

SDS 的API接口列表

函數 作用 復雜度
sdsnew 以一個c字符竄為參數新建sds O(N)
sdsempty 新建空的sds字符串 O(1)
sdsfree 釋放sds O(N)
sdslen 獲取已使用長度 O(1)
sdsavail 獲取未使用長度 O(1)
sdsdup 創建一個sds的副本 O(N)
sdsclear 清空 O(1)
sdscat 追加C字符串到sds O(N)
sdscatsds 追加sds字符串到sds O(N)
sdscpy 用c字符串覆蓋sds值 O(N)
sdsgrowzero 用空字符串擴展 sds至給定長度 O(N)
sdsrange 刪除給定區間外的數據 O(N)
sdscmp 對比sds是否相同 O(N)
sdstrim 從sds中去除給定c字符串中出現過的字符 O(N*M)

總結

sds 其實就是字符串數組的一個封裝,但是由於考慮了多種場景,作者給它適配了多個高效、優雅的接口,使得 sds成為了一個存儲字符串的優秀設計。使得sds成為一個獨立的、可提供高效優質服務的基礎實體。

我們在設計一些偏底層的數據結構、對象、甚至是數據庫表的時候,可以參考sds的設計,從中尋找一些啟發。

參考: 《Redis 設計與實現》 黃健宏

有收獲就點個贊吧~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM