Redis SDS 源码

底层数据结构的好处:

  1. 杜绝缓冲区溢出。
  2. 减少修改字符串长度时所需的内存重分配次数。
  3. 二进制安全。
  4. 兼容部分C字符串函数。

常用命令: set key value、get key 等

应用场景:共享 session、分布式锁,计数器、限流。

1、给char*定义了个别名。

复制代码
typedef char *sds;

2、创建sds字符串并且分配空间

java 复制代码
sds.c
sds结构体
/*
 * 保存字符串对象的结构
 */
struct sdshdr {
    
    // buf 中已占用空间的长度
    int len;

    // buf 中剩余可用空间的长度
    int free;

    // 数据空间
    char buf[];
};
//好处之一:创建sds字符串的时候会优先分配空间并且预留下一次分配空间。
sds sdsnewlen(const void *init, size_t initlen) {
    定义sds结构体指针
    struct sdshdr *sh;

    if (init) {
        //创建sds结构体并且分配空间
        sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
    } else {
        sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
    }
    if (sh == NULL) return NULL;
    sh->len = initlen;
    sh->free = 0;
    if (initlen && init)
        memcpy(sh->buf, init, initlen);
    sh->buf[initlen] = '\0';
    return (char*)sh->buf;
}

3、sds字符串的追加

java 复制代码
/* Append the specified binary-safe string pointed by 't' of 'len' bytes to the
 * end of the specified sds string 's'.
 *
 * After the call, the passed sds string is no longer valid and all the
 * references must be substituted with the new pointer returned by the call. */
//s目标字符串
//t源字符串
//len追加的长度
sds sdscatlen(sds s, const void *t, size_t len) {
    struct sdshdr *sh;
    //计算目标字符串的长度
    size_t curlen = sdslen(s);
    //根据要追加的长度len和目标字符串s的现有长度,判断是否要增加新的空间
    s = sdsMakeRoomFor(s,len);
    if (s == NULL) return NULL;
    sh = (void*) (s-(sizeof(struct sdshdr)));
    //将源字符串t中len长度的数据拷贝到目标字符串结尾
    memcpy(s+curlen, t, len);
    sh->len = curlen+len;
    sh->free = sh->free-len;
    //追加\0作为本次追加的结尾。
    s[curlen+len] = '\0';
    return s;
}

3-1、扩容详细。

,这是一个相对耗时的操作,这里尽量在使用的时候做好计算。

java 复制代码
/* Enlarge the free space at the end of the sds string so that the caller
 * is sure that after calling this function can overwrite up to addlen
 * bytes after the end of the string, plus one more byte for nul term.
 * 
 * Note: this does not change the *length* of the sds string as returned
 * by sdslen(), but only the free buffer space we have. 
 * 当计算后的新的长度小于1MB,则分配两倍空间
 * 当计算后的新的长度大于1MB,则在原来基础上加多1MB。
*/
#define SDS_MAX_PREALLOC (1024*1024)
sds sdsMakeRoomFor(sds s, size_t addlen) {

    struct sdshdr *sh, *newsh;

    // 获取 s 目前的空余空间长度
    size_t free = sdsavail(s);

    size_t len, newlen;

    // s 目前的空余空间已经足够,无须再进行扩展,直接返回
    if (free >= addlen) return s;

    // 获取 s 目前已占用空间的长度
    len = sdslen(s);
    sh = (void*) (s-(sizeof(struct sdshdr)));

    // s 最少需要的长度
    newlen = (len+addlen);

    // 根据新长度,为 s 分配新空间所需的大小
    if (newlen < SDS_MAX_PREALLOC)
        // 如果新长度小于 SDS_MAX_PREALLOC 
        // 那么为它分配两倍于所需长度的空间
        newlen *= 2;
    else
        // 否则,分配长度为目前长度加上 SDS_MAX_PREALLOC
        newlen += SDS_MAX_PREALLOC;
    // T = O(N)
    newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);

    // 内存不足,分配失败,返回
    if (newsh == NULL) return NULL;

    // 更新 sds 的空余长度
    newsh->free = newlen - len;

    // 返回 sds
    return newsh->buf;
}

4、SDS 类型

sdshdr5、sdshdr8、sdshdr16、sdshdr32、sdshdr64

5种结构体类型,设计是一样的,字符数组现有长度 len 和分配空间长度 alloc是不一样的。

以sdshdr8结构体为例,

attribute ((packed))的作用:

//告诉编译器,在编译 sdshdr8 结构时,不要使用字节对齐的方式,而是采用紧凑的方式分配内存。这是因为在默认情况下,编译器会按照 8 字节对齐的方式,给变量分配内存。也就是说,即使一个变量的大小不到 8 个字节,编译器也会给它分配 8 个字节。

java 复制代码
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* 字符数组现有长度*/
    uint8_t alloc; /* 字符数组的已分配空间,不包括结构体和\0结束字符*/
    unsigned char flags; /* SDS类型*/
    char buf[]; /*字符数组*/
};
相关推荐
卜及中31 分钟前
【Redis/2】核心特性、应用场景与安装配置
数据库·redis·缓存
fat house cat_3 小时前
【redis】线程IO模型
java·redis
敖云岚4 小时前
【Redis】分布式锁的介绍与演进之路
数据库·redis·分布式
让我上个超影吧6 小时前
黑马点评【基于redis实现共享session登录】
java·redis
懒羊羊大王呀10 小时前
Ubuntu20.04中 Redis 的安装和配置
linux·redis
John Song11 小时前
Redis 集群批量删除key报错 CROSSSLOT Keys in request don‘t hash to the same slot
数据库·redis·哈希算法
Zfox_20 小时前
Redis:Hash数据类型
服务器·数据库·redis·缓存·微服务·哈希算法
呼拉拉呼拉20 小时前
Redis内存淘汰策略
redis·缓存
咖啡啡不加糖1 天前
Redis大key产生、排查与优化实践
java·数据库·redis·后端·缓存
MickeyCV1 天前
使用Docker部署MySQL&Redis容器与常见命令
redis·mysql·docker·容器·wsl·镜像