打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Redis编程小技巧拾遗
最近接触了一下Redis数据,出于好奇看了下它的源码,觉得这是一个值得一读的开源项目。关于Redis的源码分析,已经有很多网友写了各种分析笔记,而且也有相关的书籍《Redis设计与实现》,因此我觉得完整的写一系列的博客就没有必要了,这里主要记录一些个人觉得有意思或者是值得了解的东西(之前面试也有问到一些问题,如果我早一点接触这些东西的话,可以回答的更好)。
如果对Redis源码有兴趣的话,可以先看一看1.0 Beta版的代码,非常的简短,对一些基本的东西有一个大致的了解之后再选一个新的稳定版本的源码进行阅读和学习。
1. 空数组
对于结构体成员中大小不确定的地方,可以考虑放一个空数组到结构体的末尾,这样通过动态内存分配,就可以合理的设置空间了,当然需要一个成员记录元素的个数。
SDS是Redis封装的一个字符串类型,因为字符串的长度需要动态的控制,所以就用了空数组这个小技巧。
12345678910111213141516171819202122struct 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)中也有用到。
12345678910111213141516171819202122232425262728293031323334353637383940414243// 跳跃表节点的定义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)进行了包裹,如:
123456#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)的使用场景(使代码更优美):
12345678910111213141516171819202122bool 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函数,当条件不通过时打印文件名、行号以及条件信息。
123456789101112131415// #_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打印日志时,可以根据第一个参数进行过滤操作。
1234567891011121314151617#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. 其他
12// 消除函数未使用参数的警告信息#define REDIS_NOTUSED(V) ((void) V)
待补充……
参考
1. do…while(0)的妙用
本文地址: 程序人生 >> Redis编程小技巧拾遗
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
[redis]SDS和链表
Redis源码学习:字符串
Redis skip list结构分析
Redis 设计与实现 10:五大数据类型之有序集合
redis 5.0.7 源码阅读——动态字符串sds
电脑上键盘上的秘密(zsl)
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服