1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        【死磕 Redis】----- Redis 數據結構:sds

        共 3217字,需瀏覽 7分鐘

         ·

        2020-09-16 06:49

        字符串使我們在編程過程中使用最為廣泛的對象了,在 Redis 中同樣如此。我們知道 Redis 是 C 語言實現的,但是 Redis 放棄了 C 語言傳統(tǒng)的字符串而是自己創(chuàng)建了一種名為簡單動態(tài)字符串 SDS(Simple Dynamic String)的抽象類型,并將 SDS 用作 Redis 的默認字符串表示,其主要原因就是傳統(tǒng)的字符串表示方式并不能滿足 Redis 對字符串在安全性、效率、以及功能方面的要求。所以這篇文章就來說說 SDS。

        在 Redis 里面,只會將 C 語言字符串當做字符串字面量,用于一些無須對字符串進行修改的地方,比如打印日志。在大多數場景下,Redis 都是使用 SDS 來作為字符串的表示。

        對比 C 語言字符串,SDS 具有如下優(yōu)點:

        1. 常數復雜度獲取字符串長度。

        2. 杜絕緩沖區(qū)溢出。

        3. 減少修改字符串長度時所需的內存重分配次數。

        4. 二進制安全。

        5. 兼容部分 C 字符串函數。

        SDS 的定義

        SDS 的源碼主要實現在 sds.csds.h 兩個文件中。其定義為:

        1. struct __attribute__ ((__packed__)) sdshdr5 {

        2. unsigned char flags; /* 3 lsb of type, and 5 msb of string length */

        3. char buf[];

        4. };


        5. struct __attribute__ ((__packed__)) sdshdr8 {

        6. uint8_t len; /* used */

        7. uint8_t alloc; /* excluding the header and null terminator */

        8. unsigned char flags; /* 3 lsb of type, 5 unused bits */

        9. char buf[];

        10. };


        11. struct __attribute__ ((__packed__)) sdshdr16 {

        12. uint16_t len; /* used */

        13. uint16_t alloc; /* excluding the header and null terminator */

        14. unsigned char flags; /* 3 lsb of type, 5 unused bits */

        15. char buf[];

        16. };


        17. struct __attribute__ ((__packed__)) sdshdr32 {

        18. uint32_t len; /* used */

        19. uint32_t alloc; /* excluding the header and null terminator */

        20. unsigned char flags; /* 3 lsb of type, 5 unused bits */

        21. char buf[];

        22. };


        23. struct __attribute__ ((__packed__)) sdshdr64 {

        24. uint64_t len; /* used */

        25. uint64_t alloc; /* excluding the header and null terminator */

        26. unsigned char flags; /* 3 lsb of type, 5 unused bits */

        27. char buf[];

        28. };

        從上述代碼可以看出,每一個 sdshdr 都是由以下幾個部分組成(sdshdr5除外):

        • len:SDS 字符串已使用的空間

        • alloc:申請的空間,減去len就是未使用的空間,初始時和len一致。

        • flag:只使用了低三位表示類型,細化了SDS的分類,根據字符串的長度的不同選擇不同的 SDS 結構體,而結構體的主要區(qū)別是 len 和 alloc 的類型,這樣做可以節(jié)省一部分空間大小,畢竟在 Redis 字符串非常多,進一步的可以節(jié)省空間。

        • buf:char 類型數組,表示不定長字符串。

        SDS 采用一段連續(xù)的內存空間來存儲字符串,下圖是字符串 “Redis” 在內存中的示例圖:

        SDS 與 C 字符串的區(qū)別

        SDS相比C字符串,在安全性、性能、功能性具有優(yōu)勢:

        1. 安全性:防止緩沖區(qū)溢出、二進制安全

        2. 性能:獲取字符串長度、空間預分配、惰性釋放

        3. 功能性:二進制安全、兼容部分C字符串函數

        緩沖區(qū)溢出

        緩沖區(qū)溢出(buffer overflow):是這樣的一種異常,當程序將數據寫入緩沖區(qū)時,會超過緩沖區(qū)的邊界,并覆蓋相鄰的內存位置。

        C 字符串不記錄自身長度,不會自動進行邊界檢查,所以會增加溢出的風險。如下面函數

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

        該函數是將 src 字符串內容拼接到 dest 字符串的末尾。假如有 s1 = “Redis”,s2 = "MongoDB",如下:

        當執(zhí)行 strcat(s1,'Cluster') 時,未給 s1 分配足夠的內存空間,s1 的數據將會溢出到 s2 所在的內存空間,導致 s2 保存的內容被修改,如下:

        與 C 字符串不同,SDS 杜絕了發(fā)生緩存溢出的可能性,他會按照如下步驟進行:

        1. 先檢查 SDS 的空間是否滿足修改所需的要求

        2. 如果不滿足要求的話,API 會自動將 SDS 的空間擴展到執(zhí)行修改所需的大小

        3. 最后才是執(zhí)行實際的修改操作

        例子可見:sds.c/sdscat

        1. sds sdscatlen(sds s, const void *t, size_t len) {

        2. size_t curlen = sdslen(s);


        3. s = sdsMakeRoomFor(s,len);

        4. if (s == NULL) return NULL;

        5. memcpy(s+curlen, t, len);

        6. sdssetlen(s, curlen+len);

        7. s[curlen+len] = '\0';

        8. return s;

        9. }

        常數復雜度獲取字符串長度

        我們知道 C 字符串是不會記錄自身的長度信息,因此我們要獲取一個 C 字符串的長度,需要變臉整個字符串,直到遇到第一個 '\0',復雜度為 O(n)。但是 SDS 記錄了自身長度 len,因此其復雜度降為 O(1) 就能獲取字符串的長度。

        空間預分配

        當 SDS 的 API 要對一個 SDS 進行修改,并且需要對 SDS 進行空間擴展的時候,程序不僅會為 SDS 分配修改所必須要的空間,還會為 SDS 分配額外的未使用的空間,具體策略見 sds.c/sdsMakeRoomFor,如下:

        1. sds sdsMakeRoomFor(sds s, size_t addlen) {

        2. void *sh, *newsh;

        3. size_t avail = sdsavail(s);

        4. size_t len, newlen;

        5. char type, oldtype = s[-1] & SDS_TYPE_MASK;

        6. int hdrlen;


        7. /* Return ASAP if there is enough space left. */

        8. if (avail >= addlen) return s;


        9. len = sdslen(s);

        10. sh = (char*)s-sdsHdrSize(oldtype);

        11. newlen = (len+addlen);

        12. if (newlen < SDS_MAX_PREALLOC)

        13. newlen *= 2;

        14. else

        15. newlen += SDS_MAX_PREALLOC;


        16. type = sdsReqType(newlen);


        17. /* Don't use type 5: the user is appending to the string and type 5 is

        18. * not able to remember empty space, so sdsMakeRoomFor() must be called

        19. * at every appending operation. */

        20. if (type == SDS_TYPE_5) type = SDS_TYPE_8;


        21. hdrlen = sdsHdrSize(type);

        22. if (oldtype==type) {

        23. newsh = s_realloc(sh, hdrlen+newlen+1);

        24. if (newsh == NULL) return NULL;

        25. s = (char*)newsh+hdrlen;

        26. } else {

        27. /* Since the header size changes, need to move the string forward,

        28. * and can't use realloc */

        29. newsh = s_malloc(hdrlen+newlen+1);

        30. if (newsh == NULL) return NULL;

        31. memcpy((char*)newsh+hdrlen, s, len+1);

        32. s_free(sh);

        33. s = (char*)newsh+hdrlen;

        34. s[-1] = type;

        35. sdssetlen(s, len);

        36. }

        37. sdssetalloc(s, newlen);

        38. return s;

        39. }

        • 如果對 SDS 進行修改后,SDS 的長度小于 1MB,那么則擴大兩倍,若 SDS 的長度大于等于 1MB,那么則會增加 1MB。

        通過空間預分配策略,Redis 可以減少聯系執(zhí)行字符串增長操作所需的內存分配次數。

        惰性釋放

        預分配空間用于優(yōu)化 SDS 字符串增長操作,惰性釋放則用于優(yōu)化字符串縮短的操作:當 SDS 的 API 需要縮短 SDS 保存的字符串時,程序并不立即使用內存重分配來回收縮短后多出來的字節(jié),而是回歸到 alloc 屬性中,并等待將來使用。

        通過惰性釋放策略,SDS 避免了縮短字符串時所需的內存重分配操作,并為將來可能有的增長操作提供了優(yōu)化。

        二進制安全

        二進制安全(binary-safe):指能處理任意的二進制數據,包括非ASCII和null字節(jié)。

        我們知道 C 字符串是以空字符(\0)結尾,所以除了字符串的末尾之外,字符串里面不能包含任何的空字符,否則最先被程序讀入的空字符會被誤認為是字符串的結尾。這些限制使得 C 字符串只能保存文本數據,不能保存圖像、視頻、音頻等二進制數據。

        而 SDS 不會對數據做任何限制、過濾、假設,數據在寫入時是什么樣子的,它被讀取時就是什么樣的。因此 Redis 不僅可以保存文本數據,還可以保存任意格式的二進制數據。

        參考

        • 《Redis 設計與實現》

        • Redis源碼分析之sds

        • redis源碼分析(二)、redis源碼分析之sds字符串











        【死磕 Redis】----- 開篇

        【死磕 Redis】----- Redis 通信協議 RESP

        【死磕 Redis】----- Redis 的線程模型

        【死磕 Redis】----- 事務

        【死磕 Redis】----- 理解 pipeline 管道

        【死磕 Redis】----- 布隆過濾器

        【死磕 Redis】----- 發(fā)布與訂閱

        【死磕 Redis】-----如何排查 Redis 中的慢查詢

        【死磕 Redis】-----持久化

        【死磕 Redis】----- 主從復制(一):概述

        【死磕 Redis】----- 主從復制(二):全量復制和部分復制

        【死磕 Redis】----- 主從復制(三):注意的問題

        【死磕 Redis】----- 哨兵(一):部署哨兵架構

        【死磕 Redis】----- 哨兵(二):基本原理

        【死磕 Redis】----- info 命令詳解

        【死磕 Redis】------ 理解 Redis 的內存

        【死磕 Redis】----- Redis 集群搭建

        【死磕 Redis】----- Redis 數據結構:dict

        瀏覽 70
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            中文久久久久 | 啪啪网站入口 | 男生裸体视频 | 国产黄色免费乱伦片 | 国产免费久久 | 亚洲三级在线免费观看 | 办公室秘书高h荡肉呻吟 | 黄片操比 | 少妇酒店露脸3p 少妇的诱惑播放 | 国产裸体无遮挡免费视频 |