众所周知redis的持久化有AOF和RDB两种方式,其中AOF类似于操作日志的方式保存数据,即所有的数据都是基于现有的操作纪录进行优化保存,文件体积相对较大,但可读性较好,理论上可以通过管道直接导入redis。相对RDB格式的生成较为复杂,是将某一个时间节点上的内存镜像的优化映射。
记得有这么一种说法是“内存是新时代的硬盘,硬盘是新时代的磁带。”Redis的RDB文件生成过程是这个理论的最优化实践!配置文件中有一个对应的save A B选项,配置的就是如果在A秒内B个键值被修改,那么就会激活bgsave方法(就版本对应的是save方法)。bgsave命令是通过调用系统中级别的fork命令,克隆一个与现有进程。由于redis的单进程特性,这个过程也就意味着制造了一个当前内存数据的完整镜像,在子进程中启动持久化。对应的还有一个save命令,它的区别在于不fork进程,直接在主进程中持久化。尽管我本人认为fork是一个很笨拙的方式,而且有切身经历的灾难,单我仍然认为这是一个代价最为低廉的方式。
上图是一个简单的rdb文件,正常情况下有几个部分组成:
- REDIS识别号
- 版本0002
- db编号标志
- db编号x00,从这个长度上看,理论上最大的切片数是xFF 总共256个
- 数据主体
- 下一个db编号标志+db编号
- 下一数据主体
- ……
- EOF结束符
数据主体的结构,首先是根据设置失效时间与否,标记一个timestamp时间戳记,然后再根据数据类型以及对应的数据体的描述:
- string: x00 + key长度+key + var 长度+var
- list: x0A+链表长+key长+key+ var1长+var1 + var2长+var2+…
- hash: x09 + key长+key+field1长+field 1 + var1长+var1 + field2长+field2+var2长+var2+…
- set: x0B+ key长+key + var1长+var1+var2长+var2+…
- sort set : x0C + key长+key + 排序+var1长+var1 + x03xC0 + score 1 +var2长+var2+x03xC0 + score2+…
对于数据主体的描述中长度的描述,rdb文件在打开了rdbcompression选项后,会对数据进行压缩存储,具体的压缩方式我个人觉得有点像UTF-8的字符编码格式。
int rdbSaveLen(FILE *fp, uint32_t len) { unsigned char buf[2]; int nwritten; if (len < (1<<6)) { /* Save a 6 bit len */ 6bit以下,用单个字节表示 buf[0] = (len&0xFF)|(REDIS_RDB_6BITLEN<<6); if (rdbWriteRaw(fp,buf,1) == -1) return -1; nwritten = 1; } else if (len < (1<<14)) { /* Save a 14 bit len */ 6~14个bit, 用01前导的2个字节表示 buf[0] = ((len>>8)&0xFF)|(REDIS_RDB_14BITLEN<<6); buf[1] = len&0xFF; if (rdbWriteRaw(fp,buf,2) == -1) return -1; nwritten = 2; } else { /* Save a 32 bit len */ 14~32个bit, 用10前导的总共5个字节表示 buf[0] = (REDIS_RDB_32BITLEN<<6); if (rdbWriteRaw(fp,buf,1) == -1) return -1; len = htonl(len); if (rdbWriteRaw(fp,&len,4) == -1) return -1; nwritten = 1+4; } return nwritten; }
大致上就这么多,下一步打算通过rdb的文件做一个基于db的切分迁移工具。