Redis之RDB持久化
来源:互联网 发布:追星必备软件 编辑:程序博客网 时间:2024/05/29 11:59
一、前言
Redis是key-value型的内存数据库,所有的数据都保存在内存中,如果一段断电或者服务器宕机那么数据将会全部丢失,这很显然满足不了很多业务场景的需求,比如我的Redis数据库中存放很多待执行的URL爬取任务,如果在运行过程中服务器突然断电,所有的数据及没有处理的任务都会丢失,因此为了解决这个问题Redis引入了RDB持久化功能,此外Redis还提供另外一种更高级的持久化功能AOF持久化,先来看下RDB持久化的实现机制等。
二、RDB持久化
2.1 RDB文件格式
在介绍持久化过程之前,我们来先看下RDB文件的格式
再看下key_value_pairs部分的结构,RDB文件中的key_value_pairs部分都保存了一个或以上数量的键值对,包括键值对的过期信息等,
2.2 RDB文件的创建与载入
Redis中有两个命令可以生成RDB文件,一个是SAVE,另外一个是BGSAVE
SAVE命令:SAVE命令会阻塞Redis服务器进程,指导RDB文件创建完毕,在这个期间服务器进程不能处理任何来自客户端的命令请求。
BGSAVE命令:BGSAVE命令会从服务器进程中派生出一个子进程,然后由子进程负责创建RDB文件,服务器进程继续处理命令请求
创建RDB文件过程由rdb.c/rdbSave函数完成
/* 后台进行rbd保存操作 */int rdbSaveBackground(char *filename) { pid_t childpid; long long start; if (server.rdb_child_pid != -1) return REDIS_ERR;// 距离上一次成功执行SAVE或者BGSAVE命令之后,服务器对数据库状态进行了多少次修改 server.dirty_before_bgsave = server.dirty; server.lastbgsave_try = time(NULL); // 获取当前系统时间 start = ustime(); //利用fork()创建子进程用来实现rdb的保存操作 //此时有2个进程在执行这段函数的代码,在子进行程返回的pid为0, //所以会执行下面的代码,在父进程中返回的代码为孩子的pid,不为0,所以执行else分支的代码 //在父进程中放返回-1代表创建子进程失败 if ((childpid = fork()) == 0) { //在这个if判断的代码就是在子线程中后执行的操作 int retval; /* Child */// 由于子进程会和父进程共享打开的Socket文件描述句柄,所以要在子进程中关闭其 closeListeningSockets(0); redisSetProcTitle("redis-rdb-bgsave"); //这个就是刚刚说的rdbSave()操作 // 开始执行save过程 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; server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024*1024*1024); /* GB per second. */ latencyAddSampleIfNeeded("fork",server.stat_fork_time/1000); 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 */}
/* 保存rdb数据库的内容到磁盘中 */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; }//初始化rbd和fp的初始操作,据此判断,后面关于rdb的操作都存入到fp这个文件中// 使用rio进行健壮的I/O读写,将文件句柄与读缓冲区联系起来 rioInitWithFile(&rdb,fp); if (server.rdb_checksum) rdb.update_cksum = rioGenericUpdateChecksum; 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 */ 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; initStaticStringObject(key,keystr); expire = getExpire(db,&key); //将里面的键值存入rdb中 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 */ if (rdbSaveType(&rdb,REDIS_RDB_OPCODE_EOF) == -1) goto werr; /* CRC64 checksum. It will be zero if checksum computation is disabled, the * loading code skips the check in this case. */ cksum = rdb.cksum; memrev64ifbe(&cksum); if (rioWrite(&rdb,&cksum,8) == 0) goto werr; /* 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; /* Use RENAME to make sure the DB file is changed atomically only * if the generate DB file is ok. */ 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://这里又用到了goto处理异常操作的代码 fclose(fp); unlink(tmpfile); redisLog(REDIS_WARNING,"Write error saving DB on disk: %s", strerror(errno)); if (di) dictReleaseIterator(di); return REDIS_ERR;}
在具体的文件写入过程中,作者使用了自己包装的rio包,实现带缓冲、健壮的读写文件,
首先使用rio进行健壮的I/O读写,将文件句柄与读缓冲区联系起来
// 使用rio进行健壮的I/O读写,将文件句柄与读缓冲区联系起来 rioInitWithFile(&rdb,fp);
/* 根据上面描述的方法,定义了FileRio */static const rio rioFileIO = { rioFileRead, rioFileWrite, rioFileTell, NULL, /* update_checksum */ 0, /* current checksum */ 0, /* bytes read or written */ 0, /* read/write chunk size */ { { NULL, 0 } } /* union for io-specific vars */};/* 初始化rio中的file变量 */void rioInitWithFile(rio *r, FILE *fp) { *r = rioFileIO; r->io.file.fp = fp; r->io.file.buffered = 0; r->io.file.autosync = 0;}
最终的实现由rio.h中的rioWrite函数实现带缓冲的读写
static inline size_t rioWrite(rio *r, const void *buf, size_t len) { while (len) { //判断当前操作字节长度是否超过最大长度 size_t bytes_to_write = (r->max_processing_chunk && r->max_processing_chunk < len) ? r->max_processing_chunk : len; //写入新的数据时,更新校验和 if (r->update_cksum) r->update_cksum(r,buf,bytes_to_write); //执行写方法 if (r->write(r,buf,bytes_to_write) == 0) return 0; buf = (char*)buf + bytes_to_write; len -= bytes_to_write; //操作字节数增加 r->processed_bytes += bytes_to_write; } return 1;}
阅读全文
0 0
- Redis持久化之RDB
- Redis之RDB持久化
- Redis 持久化之RDB
- Redis持久化之RDB
- Redis 持久化之 RDB
- redis持久化之----RDB(Redis DataBase )
- [Redis]Redis持久化之RDB快照
- redis数据库之rdb持久化
- redis源码分析之RDB持久化
- redis之RDB持久化策略
- Redis之十 RDB持久化
- Redis持久化之RDB(二)
- Redis 持久化之RDB(二)
- Redis之rdb快照持久化
- Redis的持久化之RDB方式
- Redis的持久化之RDB方式
- Redis 持久化之RDB和AOF
- Redis RDB持久化
- 用Navicat导入数据时报错,[Err] 2006
- Opencv中Mat中元素的值读取方法总结
- ASP.Net 开发中关于时间模糊匹配问题
- PHP iconv 解决utf-8和gb2312编码转换问题
- java转换数字以万为单位
- Redis之RDB持久化
- leetcode:Coin Change
- Android7.0中文文档 -- Space
- netty实现webSocket协议
- Java中的深拷贝、浅拷贝
- 由SPPnet有关解决深度网络输入必须固定的问题思考
- Centos 6.9 Install opensll1.1.0f
- 手机app开发遇到的问题总结
- okhttp3.0忽略https证书