首页 > 数据库 >Redis数据结构2:REDIS_STRING(SDS)

Redis数据结构2:REDIS_STRING(SDS)

时间:2023-12-09 16:04:55浏览次数:36  
标签:__ alloc STRING SDS REDIS Redis len char 字符串

REDIS_STRING(SDS)

SDS全称Simple Dynamic String(简单动态字符串),是专为Redis设计的简易字符串实现。

Redis并未采用C语言传统字符串char*,而是自己设计了一套字符串实现标准。

传统字符串的缺陷

C语言字符串实际上就是一个以'\0'结尾的字符数组。

例如:

char* myName = "ErickRen";

的结构即为:

1.png

该结构有个弊端,如果字符串内部有'\0',则C语言会误认为该字符串结束。

这个限制使得传统C语言字符串只能保存文本数据,不能保存图片、音频、视频等的二进制数据

此外,C语言标准库中字符串操作函数非常不安全,一不小心就会缓冲区溢出。

例如,当使用strcat("Erick", "Ren")函数拼接字符串时,C语言并不会检查该字符串是否有足够的空间,而是会直接操作。

而当C语言每次使用strlen()函数时,实际上是将字符串从头至尾遍历一遍,遇到'\0'为止,每次获取字符串长度的时间复杂度都是O(n)

C语言的确实现了大部分的字符串函数,但这些函数风险太高,并不适合用来构建业务

SDS

而为了业务开发,Redis作者构建了一套新的字符串标准,简称为SDS。

SDS一共有5个实现,都为sdshdr(Simple Dynamic Strings Header)系列,分别为sdshdr5, sdshdr8, sdshdr16, sdshdr32, sdshdr64

/* 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[];
};

从注释信息来看,sdshdr5从未使用过,但**存疑**。不在本文讨论范围内。

除了sdshdr5之外,其他的结构都有四个共同结构:len, alloc, flagsbuf[]

结构分析

len

len存储了该SDS长度,在通过函数获取字符串长度时,可以直接返回该值,将时间复杂度优化到了O(1)

alloc

alloc是分配给字符数组的空间长度,该变量是为了在修改字符串时,通过alloc - len来推导出剩余的空间大小,以判断是否需要扩容操作。alloc和通过alloc推导剩余空间大小是解决缓冲区溢出的根本

flags

flags主要用来表示不同类型的SDS实现。即表明该字符串是上述五种实现的何种。

buf[]

buf[]是一个字符数组,用来保存实际数据。

用SDS标准实现的字符串,不会存在上述的缓冲区溢出问题,可以存储任意二进制数据并且获取字符串长度的时间复杂度为O(1)。

扩容机制

上面提到SDS不存在缓冲区溢出问题,这是因为SDS在数据量不足时会进行自动扩容

当操作字符串时,通过公式alloc - len即可获取当前剩余空间,以判断是否要进行扩容。

hisds hi_sdsMakeRoomFor(hisds s, size_t addlen) {
    ... ...
    // s目前的剩余空间已足够,无需扩展,直接返回
    if (avail >= addlen)
        return s;
    //获取目前s的长度
    len = hi_sdslen(s);
    sh = (char *)s - hi_sdsHdrSize(oldtype);
    //扩展之后 s 至少需要的长度
    newlen = (len + addlen);
    //根据新长度,为s分配新空间所需要的大小
    if (newlen < HI_SDS_MAX_PREALLOC)
        //新长度<HI_SDS_MAX_PREALLOC 则分配所需空间*2的空间
        newlen *= 2;
    else
        //否则,分配长度为目前长度 + 1MB
        newlen += HI_SDS_MAX_PREALLOC;
       ...
}

如果该SDS小于1MB,那么就将它的容量翻倍。

如果该SDS大于1MB,那么每次扩容就为它的容量加上1MB。

不同实现的差异

前文提到,sdshdr中有flags存储sds的实现。

这五种实现的主要差异在lenalloc的数据类型。

例如sdshdr16和sdshdr32:

struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len;
    uint16_t alloc; 
    unsigned char flags; 
    char buf[];
};


struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len;
    uint32_t alloc; 
    unsigned char flags;
    char buf[];
};

sdshdr16中的len和alloc都是uint16_t,长度和分配空间最多只有2<sup>16</sup>。

而sdshdr32为uint32_t,可以存储更多数据。

此举是为了能灵活保存不同大小的字符串,从而有效节省内存空间。比如,在保存小型字符串时,结构头占用空间会更少。

编译优化

sdshdr声明了 __attribute__ ((packed)) ,它是为了告诉编译器取消结构体在编译过程中的优化对齐,按照实际占用字节数进行对齐

以sdshdr8举例,如果不进行此优化,则实际的结构体大小为:

2.png

而进行了优化后,占用的内存即为:

3.png

节省了一部分内存空间。

标签:__,alloc,STRING,SDS,REDIS,Redis,len,char,字符串
From: https://blog.51cto.com/ErickRen/8749775

相关文章

  • Redis学习记录第七天
        今天我们继续深入学习Redis,探讨了Redis的数据结构类型以及一些高级功能。首先,我们先来回顾一下Redis支持的数据结构类型:String(字符串):最基本的数据结构类型,可以存储字符串、数字等数据。Hash(哈希):键值对的集合,可以用于存储对象,支持添加、删除、获取单个或多个键值对。Lis......
  • 常见场景题-Redis的bitmap如何实现签到功能?
    Redis的bitmap实现签到系统?答:主要讲一下Redis原生的bitmap的使用方法,以及如何使用bitmap来实现签到功能先来看一下如何使用redisbitmap的原生命令实现签到功能:签到我们先来设计key:userid:yyyyMM,那么假如usera在2023年10月3日和2023年10月4日签到的话,使用以下命令:se......
  • Redis生产实战-Redis集群故障探测以及降级方案设计
    Redis集群故障探测在生产环境中,如果Redis集群崩溃了,那么会导致大量的请求打到数据库中,会导致整个系统都崩溃,所以系统需要可以识别缓存故障,限流保护数据库,并且启动接口的降级机制降级方案设计我们在系统中操作Redis一般都是通过工具类来进行操作的,假设工具类有两个RedisCache......
  • Redis基础(六)-Redis客户端
    Redis官方对Java语言的封装框架推荐的有十多种,主要是Jedis、Redisson。Jedis和Redisson都是Java中对Redis操作的封装。Jedis只是简单的封装了Redis的API库,可以看作是Redis客户端,它的方法和Redis的命令很类似。Redisson不仅封装了redis,还封装了对更多数据结构的支持,以及......
  • Redis基础(七)-Redis6的事务操作
    Redis的事务定义Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。Redis的事务作用Redis事务的主要作用就是串联多个命令防止别的命令插队。Multi、Exec、discard从输入Multi命令开始,输入的命......
  • JDBC针对SQLServer的sendStringParametersAsUnicode=false的验证
    JDBC针对SQLServer的sendStringParametersAsUnicode=false的验证背景部分客户的SQLServer数据库出现了大量死锁的情况.虽然部分客户并没有反馈死锁影响了产品的正常使用但是在大量业务时还是会出现卡顿等的现象基于此,经过微软case的研究,发现是JDBC4.0之后默认为ture的......
  • Redis数据结构分析1:Redis对象
    Redis数据结构分析本篇将涉及C语言,请确保您拥有C语言相关基础与计算机底层知识RedisObject(robj)robj是Redis对象的起点,所有的数据结构都封装到了robj之中。其源码如下:structredisObject{unsignedtype:4;unsignedencoding:4;unsignedlru:LRU_BITS;......
  • Caused by: io.lettuce.core.RedisCommandExecutionException: NOAUTH Authentication
    原文链接:https://blog.csdn.net/De_Buffer/article/details/132492287最终解决方法虽然通过更换连接客户端为jedis解决了问题,但不符合发展趋势,lettuce已成为主流redis客户端,springboot2官方推荐,因此在这个保底方案基础上继续探究。终于!!找到解决我的问题的一篇文章,跟着他的思......
  • Redis生产实战-热key、大key解决方案、数据库与缓存最终一致性解决方案
    生产环境中热key处理热key问题就是某一瞬间可能某条内容特别火爆,大量的请求去访问这个数据,那么这样的key就是热key,往往这样的key也是存储在了一个redis节点中,对该节点压力很大那么对于热key的处理就是通过热key探测系统对热key进行计数,一旦发现了热key,就将热key......
  • Redis报错:(error) DENIED Redis is running in protected mode because protected mod
    一、报错内容  (error)DENIEDRedisisrunninginprotectedmodebecauseprotectedmodeisenabledandnopasswordissetforthedefaultuser.Inthismodeconnectionsareonlyacceptedfromtheloopbackinterface.Ifyouwanttoconnectfromexternal......