redis RDB

简介

  1. RDB数据落地的2种方式。
  2. 3种触发RDB数据落地的方式。
  3. RDB的主要流程。
  4. object落地的序列化。
  5. RDB数据落地的互斥。

2种落地方式方式

  1. 自身进程阻塞式的进行数据落地,这种方式会使得进程阻塞,无法处理其它命令。
  2. fork子进程进行数据落地,父进程继续处理client的命令,但这个时候父进程会排斥其它落地的请求,任何RDB数据落地在子进程完成之前都会拒绝,同时也会拒绝部分AOF数据落地请求。

3种触发RDB数据落地的方式

关于数据恢复:数据恢复的逻辑比较简单,在redis启动时,判断指定目录下是否有rdb文件,有则通过rdb文件进行数据恢复。

  1. client通过命令触发RDB,SAVE和BGSAVE命令,分别对应上面的2种落地方式。
  2. 通过配置文件配置落地条件,在serverCron中定期检查条件是否满足,满足则进行数据落地,该种落地方式默认是fork子进程异步的数据落地。配置的格式如下:
save time_ivl dirty_num  // 格式
save 900  1  // 示例1
save 300 10 // 示例2
save 60 1000 // 示例3

配置的含义是,如果距离上次RDB数据落地的时间超过time_ivl且修改的数据的数量大于dirty_num,那么就需要再次RDB数据落地。如果有多个配置,则逐一比较,任何一个条件满足,则进行RDB数据落地。

  1. 关机前进行数据落地,这种方式通常都是自身进程来执行。

RDB的主要流程

  1. RDB数据落地调用的函数为:rdbSaveRio。redisdb的结构如下图,每个redisserver可以有多个db,每个db会有一个dict,存储键值对,其中键为sds字符串类型,值为object,具体的类型由object中的type字段指定。(其实还有expire_dict,存储的是具有过期属性的键和对应的到期时间)


    redis db的结构
  2. RDB过程主要伪码:

save(redis_version); // redis的版本号
save(save_info_aux_fields); // rdb的一些辅助字段
save(save_modules_aux); // rdb的辅助模块
for db in server.db_arr:
  save(RDB_OPCODE_SELECTDB); // 表示后续数据类型为db的下标
  save(db_index); // 存储db的下标
  save(RDB_OPCODE_RESIZEDB); // 表示后续的数据是db的空间大小
  save(db.db_size); // db的键的dict的size
  save(db.expire_size); // db的设置了过期时间的键dict的size
  for it in db.db_dict:
    save(it.key); // sds db的dict的键
    save(it.value); // object db的dict的值
  for it in db.expire_dict:
    save(it.key); // sds expire_dict中的键
    save(it.value); // int expire_dict中到期时间

上面的dict中的key-value在内存中通常是非连续的内存,要进行序列化才能写入到文件中。

object的序列化

1. 主要的object

在了解序列化之前,需要先了解redis中主要有哪些object。


redis中的object
2. object的属性。

redis中,object有2个类型字段,一个是type,一个是encoding,其中type表示上层业务使用对应的类型,encoding表示底层的实现。以Hash为例,在业务层表示的是该结构是键值对类型的数据结构,必须实现键值对所应该具有的接口,hashtable和ziplist是其2种不同的底层实现。

3. 连续内存存储的object

用连续内存存储的object类型(特点是数据都存储在连续的内存中,在落地时,直接将对应内存写到文件中即可)有:int,sds,ziplist,intset,zipmap(已不再使用)。这些object最终都可以通过以下方式写入dump文件。

    if ((n = rdbSaveLen(rdb,len)) == -1) return -1; // 先存值的长度
    nwritten += n;
    if (len > 0) {
        if (rdbWriteRaw(rdb,s,len) == -1) return -1; // 再将值写入到文件中
        nwritten += len;
    }

static int rdbWriteRaw(rio *rdb, void *p, size_t len) {
    if (rdb && rioWrite(rdb,p,len) == 0)
        return -1;
    return len;
}
4. 非连续内存存储的数据的序列化

在这之前需要了解复合的数据类型,redis中的最基本的数据类型是sds和数值(int, double等)这2种类型。其它的类型都是由这2种类型组合而成。上面提到的每个db都有个dict,存储了该db中所有的数据,其中key为sds类型,value为object,object可能的类型和对应的客户端命令关系如下。

命令 object的type object的encoding key的类型 value的类型
db.dict(特指db中的dict) hash dict sds object
set string sds 无key 无value
sadd set hashtable/ziplist sds 无value
hset hash hashtable/ziplist sds sds
lpush list linklist/ziplist/quicklist sds 无value
zset zset skiplist/quicklist sds double

从上面的复合数据类型的分析可知,db中的object的复合类型最复杂的hash也只是由sds类型的K-V键值对组成。hashtable的序列化代码如下:

    } else if (o->type == OBJ_HASH) {
        /* Save a hash value */
        if (o->encoding == OBJ_ENCODING_ZIPLIST) {
            if ((n = rdbSaveRawString(rdb,o->ptr, ziplist_len)) == -1) return -1;
        } else if (o->encoding == OBJ_ENCODING_HT) {
            rdbSaveLen(rdb,dictSize((dict*)o->ptr))) // 先存储键值对的数量

            while((de = dictNext(di)) != NULL) {
                rdbSaveRawString(rdb,(unsigned char*)field, sdslen(field))); // 将key以sds类型进行存储
                rdbSaveRawString(rdb,(unsigned char*)value, sdslen(value))); // 将value以sds类型进行存储
            }
        }

复合的数据类型都是基于sds和数值这2种基本类型组合而成,最终都可以变成基本类型的存储。

数据落地之间的冲突关系

1.RDB数据落地期间,其它的RDB请求都会被拒绝执行,至于AOF数据落地,待确定。

总结

RDB数据落地主要掌握以下几点:

  1. 2种数据落地方式。
  2. 3种触发RDB的方式。
  3. RDB落地的主要流程,了解了该流程即可知道RDB文件内容的格式。
  4. RDB数据落地需要把每个db中的data_dict和expire_dict进行序列化,其中data_dict的value为object类型,需要知道object类型如何进行序列化。

你可能感兴趣的:(redis RDB)