深入剖析Redis RDB持久化机制 (2)

Rdb.c:31

int rdbSaveLen(FILE *fp, uint32_t len) { unsigned char buf[2]; int nwritten; if (len < (1<<6)) { /* Save a 6 bit len */ 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 */ 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 */ 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; }

也许你发现了,上边的表格中只有3种,还有一种哪去了呢?

把这种特别放开是因为这种比较特殊

类型位表示   类型整型表示   字节后6位含义   类型解析  
11   3   编码类型   如果字符串是通过编码后存储的,则存储长度的类型的位表示为11,然后根据后6位的编码类型来确定怎样读取和解析接下来的数据  

是不是觉得这种长度类型很奇怪,为什么要这样做?

Redis在两种情况下需要对存储的内容进行编码

1.把字符串转成整数存储

比如:‘-100’需要4个字节存储,转换整数只需要一个字节

相关函数rdbTryIntegerEncoding(rdb.c:88)

2.使用lzf算法压缩字符串

相关函数lzf_compress(lzf_c.c:99),lzf的算法解释见lzf字符串压缩算法

当redis使用这两种编码对字符串进行编码时,在读取时需要区分改字符串有没有被编码过,对编码过的字符串需要特别处理,因为长度信息是存储在字符串的前面得,所以可以通过在存储长度的位置上加入编码类型的信息。

我们来看看相关代码

Rdb.c:557

uint32_t rdbLoadLen(FILE *fp, int *isencoded) { unsigned char buf[2]; uint32_t len; int type; if (isencoded) *isencoded = 0; if (fread(buf,1,1,fp) == 0) return REDIS_RDB_LENERR; type = (buf[0]&0xC0)>>6; if (type == REDIS_RDB_6BITLEN) { /* Read a 6 bit len */ return buf[0]&0x3F; } else if (type == REDIS_RDB_ENCVAL) { /* Read a 6 bit len encoding type */ if (isencoded) *isencoded = 1; return buf[0]&0x3F; } else if (type == REDIS_RDB_14BITLEN) { /* Read a 14 bit len */ if (fread(buf+1,1,1,fp) == 0) return REDIS_RDB_LENERR; return ((buf[0]&0x3F)<<8)|buf[1]; } else { /* Read a 32 bit len */ if (fread(&len,4,1,fp) == 0) return REDIS_RDB_LENERR; return ntohl(len); } }

我们可以看到,在读取rdb文件时,当发现长度类型是REDIS_RDB_ENCVAL,把编码类型返回。

我们来看看知道编码类型后的处理

Rdb.c:633

robj *rdbGenericLoadStringObject(FILE*fp, int encode) { int isencoded; uint32_t len; sds val; len = rdbLoadLen(fp,&isencoded); if (isencoded) { switch(len) { case REDIS_RDB_ENC_INT8: case REDIS_RDB_ENC_INT16: case REDIS_RDB_ENC_INT32: return rdbLoadIntegerObject(fp,len,encode); case REDIS_RDB_ENC_LZF: return rdbLoadLzfStringObject(fp); default: redisPanic("Unknown RDB encoding type"); } } if (len == REDIS_RDB_LENERR) return NULL; val = sdsnewlen(NULL,len); if (len && fread(val,len,1,fp) == 0) { sdsfree(val); return NULL; } return createObject(REDIS_STRING,val); }

读取长度

如果长度类型是有编码信息的,则根据编码类型进行读取

如果长度类型是有效长度,则根据长度信息读取字符串

REDIS_EXPIRETIME类型

如果一个key被expire设置过,那么在该key与value的前面会有一个REDIS_EXPIRETIME类型与其对应的值。

REDIS_EXPIRETIME类型对应的值是过期时间点的timestamp

REDIS_EXPIRETIME类型与其值是可选的,不是必须的,只有被expire设置过的key才有这个值

假设有一个key被expire命令设置过,把这REDIS_EXPIRETIME类型代入到上边的rdb文件的格式中,那么rdb文件的整体格式变成为:

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

数据类型

数据类型主要有以下类型:

REDIS_STRING类型

REDIS_LIST类型

REDIS_SET类型

REDIS_ZSET类型

REDIS_HASH类型

REDIS_VMPOINTER类型

REDIS_HASH_ZIPMAP类型

REDIS_LIST_ZIPLIST类型

REDIS_SET_INTSET类型

REDIS_ZSET_ZIPLIST类型

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

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