深入剖析Redis RDB持久化机制

rdb是 redis保存内存数据到磁盘数据的其中一种方式(另一种是AOF)。Rdb的主要原理就是在某个时间点把内存中的所有数据的快照保存一份到磁盘上。在条 件达到时通过fork一个子进程把内存中的数据写到一个临时文件中来实现保存数据快照。在所有数据写完后再把这个临时文件用原子函数rename(2)重 命名为目标rdb文件。这种实现方式充分利用fork的copy on write。

另外一种是通过save命令主动触发保存数据快照,这种是阻塞式的,即不会通过生成子进程来进行数据集快照的保存。

相关配置 save <seconds> <changes>

经过多少秒且多少个key有改变就进行,可以配置多个,只要有一个满足就进行保存数据快照到磁盘

rdbcompression yes

保存数据到rdb文件时是否进行压缩,如果不想可以配置成’no’,默认是’yes’,因为压缩可以减少I/O,当然,压缩需要消耗一些cpu资源。

dbfilename dump.rdb

快照文件名

dir ./

快照文件所在的目录,同时也是AOF文件所在的目录

Rdb文件格式

[注:本节所说的类型,值在没有特别标明的情况下都是针对rdb文件来说的]

Rdb文件的整体格式

文件签名 | 版本号 | 类型 | 值 | 类型 | 值 | … | 类型 | 值

[注:竖线和空格是为了便于阅读而加入的,rdb文件中是没有竖线和空格分隔的]

文件签名是字符串:REDIS

版本号是字符串:0002

类型是指值的类型,redis值的类型有很多种,下边一一介绍

值是对应的类型下的值,不同类型的值格式不一样。这里的值包含了redis中的key与val。而不是单指redis中val。

REDIS_SELECTDB类型与REDIS_EOF类型

REDIS_SELECTDB类型:对应的值是redis db的编号,从0开始到比db数小1的数值。redis中可以配置db数,每个key只属于一个db。

存储redis db的编号时使用的是存储长度时使用的格式,为了尽量压缩rdb文件,存储长度使用的字节数是不一样的,具体见下边rdb中长度的存储

REDIS_EOF类型:没有对应的值。rdb文件的结束符。

把这REDIS_SELECTDB类型和REDIS_EOF类型代入到上边的rdb文件的格式中,那么rdb文件的整体格式变成为:

文件签名 | 版本号 | REDIS_SELECTDB类型 | db编号 | 类型 | 值 | … | REDIS_SELECTD 类型 | db编号 | 类型 | 值 | … | REDIS_EOF类型

每个db编号后边到下一个REDIS_SELECTDB类型出现之前的数据都是该db下边的key和value的数据

相关代码

Rdb.c:394

int rdbSave(char *filename) { … fp = fopen(tmpfile,"w"); if (!fp) { redisLog(REDIS_WARNING, "Failed saving the DB: %s", strerror(errno)); return REDIS_ERR; } if (fwrite("REDIS0002",9,1,fp) == 0) goto werr; for (j = 0; j < server.dbnum; j++) { … /* Write the SELECT DB opcode */ if (rdbSaveType(fp,REDIS_SELECTDB) == -1) goto werr; if (rdbSaveLen(fp,j) == -1) goto werr; /* Iterate this DB writing every entry */ while((de = dictNext(di)) != NULL) { … initStaticStringObject(key,keystr); expiretime = getExpire(db,&key); /* Save the expire time */ if (expiretime != -1) { /* If this key is already expired skip it */ if (expiretime < now) continue; if (rdbSaveType(fp,REDIS_EXPIRETIME) == -1) goto werr; if (rdbSaveTime(fp,expiretime) == -1) goto werr; } /* Save the key and associated value. This requires special * handling if the value is swapped out. */ if (!server.vm_enabled || o->storage == REDIS_VM_MEMORY || o->storage == REDIS_VM_SWAPPING) { int otype = getObjectSaveType(o); /* Save type, key, value */ if (rdbSaveType(fp,otype) == -1) goto werr; if (rdbSaveStringObject(fp,&key) == -1) goto werr; if (rdbSaveObject(fp,o) == -1) goto werr; } else { /* REDIS_VM_SWAPPED or REDIS_VM_LOADING */ robj *po; /* Get a preview of the object in memory */ po = vmPreviewObject(o); /* Save type, key, value */ if (rdbSaveType(fp,getObjectSaveType(po)) == -1) goto werr; if (rdbSaveStringObject(fp,&key) == -1) goto werr; if (rdbSaveObject(fp,po) == -1) goto werr; /* Remove the loaded object from memory */ decrRefCount(po); } } dictReleaseIterator(di); } /* EOF opcode */ if (rdbSaveType(fp,REDIS_EOF) == -1) goto werr; … } Rdb中长度的存储

Redis为了尽量压缩rdb文件真是费尽心思,先来看看redis为了压缩使用的长度存储。长度主要用在字符串长度,链表长度,hash表的大小存储上。

Redis把长度的存储分为四种,最左边字节的从左到右的前两位用于区分长度的存储类型。

类型位表示   类型整型表示   占用字节数   类型解析  
00   0   1   当长度能用6位表示使用此类型  
01   1   2   当长度不能用6位表示且能用14位表示使用此类型  
10   2   5   当长度不能用14位表示且能用32位表示使用此类型  

相关代码

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzzddp.html