最近接触了一下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)的妙用
本文地址: 程序人生 >> Redis编程小技巧拾遗
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!