Redis 持久化与故障恢复之rdb

Posted pigfu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis 持久化与故障恢复之rdb相关的知识,希望对你有一定的参考价值。

一、摘要

         老生常谈一下吧,redis持久化分为rdb和aof两种模式,本篇先说一说rdb模式吧,共分为三部分:1:如何触发rdb持久化, 2:rdb持久化源码, 3:rdb文件解析。

        ps:本文基于redis7分析。

二、如何触发rdb持久化

  1:通过save关键字在redis.conf文件配置触发条件

# save <seconds> <changes> [<seconds> <changes> ...]

save 3600 1 600 100 60 3000

        上述配置表示如果满足每隔3600s内有1个key发生变化,每隔600s内有100个key发生变化,每隔60s内有3000个key发生变化三个条件中的一个,就会触发rdb持久化。

        ps:触发后执行过程与bgsave命令一样

  2:在cli执行save或bgsave命令

       save表示同步执行rdb持久化,会阻塞其它客户端命令的响应;

       bgsve表示异步处理rdb持久化,不会阻塞。

  3:bgsave执行流程如下图:

            

 可以概括为:触发rdb持久化后,redis主进程会fork一个子进程出来,子进程会将内存数据dump到临时的rdb快照文件中,在完成rdb快照文件的生成之后,就替换(通过rename系统函数完成替换)之前的旧的快照文件dump.rdb,每次生成一个新的快照,都会覆盖之前的老快照。

三、rdb持久化源码

    代码核心逻辑在rdb.c文件中,其中核心函数是rdbSaveBackground。

    通过对rdb持久化触发方式的分析,可知有两种代码路径进入rdbSaveBackground函数。

   1:redis.conf 的save配置

//server.c
int main(int argc, char **argv) 
```
    initServer();
``

void initServer(void) 
```
     /* Create the timer callback, this is our way to process many background
     * operations incrementally, like clients timeout, eviction of unaccessed
     * expired keys and so forth. */
     //将定时任务添加到reactor的时间事件中去,1s一次
    if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) 
        serverPanic("Can't create event loop timers.");
        exit(1);
    
```

int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) 
```
/* Check if a background saving or AOF rewrite in progress terminated. */
    if (hasActiveChildProcess() || ldbPendingChildren())
    
        //如果有正在处理的rdb持久化或aof持久化,则不执行,仅仅检查
        run_with_period(1000) receiveChildInfo();
        checkChildrenDone();
     else 
        /* If there is not a background saving/rewrite in progress check if
         * we have to save/rewrite now. */
        for (j = 0; j < server.saveparamslen; j++) 
            //这里的server.saveparams就是save关键字的配置,满足其中一个就执行rdbSaveBackground
            struct saveparam *sp = server.saveparams+j;

            /* Save if we reached the given amount of changes,
             * the given amount of seconds, and if the latest bgsave was
             * successful or if, in case of an error, at least
             * CONFIG_BGSAVE_RETRY_DELAY seconds already elapsed. */
            if (server.dirty >= sp->changes &&
                server.unixtime-server.lastsave > sp->seconds &&
                (server.unixtime-server.lastbgsave_try >
                 CONFIG_BGSAVE_RETRY_DELAY ||
                 server.lastbgsave_status == C_OK))
            
                serverLog(LL_NOTICE,"%d changes in %d seconds. Saving...",
                    sp->changes, (int)sp->seconds);
                rdbSaveInfo rsi, *rsiptr;
                rsiptr = rdbPopulateSaveInfo(&rsi);
                rdbSaveBackground(SLAVE_REQ_NONE,server.rdb_filename,rsiptr,RDBFLAGS_NONE);
                break;
            
        
```

2:bgsave 和save

      redis收到bgsave的命令,会执行bgsaveCommand函数;

     redis收到save的命令,会执行saveCommand函数。

void saveCommand(client *c) 
    if (server.child_type == CHILD_TYPE_RDB) 
        addReplyError(c,"Background save already in progress");
        return;
    
    server.stat_rdb_saves++;
    rdbSaveInfo rsi, *rsiptr;
    rsiptr = rdbPopulateSaveInfo(&rsi);
    //执行保存内存数据到rdb文件
    if (rdbSave(SLAVE_REQ_NONE,server.rdb_filename,rsiptr,RDBFLAGS_NONE) == C_OK) 
        addReply(c,shared.ok);
     else 
        addReplyErrorObject(c,shared.err);
    


/* BGSAVE [SCHEDULE] */
void bgsaveCommand(client *c) 
   ```
    if (server.child_type == CHILD_TYPE_RDB) 
        addReplyError(c,"Background save already in progress");
     else if (hasActiveChildProcess() || server.in_exec) 
         //有活跃的子进程就不会执行rdbSaveBackground
        if (schedule || server.in_exec) 
            server.rdb_bgsave_scheduled = 1;
            addReplyStatus(c,"Background saving scheduled");
         else 
            addReplyError(c,
            "Another child process is active (AOF?): can't BGSAVE right now. "
            "Use BGSAVE SCHEDULE in order to schedule a BGSAVE whenever "
            "possible.");
        
     else if (rdbSaveBackground(SLAVE_REQ_NONE,server.rdb_filename,rsiptr,RDBFLAGS_NONE) == C_OK) 
        addReplyStatus(c,"Background saving started");
     else 
        addReplyErrorObject(c,shared.err);
    

int rdbSaveBackground(int req, char *filename, rdbSaveInfo *rsi, int rdbflags) 
```
   if ((childpid = redisFork(CHILD_TYPE_RDB)) == 0) 
        int retval;

        /* Child */
        redisSetProcTitle("redis-rdb-bgsave");
        redisSetCpuAffinity(server.bgsave_cpulist);
        //执行保存内存数据到rdb文件
        retval = rdbSave(req, filename,rsi,rdbflags);
        if (retval == C_OK) 
            //如果重新生成rdb文件成功,则通知主进程
            sendChildCowInfo(CHILD_INFO_TYPE_RDB_COW_SIZE, "RDB");
        
        exitFromChild((retval == C_OK) ? 0 : 1);
   
```

3:概括代码函数调用过程

   

 4:rdbsave

/* Save the DB on disk. Return C_ERR on error, C_OK on success. */
int rdbSave(int req, char *filename, rdbSaveInfo *rsi, int rdbflags) 
```
    //创建临时rdb文件
    snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid());
    fp = fopen(tmpfile,"w");
    //将内存数据放入临时rdb文件
    if (rdbSaveRio(req,&rdb,&error,rdbflags,rsi) == C_ERR) 
        errno = error;
        err_op = "rdbSaveRio";
        goto werr;
    
    /* Make sure data will not remain on the OS's output buffers */
    if (fflush(fp))  err_op = "fflush"; goto werr; 
    if (fsync(fileno(fp)))  err_op = "fsync"; goto werr; 
    if (fclose(fp))  fp = NULL; err_op = "fclose"; goto werr; 
    //重命名临时文件为正式rdb文件
    if (rename(tmpfile,filename) == -1) ...
     
```

int rdbSaveRio(int req, rio *rdb, int *error, int rdbflags, rdbSaveInfo *rsi) 
```
    //定义rdb文件中check_sum部分的生成函数
    if (server.rdb_checksum)
        rdb->update_cksum = rioGenericUpdateChecksum;
    //定义rdb文件开头部分,REDIS+db_version
    snprintf(magic,sizeof(magic),"REDIS%04d",RDB_VERSION);
    //保存每个db的数据
    /* save all databases, skip this if we're in functions-only mode */
    if (!(req & SLAVE_REQ_RDB_EXCLUDE_DATA)) 
        for (j = 0; j < server.dbnum; j++) 
            if (rdbSaveDb(rdb, j, rdbflags, &key_counter) == -1) goto werr;
        
    
    //生成check_sum,并追加到rdb文件最后
    cksum = rdb->cksum;
    memrev64ifbe(&cksum);
    if (rioWrite(rdb,&cksum,8) == 0) goto werr;
```

//保存每个db的数据
ssize_t rdbSaveDb(rio *rdb, int dbid, int rdbflags, long *key_counter) 
```
    /* Write the SELECT DB opcode */
    //写入rdb文件SELECTDB标志位,表示这里开始要进入某个db了
    if ((res = rdbSaveType(rdb,RDB_OPCODE_SELECTDB)) < 0) goto werr;
    written += res;
    //写入rdb文件当前db的索引号,表示这里开始的数据是某个db的数据
    if ((res = rdbSaveLen(rdb, dbid)) < 0) goto werr;
    written += res;
    //写key val 
    /* Iterate this DB writing every entry */
    while((de = dictNext(di)) != NULL) 
        sds keystr = dictGetKey(de);
        robj key, *o = dictGetVal(de);
        long long expire;
        size_t rdb_bytes_before_key = rdb->processed_bytes;
        expire = getExpire(db,&key);
        if ((res = rdbSaveKeyValuePair(rdb, &key, o, expire, dbid)) < 0) goto werr;
    
```

int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime, int dbid) 
     /* Save the expire time */
    if (expiretime != -1) 
        //有超时时间的话,保存RDB_OPCODE_EXPIRETIME_MS标志位
        if (rdbSaveType(rdb,RDB_OPCODE_EXPIRETIME_MS) == -1) return -1;
        //保存过期时间戳,毫秒
        if (rdbSaveMillisecondTime(rdb,expiretime) == -1) return -1;
    
    //依次保存数据类型,key val三个内容
    /* Save type, key, value */
    if (rdbSaveObjectType(rdb,val) == -1) return -1;
    if (rdbSaveStringObject(rdb,key) == -1) return -1;
    if (rdbSaveObject(rdb,val,key,dbid) == -1) return -1; 
  

  ps:注意下rdbSaveRio之后的函数调用,其中可以一窥rdb文件结构。

四、rdb文件结构

     1:一个完整RDB文件所包含的各个部分如下图,代码见rdbSaveRio函数

   

     1)RDB文件的最开头是REDIS部分,这个部分的长度为5字节,保存 “REDIS”五个字符。通过这五个字符,程序可以在载入文件时,快速 检查所载入的文件是否RDB文件。

     2)db_version长度为4字节,它的值是一个字符串表示的整数,这个整 数记录了RDB文件的版本号,比如redis7的该值就是11,见rdbSaveRio函数的RDB_VERSION

    3)db_content部分包含着零个或多个数据库,以及各个数据库中的键值对数据。

    4)EOF常量的长度为1字节,这个常量标志着RDB文件正文内容的结 束,当读入程序遇到这个值的时候,它知道所有数据库的所有键值对都已经载入完毕了。

   5)check_sum是一个8字节长的无符号整数,保存着一个校验和,这个校验和是程序通过对REDISdb_versiondatabasesEOF四个部分的内 容进行计算得出的。服务器在载入RDB文件时,会将载入数据所计算出 的校验和与check_sum所记录的校验和进行对比,以此来检查RDB文件 是否有出错或者损坏的情况出现。

    2:db_content内部结构如下图,代码见rdbSaveDb函数

  

   1)SELECTDB常量的长度为1字节,当读入程序遇到这个值的时候, 它知道接下来要读入的将是一个数据库索引号。

   2)db_number保存着一个数据库索引号,根据号码的大小不同,这个部 分的长度可以是1字节、2字节或者5字节。当程序读入db_number部分之 后,服务器会调用SELECT命令,根据读入的数据库号码进行数据库切 换,使得之后读入的键值对可以载入到正确的数据库中。

  3)key_value_pairs部分保存了数据库中的所有键值对数据,如果键值 对带有过期时间,那么过期时间也会和键值对保存在一起。根据键值对 的数量、类型、内容以及是否有过期时间等条件的不同, key_value_pairs部分的长度也会有所不同。

    3:key_value_pairs结构如下图,代码见rdbSaveKeyValuePair函数   

      [1]   :无过期时间的结构

              

       [2]:有过期时间的结构

             

      1)EXPIRETIME_MS常量的长度为1字节,它告知读入程序,接下来 要读入的将是一个以毫秒为单位的过期时间。

      2)ms是一个8字节长的带符号整数,记录着一个以毫秒为单位的 UNIX时间戳,这个时间戳就是键值对的过期时间,这样在加载rdb文件时如果ms过期就不加载该值了。

      3)TYPE记录了value的类型,长度为1字节,值可以是以下常量的其中 一个:

  • REDIS_RDB_TYPE_STRING
  • REDIS_RDB_TYPE_LIST    
  • REDIS_RDB_TYPE_SET
  • REDIS_RDB_TYPE_HASH_ZIPLIST
  • REDIS_RDB_TYPE_ZSET_ZIPLIST
  • REDIS_RDB_TYPE_SET_INTSET
  • REDIS_RDB_TYPE_HASH
  • REDIS_RDB_TYPE_LIST_ZIPLIST
  • REDIS_RDB_TYPE_ZSET    这样redis在加载value时就可以根据TYPE去解析了 4) key 总是一个字符串对象,它的编码方式和 REDIS_RDB_TYPE_STRING 类型的 value 一样。根据内容长度的不同, key 的长度也会有所不同。 5)value就是具体的数据内容了, 根据 TYPE 类型的不同,以及保存内容长度的不同,保存 value 的结 构和长度也会有所不同。

Redis的持久化机制详解—RDB与AOF持久化

详细介绍了Redis的持久化机制,包括RDB与AOF持久化,以及混合持久化。

1 数据持久化

为了重启机器、机器故障、系统故障之后恢复数据,将内存中的数据写入到硬盘里面,这就是持久化,Redis恰好支持数据的持久化,这也是相比于Memcached来说一个非常大的优点。

数据从内存持久化到磁盘,通常会需要经历两步:

  1. 由应用程序调用POSIX file API 中的write()函数将数据从用户进程缓冲区(程序内存)写入磁盘,这一步write()函数实际上仅仅写入到了内核缓冲区(或者说磁盘映射内存os cache,或者说文件系统的page cache),并没有真正实际写入磁盘。这一块区域仍然属于文件系统向内核申请的一块的内存区域,并没有真正的把数据持久化到磁盘,所以速度是比较快的。
  2. 当数据在内核缓存区中累积了足够多的数据之后(主要是为了提升性能),操作系统会自动的将这些缓存数据真正的持久化到物理磁盘中,这一步完成之后,数据才算真正的完成持久化。

如果第一步完成了的时候,出现了软件层面的问题,比如进程终止或程序崩溃,我们仍然可以说数据持久化成功了,因为只要操作系统没有问题,它最终会自动的将内核缓存区中的数据持久化到磁盘,所以数据是不会丢失的。

如果第一步完成了的时候,出现了操作系统层面的问题,比如直接停电了、操作系统出故障了等等灾难性事件,那么由于此时的数据仍然在内核缓存区中,将会导致数据的丢失。

默认情况下,Linux 系统将在 30 秒后实际提交写入。可以想象,如果在这三十秒之间主机停电了,那么将会导致应用丢失大量的数据。为此,应用程序可以调用POSIX file API 提供的另一个fsync()函数,该函数会将之强制内核将缓冲区的数据真正立即的写入磁盘,该函数每次调用都会同步阻塞直到设备报告IO完成,并且我们知道磁盘的IP是一个非常消耗性能的操作,所以一般在生产环境的服务器中,Redis 通常是每隔 1s 左右执行一次 fsync()操作。

上面说到了数据从用户内存到磁盘的一个持久化的底层流程,该流程几乎都是通用的(除非使用Zero-Copy等技术)。在应用程序的层面,Redis支待两种待久化策略(也就是什么时候执行持久操作),一种是RDB(Redis DataBase),另一种是AOF(append-only file),可以单独使用其中一种持久化方式或将二者结合使用,或者二者都不使用。

当持久化失败时,Redis将会停止任何的写入命令的执行,并返回错误。

2 RDB(Redis DataBase)快照

RDB是指在指定的时间间隔内针对key的写操作达到一定数量时,就将该时间点的内存中的数据集快照写入磁盘的一个新的.rdb文件中,即一种全量备份的方式。快照(snapshotting),顾名思义可以理解为拍照一样,把某个时刻整个内存数据映射到硬盘中。RDB适合做冷备。

RDB机制是Redis的默认持久化配置,在Redis.conf 配置文件中的默认配置如下:

save 900 1
save 300 10
save 60 10000

该机制表示,达到如下条件时Redis将执行持久化操作:

  1. 900 秒(15 分钟)后,至少更改了1个key;
  2. 300 秒(5 分钟)后,至少更改了10个key;
  3. 300 秒(1 分钟)后,至少更改了10000个key;

2.1 RDB的原理

Redis的工作线程是单线程Reactor模型,如果Redis使用主线程来执行RDB持久化的操作,那么将会极大的影响Redis的性能,因此Redis实际上是使用另一个子进程来做的持久化操作,而不是主线程。

Redis 在持久化RDB时会调用 glibc 的函数 fork 产生一个子进程,即基于当前进程复制了一个进程,子进程刚刚产生时,主进程和子进程会共享内存里面的代码块和数据段,这是 Linux 操作系统的机制,为了节约内存资源,所以尽可能让它们共享起来。

之后再由子进程完成这些RDB持久化的工作,处理客户端请求仍然是主进程处理,这样就可以极大的提供了Redis的性能。子进程做数据持久化时,它不会修改现有的内存数据结构,它只是对数据结构进行遍历读取,然后序列化写到磁盘中。主进程则可以持续服务客户端请求,也可以对内存数据结构进行修改。

那么当子进程对某个数据进行备份时主进行街道修改该数据的命令之后怎么处理呢? Redis使用操作系统的COW(Copy On Write) 机制,当主进程中需要对数据段中的某一页的数据进行修改时,该数据页先会被完整的复制一份,然后主进程在复制的页面上更改,而子进程对应的原始数据页也是没有改变的,这样就保证了子进程看到那一时刻的数据是始终不变的,而在持久化过程中主进程又可以正常改动数据。这也是Redis的RDB持久化方式被称为快照(snapshotting)的原因,子进程看到的数据是不会变化的,定格在fork的那一时刻。

客户端可以使用BGSAVE命令要求Redis服务器立刻执行RDB持久化操作。

2.1 RDB的优缺点

  1. RDB的优点
    1. RDB在每一次持久化的时候,都会生成一个新的.RDB文件,内部包含了该时刻Redis里面的全部数据,这种方式适合做冷备,用于定期全量的保存数据,并且可以方便的将该文件拷贝转移到其他地方,轻松实现多地灾备。
    2. RDB对Redis的性能影响非常小,因为在同步数据的时候他只是fork了一个子进程去做持久化的,对主线程无特殊影响。
    3. 另外如果数据集很大,RDB文件在启动数据恢复时的效率比AOF会更高,直接将整个数据集映射到内存中即可,无需再执行命令。
  2. RDB的缺点
    1. 由于RDB采用在某个时刻生成快照的方式持久化,那么如果Redis在两个RDB持久化操作之间挂了,这就意味着最后一次持久化之后的这段时间的数据直接丢失,而AOF则最多丢失一秒的数据。
    2. 由于RDB是通过fork子进程来协助完成数据持久化工作的,而在fork阶段会发生阻塞。因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。这对于关键业务比如秒杀来说是不能容忍的。

2 AOF(append-only file)追加

2.1 AOF的原理

AOF机制则是以一定的规则将服务器所处理的每一个增,删,改操作以追加(append-only)的形式记录到.aof磁盘文件中,就像记录操作日志一样。Redis是先执行指令,然后再写AOF日志的。

在Redis服务器启动之初将会读取该文件并来重新构建数据库,一条条的执行操作命令(重放日志),以保证启动后数据库中的数据是完整的,明显恢复数据的速度相比于rdb文件更慢一些,但是它也有自己的优点。

默认情况下,AOF机制是关闭的,可以通过将redis.conf的appendonly 参数设置为yes开启。两种机制全部开启的时候,Redis在重启时会默认使用AOF去重新构建数据,因为AOF的数据比RDB更完整。AOF适合做热备。

同样,redis.conf配置文件中存在三种不同的 AOF 同步(fsync刷盘)方式:

  1. appendfsync always:每次有数据修改发生时都会同步写入AOF文件,会严重降低Redis的速度。
  2. appendfsync everysec:每秒钟同步一次,显示地将多个写命令同步到硬盘,这是Redis默认配置。
  3. appendfsync no:从不主动同步,而是让操作系统决定何时进行同步。

2.2 AOF重写

由于AOF文件是追加的形式,因此AOF文件将会越来越大,将会占用越来越多的磁盘空间,对于启动重放AOF日志的操作也会更加耗时,必须想办法给AOF文件瘦身!

假如对于key a进行了以下一系列操作:

127.0.0.1:6379> SADD a aa bb cc
(integer) 3
127.0.0.1:6379> SADD a bb dd
(integer) 1
127.0.0.1:6379> SREM a cc
(integer) 1
127.0.0.1:6379> SMEMBERS a
1) "aa"
2) "bb"
3) "dd"

这一系列对于key a的操作,需要在AOF文件中追加保存3条命令,但其实此时key a的状态我们可以用一条命令来保存,如:SADD a aa bb dd。

以上,就是AOF文件瘦身的思路,三条命令的操作可以使用一条命令来保存,这被称为AOF重写。但实际上,AOF重写与此前的任务操作都无关,仅仅是保存当前该时刻数据库的键值对的状态,它根据当前内存中值所对应的类型,执行对应的命令操作,可以将AOF中的多条操作记录合并为同等语义的一条操作记录。

因为在进行AOF命令重写时会进行大量的写入操作,这时如果在服务器主进程中来进行AOF重写操作,会阻塞服务器主进程,导致其无法处理客户端的请求,为了避免这种情况的发生,AOF重写同样是在fork的子进程中来完成的,类似于RDB。

子进程会使用COW机制拷贝服务器进程的数据副本,AOF重写会对内存进行遍历转换成一系列Redis的操作指令,序列化到一个新的 AOF 日志文件中。同样,使用子进程带来的一个问题是当子进程进行AOF重写时服务器进程在这段期间会接收客户端的新的命令请求,这导致重写后的AOF文件与服务器的状态不一致的情况。

为了解决这个情况,Redis引入了“重写缓冲区”的概念,这个重写缓冲区在Redis创建AOF子进程重写后使用,在AOF重写期间,新到达的命令会在主进程中执行三个操作:

  1. 执行客户端的命令请求;
  2. 客户端的命令追加到现有AOF文件;
  3. 客户端的命令追加到重写缓冲区中;

当子进程完成了AOF文件的重写时,会像父进程发送一个重写完成的信号,父进程在接收到信号后,会将重写缓冲区的新增的指令内容同步到新的AOF文件中,最后重命名新的AOF文件,覆盖原有的AOF文件,实现AOF文件的“瘦身”(这一步是在父进程中操作的,将会短暂的阻塞其他网络请求的执行)。

总结起来就是:

  1. AOF重写,可以减少AOF文件的磁盘占用量,加快数据重启恢复的速度。
  2. AOF重写时,实际上是基于当前内存中数据状态来确定重写后的命令的,对于原来的AOF文件无关。

客户端执行BGREWRITEAOF指令即可命令Redis服务器立即执行AOF 日志重写。服务器也会在满足以下三个条件的情况下自动重写(自动调用BGREWRITEAOF)。

  1. 当前没有RDB和AOF在进行。
  2. Redis 会记住最近一次重写后的 AOF 文件的大小(如果重启后没有发生过重写,则使用启动时 AOF 的大小),Redis将此基本大小与当前AOF文件的大小进行比较,如果当前大小大于指定的百分比,则触发重写。通过redis.conf中配置的auto-aof-rewrite-percentage属性的大小,可以指定百分比,默认100,指定为0表示禁用AOF重写。
  3. 除了满足百分比之外,还有一个条件,那就是重写的 AOF 文件必须要大于等于指定最小大小,这对于避免重写 AOF 文件(即使达到百分比增加但仍然很小)很有用。当前AOF文件重写的最小大于在redis.conf中通过auto-aof-rewrite-min-size属性配置,默认64mb

2.3 AOF的优缺点

  1. AOF的优点
    1. 如果可开启AOF机制并且使用appendfsync everysec同步刷盘机制,那么最多会丢失1秒钟的数据,相比于RBD机制数据安全性更高。
    2. AOF机制对日志文件的写入操作采用的是append-only模式,即追加写入,对于此前写入的命令数据没有任何影响,如果某次操作误删了数据,那么在AOF重写之前只需要立即找到AOF文件并且将“删除命令”删除,然后重启即可恢复原数据。
    3. 追加写入的方式为顺序写入无需过多的磁盘寻址。
  2. AOF的缺点
    1. 对于相同数量的数据集而言,AOF文件通常要大于RDB文件,启动Redis重新加载aof持久化文件的时间也会更久。
    2. 使用AOF之后,Redis能保证更好的数据安全性,但qps往往低于RDB。一般而言,每秒同步的策略的效率是比较高的。

3 混合持久化策略

如果采用RDB持久化,那么将可能丢失大量数据,如果采用AOF持久化,那么在启动时会慢很多。

Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认开启,可以在redis.conf中通过通过配置aof-use-rdb-preamble 开启)。

混合持久化依赖aof文件,因此必须开启AOF持久化。主要就是在进行AOF重写时候,可以避免aof文件过大的问题。

开启混合持久化之后,RDB文件和AOF日志文件存在一起,aof文件内容格式是[RDB file][AOF tail] 明显特征是aof文件的开头为:REDIS。在进行重写的时候,内存中的原始数据被持久化为RBD格式的数据,而重写开始到重写结束(RDB持久化)期间的数据则记录为AOF格式的增量日志。

开启混合持久化之后,aof中的AOF日志不再是全量的日志,而是自持久化开始到持久化结束的这段时间发生的增量AOF日志,通常这部分AOF日志很小。在 Redis 重启的时候,可以先快速加载 RDB部分的内容,然后再重放增量 AOF 日志就可以完全替代之前的AOF全量文件重放,数据安全性和重启效率都得到大幅得到提升。

相关文章:

  1. https://redis.io/topics/data-types
  2. https://redis.io/topics/data-types-intro

如有需要交流,或者文章有误,请直接留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!

以上是关于Redis 持久化与故障恢复之rdb的主要内容,如果未能解决你的问题,请参考以下文章

Redis突然出现故障,如何让它自动恢复?

redis持久化RDB与AOF

宕机后,Redis如何实现快速恢复?

Redis学习总结(23)——Redis如何实现故障自动恢复?浅析哨兵的工作原理

Redis学习总结(23)——Redis如何实现故障自动恢复?浅析哨兵的工作原理

Redis 宕机后,如何实现故障,快速自动恢复?