Redis数据结构之SDS

redis构建了一种简单动态字符串(Simple Dynamic String, SDS),作为Redis的默认字符串表示。
SDS除了被用于保存数据中的字符串值之外,还可以用作缓冲区。

一、SDS数据结构

在redis的源码中,有5种header类型,分别代表占用大小不同的空间。
Redis数据结构之SDS_第1张图片

  • len: 记录buf数组中已经占用的字节数量,等于SDS所保存字符串的长度
  • alloc: 分配的buf的长度,不包括空字符结尾
  • flags:低3位标识类型,高5位暂时未用
  • buf:字节数组,用于保存字符串内容
typedef char *sds;

/* Note: sdshdr5 is never used, we just access the flags byte directly.
 * However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

二、SDS有点

2.1 O(1)复杂度读取字符串长度

C字符串不记录自身长度,如果需要过去字符串长度,需要遍历字符串,操作复杂度为O(N)。SDS中维护了len属性,获取字符串长度操作时间复杂度为O(1)。这就保障了redis在对字符串对象执行STRLEN获取字符串长度时,效率很高。

2.2 杜绝缓冲区溢出

因为C字符串不记录自身长度,所以也有可能造成缓冲区溢出。
SDS具有空间分配策略,可以杜绝缓冲区溢出的问题。当对SDS进行修改时,会首先检查SDS空间是否满足要求,如果不满足,会首先对空间进行扩容,然后才真正执行修改操作。
但是对空间进行扩容是一个比较耗时的操作,对于效率至上的redis来说,也需要考虑并进行优化。

2.3 减少内存分配次数

为了满足效率要求,应该尽可能减少内存分配次数,做到空间换时间。redis主要通过空间预分配和空间惰性释放两个手段进行效率提升。

2.3.1 空间预分配

redis有两个方法会进行空间再分配,sdsMakeRoomForNonGreedy和sdsMakeRoomFor。顾名思义,sdsMakeRoomForNonGreedy只是将空间分配到所需大小,sdsMakeRoomFor会进行预分配。两者都是调用_sdsMakeRoomFor,其中sdsMakeRoomForNonGreedy的greedy参数传0,sdsMakeRoomFor的greedy参数传1。
redis通过_sdsMakeRoomFor的空间分配逻辑如下,在空间分配时会根据当前空间大小决定是否重新分配或者如何重新分配。

  • 如果当前空间满足要求,不进行内存分配,直接return。
  • 如果当前空间不满足要求,且当前需要扩容的话,会根据当前空间大小决定,如果当前空间小于SDS_MAX_PREALLOC(1M),会进行翻倍扩容
  • 如果当前空间不满足要求,且当前需要扩容的话,会根据当前空间大小决定,如果当前空间大于SDS_MAX_PREALLOC(1M),每次扩容会增加1M
void *sh, *newsh;
    size_t avail = sdsavail(s);
    size_t len, newlen, reqlen;
    char type, oldtype = s[-1] & SDS_TYPE_MASK;
    int hdrlen;
    size_t usable;

    /* Return ASAP if there is enough space left. */
    if (avail >= addlen) return s;

    len = sdslen(s);
    sh = (char*)s-sdsHdrSize(oldtype);
    reqlen = newlen = (len+addlen);
    assert(newlen > len);   /* Catch size_t overflow */
    if (greedy == 1) {
        if (newlen < SDS_MAX_PREALLOC)
            newlen *= 2;
        else
            newlen += SDS_MAX_PREALLOC;
    }

    type = sdsReqType(newlen);

2.3.2 空间惰性释放

当对SDS进行字符串缩减时,也不会立即释放空间,而且将释放的空间保留在SDS中,为后续的字符串追加做准备。

2.4 二进制安全

C字符串特殊格式要求,除字符串末尾之处,字符串不允许包含空字符串,否则最先被程序读入的空字符串就会误认为字符串结尾。所以这些限制就C字符串不能保存图片、音频、视频、压缩文件等二进制文件数据。
而SDS底层使用的是二进制数组来保存的数据,所以对二进制安全。

你可能感兴趣的:(Redis,redis,数据结构,数据库)