本文共 12972 字,大约阅读时间需要 43 分钟。
关于这两者的区别,网上有很多资料,这里我只想补充下自己理解的两个比较核心的点:
分析redis的rdb持久化过程直接从bgsaveCommand命令的执行过程开始分析
void bgsaveCommand(redisClient *c) { // 不能重复执行 BGSAVE if (server.rdb_child_pid != -1) { addReplyError(c,"Background save already in progress"); // 不能在 BGREWRITEAOF 正在运行时执行 } else if (server.aof_child_pid != -1) { addReplyError(c,"Can't BGSAVE while AOF log rewriting is in progress"); // 执行 BGSAVE } else if (rdbSaveBackground(server.rdb_filename) == REDIS_OK) { addReplyStatus(c,"Background saving started"); } else { addReply(c,shared.err); }}在rdbSaveBackground内部执行了fork子进程开始进行rdb的持久化操作,核心逻辑在执行rdbSave(filename)的命令。
int rdbSaveBackground(char *filename) { pid_t childpid; long long start; // 如果 BGSAVE 已经在执行,那么出错 if (server.rdb_child_pid != -1) return REDIS_ERR; // 记录 BGSAVE 执行前的数据库被修改次数 server.dirty_before_bgsave = server.dirty; // 最近一次尝试执行 BGSAVE 的时间 server.lastbgsave_try = time(NULL); // fork() 开始前的时间,记录 fork() 返回耗时用 start = ustime(); if ((childpid = fork()) == 0) { int retval; /* Child */ // 关闭网络连接 fd closeListeningSockets(0); // 设置进程的标题,方便识别 redisSetProcTitle("redis-rdb-bgsave"); // 执行保存操作 retval = rdbSave(filename); // 打印 copy-on-write 时使用的内存数 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); } // 省略非核心的逻辑 return REDIS_OK;}整个生成rdb文件的核心,整体逻辑如下
整个写入数据是将redis内存中的数据原封不动的写入到rdb文件当中,整个写入过程按照以下顺序进行执行:
整个过程中我们发现redis就是把实际内存数据库的数据dump到rdb文件当中
/* * 将数据库保存到磁盘上。 * * 保存成功返回 REDIS_OK ,出错/失败返回 REDIS_ERR 。 */int rdbSave(char *filename) { dictIterator *di = NULL; dictEntry *de; char tmpfile[256]; char magic[10]; int j; long long now = mstime(); FILE *fp; 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; } // 初始化 I/O rioInitWithFile(&rdb,fp); // 设置校验和函数 if (server.rdb_checksum) rdb.update_cksum = rioGenericUpdateChecksum; // 写入 RDB 版本号 snprintf(magic,sizeof(magic),"REDIS%04d",REDIS_RDB_VERSION); if (rdbWriteRaw(&rdb,magic,9) == -1) goto werr; // 遍历所有数据库 for (j = 0; j < server.dbnum; j++) { // 指向数据库 redisDb *db = server.db+j; // 指向数据库键空间 dict *d = db->dict; // 跳过空数据库 if (dictSize(d) == 0) continue; // 创建键空间迭代器 di = dictGetSafeIterator(d); if (!di) { fclose(fp); return REDIS_ERR; } /* Write the SELECT DB opcode * * 写入 DB 选择器 */ if (rdbSaveType(&rdb,REDIS_RDB_OPCODE_SELECTDB) == -1) goto werr; if (rdbSaveLen(&rdb,j) == -1) goto werr; /* Iterate this DB writing every entry * * 遍历数据库,并写入每个键值对的数据 */ while((de = dictNext(di)) != NULL) { sds keystr = dictGetKey(de); robj key, *o = dictGetVal(de); long long expire; // 根据 keystr ,在栈中创建一个 key 对象 initStaticStringObject(key,keystr); // 获取键的过期时间 expire = getExpire(db,&key); // 保存键值对数据 if (rdbSaveKeyValuePair(&rdb,&key,o,expire,now) == -1) goto werr; } dictReleaseIterator(di); } di = NULL; /* So that we don't release it again on error. */ /* EOF opcode * * 写入 EOF 代码 */ if (rdbSaveType(&rdb,REDIS_RDB_OPCODE_EOF) == -1) goto werr; /* * CRC64 校验和。 * * 如果校验和功能已关闭,那么 rdb.cksum 将为 0 , * 在这种情况下, RDB 载入时会跳过校验和检查。 */ cksum = rdb.cksum; memrev64ifbe(&cksum); rioWrite(&rdb,&cksum,8); /* Make sure data will not remain on the OS's output buffers */ // 冲洗缓存,确保数据已写入磁盘 if (fflush(fp) == EOF) goto werr; if (fsync(fileno(fp)) == -1) goto werr; if (fclose(fp) == EOF) goto werr; /* * 使用 RENAME ,原子性地对临时文件进行改名,覆盖原来的 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; // 记录最后一次完成 SAVE 的时间 server.lastsave = time(NULL); // 记录最后一次执行 SAVE 的状态 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;}将键值对的键、值、过期时间和类型写入到 RDB 中
/* * 将键值对的键、值、过期时间和类型写入到 RDB 中。 * * 出错返回 -1 。 * * On success if the key was actually saved 1 is returned, otherwise 0 * is returned (the key was already expired). * * 成功保存返回 1 ,当键已经过期时,返回 0 。 */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; } /* Save type, key, value * * 保存类型,键,值 */ if (rdbSaveObjectType(rdb,val) == -1) return -1; if (rdbSaveStringObject(rdb,key) == -1) return -1; if (rdbSaveObject(rdb,val) == -1) return -1; return 1;}将键值对的值类型写入到 rdb 中
/* * 将对象 o 的类型写入到 rdb 中 */int rdbSaveObjectType(rio *rdb, robj *o) { switch (o->type) { case REDIS_STRING: return rdbSaveType(rdb,REDIS_RDB_TYPE_STRING); case REDIS_LIST: if (o->encoding == REDIS_ENCODING_ZIPLIST) return rdbSaveType(rdb,REDIS_RDB_TYPE_LIST_ZIPLIST); else if (o->encoding == REDIS_ENCODING_LINKEDLIST) return rdbSaveType(rdb,REDIS_RDB_TYPE_LIST); else redisPanic("Unknown list encoding"); case REDIS_SET: if (o->encoding == REDIS_ENCODING_INTSET) return rdbSaveType(rdb,REDIS_RDB_TYPE_SET_INTSET); else if (o->encoding == REDIS_ENCODING_HT) return rdbSaveType(rdb,REDIS_RDB_TYPE_SET); else redisPanic("Unknown set encoding"); case REDIS_ZSET: if (o->encoding == REDIS_ENCODING_ZIPLIST) return rdbSaveType(rdb,REDIS_RDB_TYPE_ZSET_ZIPLIST); else if (o->encoding == REDIS_ENCODING_SKIPLIST) return rdbSaveType(rdb,REDIS_RDB_TYPE_ZSET); else redisPanic("Unknown sorted set encoding"); case REDIS_HASH: if (o->encoding == REDIS_ENCODING_ZIPLIST) return rdbSaveType(rdb,REDIS_RDB_TYPE_HASH_ZIPLIST); else if (o->encoding == REDIS_ENCODING_HT) return rdbSaveType(rdb,REDIS_RDB_TYPE_HASH); else redisPanic("Unknown hash encoding"); default: redisPanic("Unknown object type"); } return -1; /* avoid warning */}将给定的字符串对象 obj 保存到 rdb 中,我们的key就是通过这个方法保存的
/* * 将给定的字符串对象 obj 保存到 rdb 中。 * * 函数返回 rdb 保存字符串对象所需的字节数。 * * p.s. 代码原本的注释 rdbSaveStringObjectRaw() 函数已经不存在了。 */int rdbSaveStringObject(rio *rdb, robj *obj) { /* Avoid to decode the object, then encode it again, if the * object is already integer encoded. */ // 尝试对 INT 编码的字符串进行特殊编码 if (obj->encoding == REDIS_ENCODING_INT) { return rdbSaveLongLongAsStringObject(rdb,(long)obj->ptr); // 保存 STRING 编码的字符串 } else { redisAssertWithInfo(NULL,obj,sdsEncodedObject(obj)); return rdbSaveRawString(rdb,obj->ptr,sdslen(obj->ptr)); }}将给定对象 o 保存到 rdb 中。
/* * 将给定对象 o 保存到 rdb 中。 * * 保存成功返回 rdb 保存该对象所需的字节数 ,失败返回 0 。 * * p.s.上面原文注释所说的返回值是不正确的 */int rdbSaveObject(rio *rdb, robj *o) { int n, nwritten = 0; // 保存字符串对象 if (o->type == REDIS_STRING) { /* Save a string value */ if ((n = rdbSaveStringObject(rdb,o)) == -1) return -1; nwritten += n; // 保存列表对象 } else if (o->type == REDIS_LIST) { /* Save a list value */ if (o->encoding == REDIS_ENCODING_ZIPLIST) { size_t l = ziplistBlobLen((unsigned char*)o->ptr); // 以字符串对象的形式保存整个 ZIPLIST 列表 if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1; nwritten += n; } else if (o->encoding == REDIS_ENCODING_LINKEDLIST) { list *list = o->ptr; listIter li; listNode *ln; if ((n = rdbSaveLen(rdb,listLength(list))) == -1) return -1; nwritten += n; // 遍历所有列表项 listRewind(list,&li); while((ln = listNext(&li))) { robj *eleobj = listNodeValue(ln); // 以字符串对象的形式保存列表项 if ((n = rdbSaveStringObject(rdb,eleobj)) == -1) return -1; nwritten += n; } } else { redisPanic("Unknown list encoding"); } // 保存集合对象 } else if (o->type == REDIS_SET) { /* Save a set value */ if (o->encoding == REDIS_ENCODING_HT) { dict *set = o->ptr; dictIterator *di = dictGetIterator(set); dictEntry *de; if ((n = rdbSaveLen(rdb,dictSize(set))) == -1) return -1; nwritten += n; // 遍历集合成员 while((de = dictNext(di)) != NULL) { robj *eleobj = dictGetKey(de); // 以字符串对象的方式保存成员 if ((n = rdbSaveStringObject(rdb,eleobj)) == -1) return -1; nwritten += n; } dictReleaseIterator(di); } else if (o->encoding == REDIS_ENCODING_INTSET) { size_t l = intsetBlobLen((intset*)o->ptr); // 以字符串对象的方式保存整个 INTSET 集合 if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1; nwritten += n; } else { redisPanic("Unknown set encoding"); } // 保存有序集对象 } else if (o->type == REDIS_ZSET) { /* Save a sorted set value */ if (o->encoding == REDIS_ENCODING_ZIPLIST) { size_t l = ziplistBlobLen((unsigned char*)o->ptr); // 以字符串对象的形式保存整个 ZIPLIST 有序集 if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1; nwritten += n; } else if (o->encoding == REDIS_ENCODING_SKIPLIST) { zset *zs = o->ptr; dictIterator *di = dictGetIterator(zs->dict); dictEntry *de; if ((n = rdbSaveLen(rdb,dictSize(zs->dict))) == -1) return -1; nwritten += n; // 遍历有序集 while((de = dictNext(di)) != NULL) { robj *eleobj = dictGetKey(de); double *score = dictGetVal(de); // 以字符串对象的形式保存集合成员 if ((n = rdbSaveStringObject(rdb,eleobj)) == -1) return -1; nwritten += n; // 成员分值(一个双精度浮点数)会被转换成字符串 // 然后保存到 rdb 中 if ((n = rdbSaveDoubleValue(rdb,*score)) == -1) return -1; nwritten += n; } dictReleaseIterator(di); } else { redisPanic("Unknown sorted set encoding"); } // 保存哈希表 } else if (o->type == REDIS_HASH) { /* Save a hash value */ if (o->encoding == REDIS_ENCODING_ZIPLIST) { size_t l = ziplistBlobLen((unsigned char*)o->ptr); // 以字符串对象的形式保存整个 ZIPLIST 哈希表 if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1; nwritten += n; } else if (o->encoding == REDIS_ENCODING_HT) { dictIterator *di = dictGetIterator(o->ptr); dictEntry *de; if ((n = rdbSaveLen(rdb,dictSize((dict*)o->ptr))) == -1) return -1; nwritten += n; // 迭代字典 while((de = dictNext(di)) != NULL) { robj *key = dictGetKey(de); robj *val = dictGetVal(de); // 键和值都以字符串对象的形式来保存 if ((n = rdbSaveStringObject(rdb,key)) == -1) return -1; nwritten += n; if ((n = rdbSaveStringObject(rdb,val)) == -1) return -1; nwritten += n; } dictReleaseIterator(di); } else { redisPanic("Unknown hash encoding"); } } else { redisPanic("Unknown object type"); } return nwritten;}
转载地址:http://hqxym.baihongyu.com/