Redis设计与实现(学习总结第一章)

Redis 和其他很多 key-value 数据库的不同之处在于,Redis 不仅支持简单的字符串键值对,它 还提供了一系列数据结构类型值,比如列表、哈希、集合和有序集,并在这些数据结构类型上 定义了一套强大的 API 。

1.1 简单动态字符串

Sds (Simple Dynamic String,简单动态字符串)是 Redis 底层所使用的字符串表示,它被用 在几乎所有的 Redis 模块中。
本章将对 sds 的实现、性能和功能等方面进行介绍,并说明 Redis 使用 sds 而不是传统 C 字符 串的原因。

1.1.1 sds 的用途

Sds 在 Redis 中的主要作用有以下两个:
1. 实现字符串对象(StringObject);
2. 在 Redis 程序内部用作 char* 类型的替代品;

1.1.2 Redis 中的字符串

在 C 语言中,字符串可以用一个 \0 结尾的 char 数组来表示。
比如说,hello world 在 C 语言中就可以表示为 “hello world\0” 。
这种简单的字符串表示在大多数情况下都能满足要求,但是,它并不能高效地支持长度计算和 追加(append)这两种操作: • 每次计算字符串长度(strlen(s))的复杂度为 θ(N) 。

对字符串进行 N 次追加,必定需要对字符串进行 N 次内存重分配(realloc)。
在 Redis 内部,字符串的追加和长度计算并不少见,而 APPEND 和 STRLEN 更是这两种操 作在 Redis 命令中的直接映射,这两个简单的操作不应该成为性能的瓶颈。
另外,Redis 除了处理 C 字符串之外,还需要处理单纯的字节数组,以及服务器协议等内容, 所以为了方便起见,Redis 的字符串表示还应该是二进制安全的:
程序不应对字符串里面保存 的数据做任何假设,数据可以是以 \0 结尾的 C 字符串,也可以是单纯的字节数组,或者其他 格式的数据。 考虑到这两个原因,Redis 使用 sds 类型替换了 C 语言的默认字符串表示:sds 既可以高效地 实现追加和长度计算,并且它还是二进制安全的。

sds 的实现

typedef char *sds;
struct sdshdr {
// buf 已占用长度 
    int len;
// buf 剩余可用长度 
    int free;
// 实际保存字符串数据的地方 
    char buf[];
};

1.1.4 sds 模块的 API

sds 模块基于 sds 类型和 sdshdr 结构提供了以下 API
这里写图片描述

1.1.5 小结

• Redis 的字符串表示为 sds ,而不是 C 字符串(以 \0 结尾的 char*)。 
• 对比 C 字符串,sds 有以下特性: – 可以高效地执行长度计算(strlen); 
– 可以高效地执行追加操作(append); – 二进制安全; 
• sds 会为追加操作进行优化:加快追加操作的速度,并降低内存分配的次数,
代价是多占 用了一些内存,而且这些内存不会被主动释放。

重要源码解析

sdslen

/* 获取字符串长度 */
static inline size_t sdslen(const sds s) {
    //  sizeof(struct sdshdr)的值为8
    /* 
        从后面我们可以看到sds指向sdshdr结构的buf[]字符数组,所以
        s-(sizeof(struct sdshdr))就是sdshdr结构的地址。
    */
    struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
    return sh->len;
}

sdsavail

/* 获取字符数组中的可用空间 */
static inline size_t sdsavail(const sds s) {
    //  sizeof(struct sdshdr)的值为8
    struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
    return sh->free;
}

sdsMakeRoomFor

 /* 确保sds中的可用空间大于或等于addlen,如果当前字符串可用空间不满足则重新配置空间 */
sds sdsMakeRoomFor(sds s, size_t addlen) {
    struct sdshdr *sh, *newsh;
    size_t free = sdsavail(s);
    size_t len, newlen;

    // 当前空间满足要求,直接返回
    if (free >= addlen) return s;
    len = sdslen(s);
    sh = (void*) (s-(sizeof(struct sdshdr)));
    // 重新分配空间时并不是分配刚刚好满足需求的空间,而是以其2倍的数量进行分配。
    //这点类似于STL中的vector
    newlen = (len+addlen);//(当前原有的长度+追加的长度)*2就是我们所需要的
    if (newlen < SDS_MAX_PREALLOC)
        newlen *= 2;
    else
        newlen += SDS_MAX_PREALLOC;
    // 调用zrealloc直接在原地进行扩展
    newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);
    if (newsh == NULL) return NULL;

    // 更新可用空间
    newsh->free = newlen - len;
    return newsh->buf;
}

在目前版本的 Redis 中,SDS_MAX_PREALLOC 的值为 1024 * 1024 ,也就是说,当大小小于 1MB 的字符串执行追加操作时,sdsMakeRoomFor 就为它们分配多于所需大小一倍的空间;当 字符串的大小大于 1MB ,那么 sdsMakeRoomFor 就为它们额外多分配 1MB 的空间。

/* 释放字符数组buf中的多余空间,使其刚好能存放当前字符数 */

sdsRemoveFreeSpace

sds sdsRemoveFreeSpace(sds s) {
    struct sdshdr *sh;

    sh = (void*) (s-(sizeof(struct sdshdr)));
    // 重新分配空间,使其刚好能存放当前的字符数量(sizeof(struct sdshdr)+sh->len+1)
    sh = zrealloc(sh, sizeof(struct sdshdr)+sh->len+1);
    // 重新分配后当前可用空间为0
    sh->free = 0;
    return sh->buf;
}

sdsIncrLen

void sdsIncrLen(sds s, int incr) {
    struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));

   // 判断参数incr是否合法,如果不合法说明数据已经发生错误
   if (incr >= 0)
       assert(sh->free >= (unsigned int)incr);
   else
       assert(sh->len >= (unsigned int)(-incr));
   // 当前长度增加incr
   sh->len += incr;
   // 可用空间减少incr
   sh->free -= incr;
   s[sh->len] = '\0';
}

sdsAllocSize

/* 获取sds实际分配的空间大小(包括最后的'\0'结束符) */
size_t sdsAllocSize(sds s) {
    struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));

    return sizeof(*sh)+sh->len+sh->free+1;
}

如果要研究更多的API实现大家可以去

http://blog.csdn.net/Xiejingfa/article/details/50972592

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值