申请会员ID:logbird
1、申 请 I D :logbird
2、个人邮箱:logbird@126.com
3、原创技术文章:Redis 2.8.9源码 - RDB 模块 - save 和 load处理流程
本人博客:http://my.oschina.net/fuckphp/blog本文代码可以在 src/rdb.h 和 src/rdb.c 两个文件中找到,设计其余文件的会注明。redis 中 rdb作为Redis实现持久化的一部分,主要用于将数据库中的内存数据,通过对不同类型的处理后直接导出在硬盘中,并且在需要的时候来恢复数据,Redis主从同步 主全量推送 到从的时候主要通过 发送rdb 文件来实现。redis在进行rdbSave时候的执行如图:http://static.oschina.net/uploads/space/2014/1201/142347_78o5_226106.pngrdbSave:(如下代码定义在 src/rdb.c 中):Redis中实现rdb操作的主函数,所有的流程逻辑都在此处
?
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
int rdbSave(char *filename) {
//字典迭代器
dictIterator *di = NULL;
dictEntry *de;
char tmpfile;
char magic;
int j;
long long now = mstime();
FILE *fp;
//创建rio对象
rio rdb;
uint64_t cksum;
//临时文件
snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid());
//创建一个临时文件
fp = fopen(tmpfile,"w");
if (!fp) {
redisLog(REDIS_WARNING, "Failed opening .rdb for saving: %s",
strerror(errno));
return REDIS_ERR;
}
//初始化 file rio
rioInitWithFile(&rdb,fp);
//设置校验和计算函数
if (server.rdb_checksum)
rdb.update_cksum = rioGenericUpdateChecksum;
//RDB版本
snprintf(magic,sizeof(magic),"REDIS%04d",REDIS_RDB_VERSION);
//临时文件中写入rdb版本号
if (rdbWriteRaw(&rdb,magic,9) == -1) goto werr;
//遍历每一个数据库
for (j = 0; j < server.dbnum; j++) {
//设置 当前遍历的数据库
redisDb *db = server.db+j;
//取到key space对应的字典
dict *d = db->dict;
if (dictSize(d) == 0) continue;
//创建一个安全迭代
di = dictGetSafeIterator(d);
//安全迭代失败返回错误
if (!di) {
fclose(fp);
return REDIS_ERR;
}
//写入当前执行rdb的数据库号
if (rdbSaveType(&rdb,REDIS_RDB_OPCODE_SELECTDB) == -1) goto werr;
//通过位运算压缩写入长度 详细见后文
if (rdbSaveLen(&rdb,j) == -1) goto werr;
//进行安全迭代
while((de = dictNext(di)) != NULL) {
//获取当前遍历的key val
sds keystr = dictGetKey(de);
robj key, *o = dictGetVal(de);
long long expire;
//初始化一个string类型的Redis Object
initStaticStringObject(key,keystr);
//获取key的过期时间
expire = getExpire(db,&key);
//存储 key value
if (rdbSaveKeyValuePair(&rdb,&key,o,expire,now) == -1) goto werr;
}
//释放安全迭代器
dictReleaseIterator(di);
}
di = NULL; ;
/* EOF opcode */
//标示rdb结束
if (rdbSaveType(&rdb,REDIS_RDB_OPCODE_EOF) == -1) goto werr;
//更新校验和,并写入校验和
cksum = rdb.cksum;
//翻转校验和
memrev64ifbe(&cksum);
rioWrite(&rdb,&cksum,8);
//将剩余没有 flush的数据 flush到硬盘上
if (fflush(fp) == EOF) goto werr;
if (fsync(fileno(fp)) == -1) goto werr;
if (fclose(fp) == EOF) goto werr; //使用临时文件替换rdb文件
if (rename(tmpfile,filename) == -1) {
redisLog(REDIS_WARNING,"Error moving temp DB file on the final destination: %s", strerror(errno));
unlink(tmpfile);
return REDIS_ERR;
}
redisLog(REDIS_NOTICE,"DB saved on disk");
server.dirty = 0;
//更新最后修改时间
server.lastsave = time(NULL);
//更新状态
server.lastbgsave_status = REDIS_OK;
return REDIS_OK;
werr:
fclose(fp);
unlink(tmpfile);
redisLog(REDIS_WARNING,"Write error saving DB on disk: %s", strerror(errno));
if (di) dictReleaseIterator(di);
return REDIS_ERR;
}
rdbSaveBackground:redis 执行rdbSave操作的时候,会阻塞主进程,这时候将无法继续响应客户端请求,对于一个数据库,这样是不可行的,所以Redis 会使用子进程来执行rdbSave操作?
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
44
int rdbSaveBackground(char *filename) {
pid_t childpid;
long long start;
if (server.rdb_child_pid != -1) return REDIS_ERR;
server.dirty_before_bgsave = server.dirty;
server.lastbgsave_try = time(NULL);
start = ustime();
//创建子进程,并复制父进程的进程空间
if ((childpid = fork()) == 0) {
int retval;
//子进程关闭继承的socket
closeListeningSockets(0);
//设置当前状态
redisSetProcTitle("redis-rdb-bgsave");
//执行 rdbSave 操作
retval = rdbSave(filename);
if (retval == REDIS_OK) {
size_t private_dirty = zmalloc_get_private_dirty();
if (private_dirty) {
redisLog(REDIS_NOTICE,
"RDB: %zu MB of memory used by copy-on-write",
private_dirty/(1024*1024));
}
}
exitFromChild((retval == REDIS_OK) ? 0 : 1);
} else {
/* Parent */
server.stat_fork_time = ustime()-start;
if (childpid == -1) {
server.lastbgsave_status = REDIS_ERR;
redisLog(REDIS_WARNING,"Can't save in background: fork: %s",
strerror(errno));
return REDIS_ERR;
}
redisLog(REDIS_NOTICE,"Background saving started by pid %d",childpid);
server.rdb_save_time_start = time(NULL);
server.rdb_child_pid = childpid;
updateDictResizePolicy();
return REDIS_OK;
}
return REDIS_OK; /* unreached */
}
另外一个比较重要的函数:?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val,
long long expiretime, long long now)
{
/* Save the expire time */
if (expiretime != -1) {
/* If this key is already expired skip it */
if (expiretime < now) return 0;
//如果存在有效期 并且未过期 写入类型 和 过期时间
if (rdbSaveType(rdb,REDIS_RDB_OPCODE_EXPIRETIME_MS) == -1) return -1;
if (rdbSaveMillisecondTime(rdb,expiretime) == -1) return -1;
}
//写入 type key 和 value
if (rdbSaveObjectType(rdb,val) == -1) return -1;
//保存key信息
if (rdbSaveStringObject(rdb,key) == -1) return -1;
//保存value对象
if (rdbSaveObject(rdb,val) == -1) return -1;
return 1;
}
redis在进行rdbLoad时候的执行如图:http://static.oschina.net/uploads/space/2014/1201/143142_br70_226106.pngrdbLoad :
?
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
int rdbLoad(char *filename) {
uint32_t dbid;
int type, rdbver;
redisDb *db = server.db+0;
char buf;
long long expiretime, now = mstime();
FILE *fp;
rio rdb;
//打开rdb文件
if ((fp = fopen(filename,"r")) == NULL) return REDIS_ERR;
rioInitWithFile(&rdb,fp);
//设置校验和函数
rdb.update_cksum = rdbLoadProgressCallback;
//这只每次处理的最大块
rdb.max_processing_chunk = server.loading_process_events_interval_bytes;
//读取redis版本
if (rioRead(&rdb,buf,9) == 0) goto eoferr;
buf = '\0';
//验证是否REDIS开头
if (memcmp(buf,"REDIS",5) != 0) {
fclose(fp);
redisLog(REDIS_WARNING,"Wrong signature trying to load DB from file");
errno = EINVAL;
return REDIS_ERR;
}
//验证版本 必须大于1 并且 比当前版本低
rdbver = atoi(buf+5);
if (rdbver < 1 || rdbver > REDIS_RDB_VERSION) {
fclose(fp);
redisLog(REDIS_WARNING,"Can't handle RDB format version %d",rdbver);
errno = EINVAL;
return REDIS_ERR;
}
//标记开始进行加载
startLoading(fp);
while(1) {
robj *key, *val;
expiretime = -1;
//读取当前行的类型
if ((type = rdbLoadType(&rdb)) == -1) goto eoferr;
//读取秒级别的过期时间
if (type == REDIS_RDB_OPCODE_EXPIRETIME) {
if ((expiretime = rdbLoadTime(&rdb)) == -1) goto eoferr;
//继续读取redis key的类型
if ((type = rdbLoadType(&rdb)) == -1) goto eoferr;
expiretime *= 1000;
} else if (type == REDIS_RDB_OPCODE_EXPIRETIME_MS) {
//读取毫秒级别的过期时间
if ((expiretime = rdbLoadMillisecondTime(&rdb)) == -1) goto eoferr;
//继续读取redis key的类型
if ((type = rdbLoadType(&rdb)) == -1) goto eoferr;
}
//如果结束则退出
if (type == REDIS_RDB_OPCODE_EOF)
break;
//选择切换db
if (type == REDIS_RDB_OPCODE_SELECTDB) {
if ((dbid = rdbLoadLen(&rdb,NULL)) == REDIS_RDB_LENERR)
goto eoferr;
if (dbid >= (unsigned)server.dbnum) {
redisLog(REDIS_WARNING,"FATAL: Data file was created with a Redis server configured to handle more than %d databases. Exiting\n", server.dbnum);
exit(1);
}
db = server.db+dbid;
continue;
}
//加载key
if ((key = rdbLoadStringObject(&rdb)) == NULL) goto eoferr;
//根据不同的类型的value加载value值
if ((val = rdbLoadObject(type,&rdb)) == NULL) goto eoferr;
//如果为redis 主 则忽略过期key,并将key val的引用计数减少
if (server.masterhost == NULL && expiretime != -1 && expiretime < now) {
decrRefCount(key);
decrRefCount(val);
continue;
}
//将key val加到指定的db中
dbAdd(db,key,val);
//如果存在过期时间 则设置过期
if (expiretime != -1) setExpire(db,key,expiretime);
//加入完成减少key的引用计数
decrRefCount(key);
}
//验证redis 版本,验证校验和
if (rdbver >= 5 && server.rdb_checksum) {
uint64_t cksum, expected = rdb.cksum;
if (rioRead(&rdb,&cksum,8) == 0) goto eoferr;
memrev64ifbe(&cksum);
if (cksum == 0) {
redisLog(REDIS_WARNING,"RDB file was saved with checksum disabled: no check performed.");
} else if (cksum != expected) {
redisLog(REDIS_WARNING,"Wrong RDB checksum. Aborting now.");
exit(1);
}
}
fclose(fp);
//标志结束
stopLoading();
return REDIS_OK;
eoferr: /* unexpected end of file is handled here with a fatal exit */
redisLog(REDIS_WARNING,"Short read or OOM loading DB. Unrecoverable error, aborting now.");
exit(1);
return REDIS_ERR; /* Just to avoid warning */
}
Redis会对不同类型的 redis object 进行不同的处理,将会在下一篇文章介绍每种类型的压缩以及处理方式。
Redis2.8.9源码 src/rdb.h src/rdb.c流程图工具
请问如何提高Discuz使用中的Redis缓存效果? Hmily 发表于 2015-3-5 12:04
请问如何提高Discuz使用中的Redis缓存效果?
最好说具体场景吧。
因为没用过dz 所以不清楚dz中用到了 redis的那些特性 【开放注册公告】吾爱破解论坛2015年3月13日七周年开放注册公告
http://www.52pojie.cn/thread-335163-1-1.html
近期开放注册,看好时间自己到时间来注册吧。
页:
[1]