<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【死磕 Redis】----- Redis 數(shù)據(jù)結(jié)構(gòu):sds

          共 3217字,需瀏覽 7分鐘

           ·

          2020-09-16 06:49

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

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

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

          1. 常數(shù)復(fù)雜度獲取字符串長度。

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

          3. 減少修改字符串長度時所需的內(nèi)存重分配次數(shù)。

          4. 二進制安全。

          5. 兼容部分 C 字符串函數(shù)。

          SDS 的定義

          SDS 的源碼主要實現(xiàn)在 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:只使用了低三位表示類型,細(xì)化了SDS的分類,根據(jù)字符串的長度的不同選擇不同的 SDS 結(jié)構(gòu)體,而結(jié)構(gòu)體的主要區(qū)別是 len 和 alloc 的類型,這樣做可以節(jié)省一部分空間大小,畢竟在 Redis 字符串非常多,進一步的可以節(jié)省空間。

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

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

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

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

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

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

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

          緩沖區(qū)溢出

          緩沖區(qū)溢出(buffer overflow):是這樣的一種異常,當(dāng)程序?qū)?shù)據(jù)寫入緩沖區(qū)時,會超過緩沖區(qū)的邊界,并覆蓋相鄰的內(nèi)存位置。

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

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

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

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

          與 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. }

          常數(shù)復(fù)雜度獲取字符串長度

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

          空間預(yù)分配

          當(dāng) 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。

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

          惰性釋放

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

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

          二進制安全

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

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

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

          參考

          • 《Redis 設(shè)計與實現(xiàn)》

          • Redis源碼分析之sds

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











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

          【死磕 Redis】----- Redis 通信協(xié)議 RESP

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

          【死磕 Redis】----- 事務(wù)

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

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

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

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

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

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

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

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

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

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

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

          【死磕 Redis】------ 理解 Redis 的內(nèi)存

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

          【死磕 Redis】----- Redis 數(shù)據(jù)結(jié)構(gòu):dict

          瀏覽 70
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产 无码 成人免费 | 丁香五月网站 | 韩国免费一区二区三区 | 在线三级在线观看网站 | 久久亚洲AV成人无码国产精品 |