发表于 2015-3-4 20:42

申请会员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流程图工具

Hmily 发表于 2015-3-5 12:04

请问如何提高Discuz使用中的Redis缓存效果?

发表于 2015-3-5 20:14

Hmily 发表于 2015-3-5 12:04
请问如何提高Discuz使用中的Redis缓存效果?

最好说具体场景吧。
因为没用过dz 所以不清楚dz中用到了 redis的那些特性

Hmily 发表于 2015-3-6 09:58

【开放注册公告】吾爱破解论坛2015年3月13日七周年开放注册公告
http://www.52pojie.cn/thread-335163-1-1.html

近期开放注册,看好时间自己到时间来注册吧。
页: [1]
查看完整版本: 申请会员ID:logbird