|
最近接觸了一下Redis數(shù)據(jù),出于好奇看了下它的源碼,覺得這是一個值得一讀的開源項目。關(guān)于Redis的源碼分析,已經(jīng)有很多網(wǎng)友寫了各種分析筆記,而且也有相關(guān)的書籍《Redis設(shè)計與實現(xiàn)》,因此我覺得完整的寫一系列的博客就沒有必要了,這里主要記錄一些個人覺得有意思或者是值得了解的東西(之前面試也有問到一些問題,如果我早一點接觸這些東西的話,可以回答的更好)。 如果對Redis源碼有興趣的話,可以先看一看1.0 Beta版的代碼,非常的簡短,對一些基本的東西有一個大致的了解之后再選一個新的穩(wěn)定版本的源碼進行閱讀和學習。 1. 空數(shù)組 對于結(jié)構(gòu)體成員中大小不確定的地方,可以考慮放一個空數(shù)組到結(jié)構(gòu)體的末尾,這樣通過動態(tài)內(nèi)存分配,就可以合理的設(shè)置空間了,當然需要一個成員記錄元素的個數(shù)。 SDS是Redis封裝的一個字符串類型,因為字符串的長度需要動態(tài)的控制,所以就用了空數(shù)組這個小技巧。 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[]; // 空數(shù)組
};
sds sdsnewlen(const void *init, size_t initlen) {
struct sdshdr *sh;
// 額外的空間大小: 字符串長度initlen,以及用于填充空字符的1字節(jié)
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
| // 跳躍表節(jié)點的定義
typedef struct zskiplistNode {
robj *obj;
double score;
struct zskiplistNode *backward;
struct zskiplistLevel {
struct zskiplistNode *forward;
unsigned int span;
} level[]; // 空數(shù)組
} 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數(shù)組
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) |
如果宏里面的代碼包含多條語句的時候,這里的作用就是將其封裝為一條語句,這樣即使放到?jīng)]有大括號的if后面也不會有問題了。在網(wǎng)上還看到了另一種do { } while (0)的使用場景(使代碼更優(yōu)美): 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. 調(diào)試信息打印 自定義assert函數(shù),當條件不通過時打印文件名、行號以及條件信息。 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| // #_e 將_e轉(zhuǎn)換為字符串
#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打印日志時,可以根據(jù)第一個參數(shù)進行過濾操作。 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 從配置文件讀取設(shè)定
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
| // 消除函數(shù)未使用參數(shù)的警告信息
#define REDIS_NOTUSED(V) ((void) V) |
待補充…… 參考 1. do…while(0)的妙用
本文地址: 程序人生 >> Redis編程小技巧拾遺 作者:代碼瘋子(Wins0n) 本站內(nèi)容如無聲明均屬原創(chuàng),轉(zhuǎn)載請保留作者信息與原文鏈接,謝謝!
|