最近好几次用到redis,但是一直没有时间来好好学习下redis的原理,打算最近花一个月的空余时间来整理学习下redis的源码。redis本身非常简洁,下载源码直接make就ok了,而且似乎没有依赖第三方库什么的。先从基本的数据结构开始把,主要参考了http://www.redisbook.com/en/latest/index.html#, 非常赞的关于redis 的站点,作者huangz1990对redis的分析非常清楚易懂,作者提供的有详细注释的源代码地址:https://github.com/huangz1990/annotated_redis_source。在此基础上我读读源码。好久不搞c语言了,看起来真陌生。先从基本数据结构看起吧,首先分析sds.c文件。
1.sdshdr结构
- // sds 类型
- typedef char *sds;
- // sdshdr 结构
- struct sdshdr {
- // buf 已占用长度
- int len;
- // buf 剩余可用长度
- int free;
- // 实际保存字符串数据的地方
- char buf[];
- };
看来sdshdr结构还是挺简单的,主要用来管理字符串,之所以另外设计出一个结构,作者自有用意了,管理字符串比原生的字符串方便高效许多。
这样我们如果构建一个字符串“hello world”, 那么在sdshdr结构体中表示起来就是这个样子的了:
- struct sdshdr {
- len = 11;
- free = 0;
- buf = "hello world\0"; // buf 的实际长度为 len + 1
- };
2.sdsnew构建字符串
构建字符串的函数sdsnew如下:- /*
- * 根据给定初始化值 init ,创建 sds
- * 如果 init 为 NULL ,那么创建一个 buf 内只包含 \0 终结符的 sds
- *
- * T = O(N)
- */
- sds sdsnew(const char *init) {
- size_t initlen = (init == NULL) ? 0 : strlen(init);
- return sdsnewlen(init, initlen);
- }
- /*
- * 创建一个指定长度的 sds
- * 如果给定了初始化值 init 的话,那么将 init 复制到 sds 的 buf 当中
- *
- * T = O(N)
- */
- sds sdsnewlen(const void *init, size_t initlen) {
- struct sdshdr *sh;
- // 有 init ?
- // O(N)
- if (init) {
- 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;
- // 如果给定了 init 且 initlen 不为 0 的话
- // 那么将 init 的内容复制至 sds buf
- // O(N)
- if (initlen && init)
- memcpy(sh->buf, init, initlen);
- // 加上终结符
- sh->buf[initlen] = '\0';
- // 返回 buf 而不是整个 sdshdr
- return (char*)sh->buf;
- }
3.sdscatsds 字符串扩展
使用sdshdr结构体,在append字符串的时候就非常方便了。如果我们在“hello world”后append“ again!“,则sdshdr会更新为下面这个样子:
- struct sdshdr {
- len = 18;
- free = 18;
- buf = "hello world again!\0 "; // 空白的地方为预分配空间,共 18 + 18 + 1 个字节
- }
append操作在函数sdscat中实现,而sdscat又是调用sdscatlen中实现。
- /*
- * 将一个 char 数组拼接到 sds 末尾
- *
- * T = O(N)
- */
- sds sdscat(sds s, const char *t) {
- return sdscatlen(s, t, strlen(t));
- }
- /*
- * 按长度 len 扩展 sds ,并将 t 拼接到 sds 的末尾
- *
- * T = O(N)
- */
- sds sdscatlen(sds s, const void *t, size_t len) {
- struct sdshdr *sh;
- size_t curlen = sdslen(s);
- // O(N)
- s = sdsMakeRoomFor(s,len);
- if (s == NULL) return NULL;
- // 复制
- // O(N)
- memcpy(s+curlen, t, len);
- // 更新 len 和 free 属性
- // O(1)
- sh = (void*) (s-(sizeof(struct sdshdr)));
- sh->len = curlen+len;
- sh->free = sh->free-len;
- // 终结符
- // O(1)
- s[curlen+len] = '\0';
- return s;
- }
- sds sdsMakeRoomFor(
- sds s,
- size_t addlen // 需要增加的空间长度
- )
- {
- struct sdshdr *sh, *newsh;
- size_t free = sdsavail(s); //根据sds找出sdshdr结构体指针位置,并得到结构体free字段的值
- size_t len, newlen;
- // 剩余空间可以满足需求,无须扩展
- if (free >= addlen) return s;
- sh = (void*) (s-(sizeof(struct sdshdr)));
- // 目前 buf 长度
- len = sdslen(s);
- // 新 buf 长度
- newlen = (len+addlen);
- // 如果新 buf 长度小于 SDS_MAX_PREALLOC 长度
- // 那么将 buf 的长度设为新 buf 长度的两倍
- if (newlen < SDS_MAX_PREALLOC)
- newlen *= 2;
- else
- newlen += SDS_MAX_PREALLOC;
- // 扩展长度
- newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);
- if (newsh == NULL) return NULL;
- newsh->free = newlen - len;
- return newsh->buf;
- }
4.sdsclear 清除
清除sds的函数为sdsclear,代码如下:
- void sdsclear(sds s) {
- struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); //获取sdshdr结构体地址
- sh->free += sh->len; //free加上字符串长度
- sh->len = 0; //len设为0
- sh->buf[0] = '\0'; //buf第0个元素设为\0
- }
- struct sdshdr {
- len = 0;
- free = 11;
- buf = "\0ello world\0";
- };