IT技术博客大学习 共学习 共进步
全部 移动开发 后端 数据库 AI 算法 安全 DevOps 前端 设计 开发者

Redis编程小技巧拾遗

程序人生 2015-01-25 21:36:02 累计浏览 3,804 次
本机暂存

   最近接触了一下Redis数据,出于好奇看了下它的源码,觉得这是一个值得一读的开源项目。关于Redis的源码分析,已经有很多网友写了各种分析笔记,而且也有相关的书籍《Redis设计与实现》,因此我觉得完整的写一系列的博客就没有必要了,这里主要记录一些个人觉得有意思或者是值得了解的东西(之前面试也有问到一些问题,如果我早一点接触这些东西的话,可以回答的更好)。

   如果对Redis源码有兴趣的话,可以先看一看1.0 Beta版的代码,非常的简短,对一些基本的东西有一个大致的了解之后再选一个新的稳定版本的源码进行阅读和学习。

   1. 空数组

   对于结构体成员中大小不确定的地方,可以考虑放一个空数组到结构体的末尾,这样通过动态内存分配,就可以合理的设置空间了,当然需要一个成员记录元素的个数。

   SDS是Redis封装的一个字符串类型,因为字符串的长度需要动态的控制,所以就用了空数组这个小技巧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct sdshdr {
    unsigned int len;
    unsigned int free;
    char buf[];     // 空数组
};
 
sds sdsnewlen(const void *init, size_t initlen) {
    struct sdshdr *sh;
    // 额外的空间大小: 字符串长度initlen,以及用于填充空字符的1字节
    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;
    if (initlen && init)
        memcpy(sh->buf, init, initlen);
    sh->buf[initlen] = '\0';
    return (char*)sh->buf;
}

   同样的技巧在跳跃表(skiplist)中也有用到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 跳跃表节点的定义
typedef struct zskiplistNode {
    robj *obj;
    double score;
    struct zskiplistNode *backward;
    struct zskiplistLevel {
        struct zskiplistNode *forward;
        unsigned int span;
    } level[];      // 空数组
} zskiplistNode;
 
// 跳跃表的定义
typedef struct zskiplist {
    struct zskiplistNode *header, *tail;
    unsigned long length;
    int level;
} zskiplist;
 
zskiplistNode *zslCreateNode(int level, double score, robj *obj) {
    // 通过level计算出额外需要的空间大小
    zskiplistNode *zn = zmalloc(sizeof(*zn)+level*sizeof(struct zskiplistLevel));
    zn->score = score;
    zn->obj = obj;
    return zn;
}
 
zskiplist *zslCreate(void) {
    int j;
    zskiplist *zsl;
 
    zsl = zmalloc(sizeof(*zsl));
    zsl->level = 1;
    zsl->length = 0;
    zsl->header = zslCreateNode(ZSKIPLIST_MAXLEVEL,0,NULL);
    // 初始化level数组
    for (j = 0; j < ZSKIPLIST_MAXLEVEL; j++) {
        zsl->header->level[j].forward = NULL;
        zsl->header->level[j].span = 0;
    }
    zsl->header->backward = NULL;
    zsl->tail = NULL;
    return zsl;
}

   2. 宏定义中使用do while(0)

   Redis中宏定义中的很多地方都使用了do { } while (0)进行了包裹,如:

1
2
3
4
5
6
#define dictSetVal(d, entry, _val_) do { \
    if ((d)->type->valDup) \
        entry->v.val = (d)->type->valDup((d)->privdata, _val_); \
    else \
        entry->v.val = (_val_); \
} while(0)

   如果宏里面的代码包含多条语句的时候,这里的作用就是将其封装为一条语句,这样即使放到没有大括号的if后面也不会有问题了。在网上还看到了另一种do { } while (0)的使用场景(使代码更优美):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
bool foobar()
{
    int *p = new int[10];
    bool bOk = true;
 
    do
    {
        bOk = func1();
        if(!bOk) break;
 
        bOk = func2();
        if(!bOk) break;
 
        bOk = func3();
        if(!bOk) break;
 
        // ..........
    }while(0);
 
    delete[] p;
    return bOk;
}

   3. 调试信息打印

   自定义assert函数,当条件不通过时打印文件名、行号以及条件信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// #_e 将_e转换为字符串
#define redisAssert(_e) ((_e)?(void)0 : (_redisAssert(#_e,__FILE__,__LINE__),_exit(1)))
 
void _redisAssert(char *estr, char *file, int line) {
    bugReportStart();
    redisLog(REDIS_WARNING,"=== ASSERTION FAILED ===");
    redisLog(REDIS_WARNING,"==> %s:%d '%s' is not true",file,line,estr);
#ifdef HAVE_BACKTRACE
    server.assert_failed = estr;
    server.assert_file = file;
    server.assert_line = line;
    redisLog(REDIS_WARNING,"(forcing SIGSEGV to print the bug report.)");
#endif
    *((char*)-1) = 'x';
}

   另外redisLog打印日志时,可以根据第一个参数进行过滤操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#define REDIS_DEBUG 0
#define REDIS_VERBOSE 1
#define REDIS_NOTICE 2
#define REDIS_WARNING 3
 
void redisLog(int level, const char *fmt, ...) {
    va_list ap;
    char msg[REDIS_MAX_LOGMSG_LEN];
    // server.verbosity 从配置文件读取设定
    if ((level&0xff) < server.verbosity) return;
 
    va_start(ap, fmt);
    vsnprintf(msg, sizeof(msg), fmt, ap);
    va_end(ap);
 
    redisLogRaw(level,msg);
}

   4. 其他

1
2
// 消除函数未使用参数的警告信息
#define REDIS_NOTUSED(V) ((void) V)

   待补充……

   参考

   1. do…while(0)的妙用

同分类推荐文章

  1. 使用deepseek进行Oracle恢复,引起重大故障 (2026-06-22 10:56:00)
  2. 接手一个只差临门一脚的数据库恢复 (2026-06-18 00:13:09)
  3. 我做了一个 AI 版的 StarRocks 升级风险扫描工具,直接帮我定位到一个风险 (2026-06-15 01:00:00)

查看更多 数据库 文章 →

建议继续学习

  1. redis源代码分析 - persistence (累计阅读 32,226)
  2. 红黑树并没有我们想象的那么难(上) (累计阅读 21,492)
  3. 为什么算法这么难? (累计阅读 12,393)
  4. Redis消息队列的若干实现方式 (累计阅读 12,085)
  5. 浅谈MySQL索引背后的数据结构及算法 (累计阅读 11,896)
  6. 加州求职记 (累计阅读 11,560)
  7. 海量数据面试题举例 (累计阅读 11,111)
  8. Facebook 网站架构 (累计阅读 11,107)
  9. 基于Redis构建系统的经验和教训 (累计阅读 10,518)
  10. 谷歌(Google)2011年校园招聘笔试题 (累计阅读 9,571)