redis中SDS实现

sds(simple dynamic string)是Redis自己构建的字符串,作为Redis默认的字符串表示。

/*
 * 类型别名,用于指向 sdshdr 的 buf 属性
 */
typedef char *sds;


/*
 * 保存字符串对象的结构
 */
struct sdshdr {
    
    // buf 中已占用空间的长度
    int len;


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


    // 数据空间
    char buf[];
};

最后的buff用到了c语言中一种空数组的语法技巧——柔性数组,这种写法一般就是结构体和缓存区一起分配内存,空数组不像指针,会占用指针字节,数组名即为指针的其实地址。

SDS字符串不同于C语言的字符串,首先SDS保存了字符串的长度,可以在O(1)的时间获取字符串的长度,不像C语言需要遍历到\0字符才知道是字符串结束;其次防止了缓冲区溢出的问题;通过预先分配使用空间,防止了修改字符串长度带来的内存分配次数,sds的长度策略是这样的,小于1M,则按照申请空间的2倍进行分配,多余1M的内存申请,则在原有的基础上增加1M的未使用空间,代码如下:

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;
}

并且在字符串长度变小时,不会去改变SDS结构的大小的,采用惰性释放,有需要时再释放。

SDS同时也是二进制安全的,但是一样遵循C字符串以空字符结尾的惯例,也就是字符串长度+1是空字符串,所以实现上兼容了很多C的字符串处理函数,用sds接口封装。


Redis3.2在sds增加了多种类型,目的是为了进一步节省内存



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

alloc不同于free,记录的是总共的字符串内存长度,根据不同的申请内存的长度,选择不同的sdshdr,sdshdr5没有使用。

__attribute__ ((__packed__))是gcc一个特性,告诉编译器取消内存对齐,目的是为了节省内存,然而内存的不对齐,可能导致执行时间上的开销。

flags只是用位记录sdshdr具体属于哪一种类型。

 sdsjoin存在效率问题,感觉可以优化为预分配长度,在进行copy.

sds sdsjoin(char **argv, int argc, char *sep) {
    sds join = sdsempty();
    int j;


    for (j = 0; j < argc; j++) {
        join = sdscat(join, argv[j]);
        if (j != argc-1) join = sdscat(join,sep);
    }
    return join;
}




    // 函数先从数据库中删除过期键,然后再对数据库的大小进行修改


    /* Expire keys by random sampling. Not required for slaves
     * as master will synthesize DELs for us. */
    // 如果服务器不是从服务器,那么执行主动过期键清除
    if (server.active_expire_enabled && server.masterhost == NULL)
        // 清除模式为 CYCLE_SLOW ,这个模式会尽量多清除过期键
        activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW);


    /* Perform hash tables rehashing if needed, but only if there are no
     * other processes saving the DB on disk. Otherwise rehashing is bad
     * as will cause a lot of copy-on-write of memory pages. */
    // 在没有 BGSAVE 或者 BGREWRITEAOF 执行时,对哈希表进行 rehash
    if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) {
        /* We use global counters so if we stop the computation at a given
         * DB we'll be able to start from the successive in the next
         * cron loop iteration. */
        static unsigned int resize_db = 0;
        static unsigned int rehash_db = 0;
        unsigned int dbs_per_call = REDIS_DBCRON_DBS_PER_CALL;
        unsigned int j;


        /* Don't test more DBs than we have. */
        // 设定要测试的数据库数量
        if (dbs_per_call > server.dbnum) dbs_per_call = server.dbnum;


        /* Resize */
        // 调整字典的大小
        for (j = 0; j < dbs_per_call; j++) {
            tryResizeHashTables(resize_db % server.dbnum);
            resize_db++;
        }


        /* Rehash */
        // 对字典进行渐进式 rehash
        if (server.activerehashing) {
            for (j = 0; j < dbs_per_call; j++) {
                int work_done = incrementallyRehash(rehash_db % server.dbnum);
                rehash_db++;
                if (work_done) {
                    /* If the function did some work, stop here, we'll do
                     * more at the next cron loop. */
                    break;
                }
            }
        }
    }
}



    // 函数先从数据库中删除过期键,然后再对数据库的大小进行修改


    /* Expire keys by random sampling. Not required for slaves
     * as master will synthesize DELs for us. */
    // 如果服务器不是从服务器,那么执行主动过期键清除
    if (server.active_expire_enabled && server.masterhost == NULL)
        // 清除模式为 CYCLE_SLOW ,这个模式会尽量多清除过期键
        activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW);


    /* Perform hash tables rehashing if needed, but only if there are no
     * other processes saving the DB on disk. Otherwise rehashing is bad
     * as will cause a lot of copy-on-write of memory pages. */
    // 在没有 BGSAVE 或者 BGREWRITEAOF 执行时,对哈希表进行 rehash
    if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) {
        /* We use global counters so if we stop the computation at a given
         * DB we'll be able to start from the successive in the next
         * cron loop iteration. */
        static unsigned int resize_db = 0;
        static unsigned int rehash_db = 0;
        unsigned int dbs_per_call = REDIS_DBCRON_DBS_PER_CALL;
        unsigned int j;


        /* Don't test more DBs than we have. */
        // 设定要测试的数据库数量
        if (dbs_per_call > server.dbnum) dbs_per_call = server.dbnum;


        /* Resize */
        // 调整字典的大小
        for (j = 0; j < dbs_per_call; j++) {
            tryResizeHashTables(resize_db % server.dbnum);
            resize_db++;
        }


        /* Rehash */
        // 对字典进行渐进式 rehash
        if (server.activerehashing) {
            for (j = 0; j < dbs_per_call; j++) {
                int work_done = incrementallyRehash(rehash_db % server.dbnum);
                rehash_db++;
                if (work_done) {
                    /* If the function did some work, stop here, we'll do
                     * more at the next cron loop. */
                    break;
                }
            }
        }
    }
}


猜你喜欢

转载自blog.csdn.net/sysucph/article/details/80780710