redis4.0之RDB-AOF混合持久化-阿里云开发者社区

开发者社区> 仲肥> 正文

redis4.0之RDB-AOF混合持久化

简介: redis有两种持久化的方式——RDB和AOF,RDB是一份内存快照,AOF则为可回放的命令日志,他们两个各有特点也相互独立。4.0开始允许使用RDB-AOF混合持久化的方式,结合了两者的优点,通过aof-use-rdb-preamble配置项可以打开混合开关。
+关注继续查看

前言

redis有两种持久化的方式——RDB和AOF其中RDB是一份内存快照AOF则为可回放的命令日志他们两个各有特点也相互独立。4.0开始允许使用RDB-AOF混合持久化的方式结合了两者的优点通过aof-use-rdb-preamble配置项可以打开混合开关。

RDB V.S. AOF

1. RDB

RDB文件本质上是一份内存快照保存了创建RDB文件那个时间点的redis全量数据具有数据文件小创建、恢复快的优点但是由于快照的特性无法保存创建RDB之后的增量数据。

2. AOF

AOF文件本质上是一份执行日志保存所有对redis进行更改的命令增量数据也就随命令写入AOF文件刷盘的策略由配置项appendfsync控制可以选择"everysec"或"always"。

AOF文件基本上是human-readable的文本所以其体积相对较大在从AOF文件恢复数据时就是做日志回放执行AOF文件中记录的所有命令所以相对RDB而言恢复耗时较长。

随着redis的运行AOF文件会不断膨胀由aofrewrite机制来防止磁盘空间被撑满详见上一篇文章《redis4.0之利用管道优化aofrewrite》

RDB-AOF混合持久化

细细想来aofrewrite时也是先写一份全量数据到新AOF文件中再追加增量只不过全量数据是以redis命令的格式写入。那么是否可以先以RDB格式写入全量数据再追加增量日志呢这样既可以提高aofrewrite和恢复速度也可以减少文件大小还可以保证数据的完毕性整合RDB和AOF的优点那么现在4.0实现了这一特性——RDB-AOF混合持久化。

aofrewrite

综上所述RDB-AOF混合持久化体现在aofrewrite时那么我们就从这里开始来看4.0是如何实现的

  • 回忆下aofrewrite的过程

    无论是serverCron触发或者执行BGREWRITEAOF命令最终redis都会走到rewriteAppendOnlyFileBackground()
    
    rewriteAppendOnlyFileBackground函数会fork子进程子进程进入rewriteAppendOnlyFile函数来生成新的AOF文件混合持久化就从这里开始
    
int rewriteAppendOnlyFile(char *filename) {
    ...
    if (server.aof_use_rdb_preamble) {
        int error;
        if (rdbSaveRio(&aof,&error,RDB_SAVE_AOF_PREAMBLE,NULL) == C_ERR) {
            errno = error;
            goto werr;
        }
    } else {
        if (rewriteAppendOnlyFileRio(&aof) == C_ERR) goto werr;
    }
    ...
}
  • 可以看到当混合持久化开关打开时就会进入rdbSaveRio函数先以RDB格式来保存全量数据

    前文说道子进程在做aofrewrite时会通过管道从父进程读取增量数据并缓存下来
    
    那么在以RDB格式保存全量数据时也会从管道读取数据并不会造成管道阻塞
    
int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
    ...
    snprintf(magic,sizeof(magic),"REDIS%04d",RDB_VERSION);
    if (rdbWriteRaw(rdb,magic,9) == -1) goto werr;
    if (rdbSaveInfoAuxFields(rdb,flags,rsi) == -1) goto werr;
  • 首先把RDB的版本注意不是redis的版本和辅助域写入文件
    for (j = 0; j < server.dbnum; j++) {
        redisDb *db = server.db+j;
        dict *d = db->dict;
        if (dictSize(d) == 0) continue;
        di = dictGetSafeIterator(d);
        if (!di) return C_ERR;

        /* Write the SELECT DB opcode */
        if (rdbSaveType(rdb,RDB_OPCODE_SELECTDB) == -1) goto werr;
        if (rdbSaveLen(rdb,j) == -1) goto werr;

        /* Write the RESIZE DB opcode. We trim the size to UINT32_MAX, which
         * is currently the largest type we are able to represent in RDB sizes.
         * However this does not limit the actual size of the DB to load since
         * these sizes are just hints to resize the hash tables. */
        uint32_t db_size, expires_size;
        db_size = (dictSize(db->dict) <= UINT32_MAX) ?
                                dictSize(db->dict) :
                                UINT32_MAX;
        expires_size = (dictSize(db->expires) <= UINT32_MAX) ?
                                dictSize(db->expires) :
                                UINT32_MAX;
        if (rdbSaveType(rdb,RDB_OPCODE_RESIZEDB) == -1) goto werr;
        if (rdbSaveLen(rdb,db_size) == -1) goto werr;
        if (rdbSaveLen(rdb,expires_size) == -1) goto werr;
  • 然后遍历DB先把dbnum和db_size、expires_size写入文件
        /* Iterate this DB writing every entry */
        while((de = dictNext(di)) != NULL) {
            sds keystr = dictGetKey(de);
            robj key, *o = dictGetVal(de);
            long long expire;

            initStaticStringObject(key,keystr);
            expire = getExpire(db,&key);
            if (rdbSaveKeyValuePair(rdb,&key,o,expire,now) == -1) goto werr;

            /* When this RDB is produced as part of an AOF rewrite, move
             * accumulated diff from parent to child while rewriting in
             * order to have a smaller final write. */
            if (flags & RDB_SAVE_AOF_PREAMBLE &&
                rdb->processed_bytes > processed+AOF_READ_DIFF_INTERVAL_BYTES)
            {
                processed = rdb->processed_bytes;
                aofReadDiffFromParent();
            }
        }
        dictReleaseIterator(di);
    }
    di = NULL; /* So that we don't release it again on error. */
  • 在当前DB中遍历所有的key把key-value对及过期时间如果有设置的话写入文件

    这里小插曲一下在rdbSaveKeyValuePair函数中会判断expire是否已经到了过期时间如果已经过期就不会写入文件
    
    同时如果flags标记了RDB_SAVE_AOF_PREAMBLE的话说明是在aofrewrite且开启了RDB-AOF混合开关此时就会从父进程去读取增量数据了
    
    /* EOF opcode */
    if (rdbSaveType(rdb,RDB_OPCODE_EOF) == -1) goto werr;

    /* CRC64 checksum. It will be zero if checksum computation is disabled, the
     * loading code skips the check in this case. */
    cksum = rdb->cksum;
    memrev64ifbe(&cksum);
    if (rioWrite(rdb,&cksum,8) == 0) goto werr;
    return C_OK;
}
  • 最后把代表RDB格式结束的RDB_OPCODE_EOF标记和校验和写入文件

RDB-AOF混合持久化的RDB部分到此结束rdbSaveRio函数运行完后返回rewriteAppendOnlyFile继续把增量数据写入AOF文件。

也就是说AOF文件的前半段是RDB格式的全量数据后半段是redis命令格式的增量数据。

数据恢复

当appendonly配置项为no时redis启动后会去加载RDB文件以RDB格式来解析RDB文件自然没有问题。

而appendonly配置项为yes时redis启动后会加载AOF文件来恢复数据如果持久化时开启了RDB-AOF混合开关那么AOF文件的前半段就是RDB格式此时要如何正确加载数据呢

一切数据都逃不过协议二字不以正确的协议存储和解析那就是乱码既然允许RDB-AOF混合持久化就要能够识别并恢复数据这一节我们来介绍如何以正确的姿势来恢复数据。

加载AOF文件的入口为loadAppendOnlyFile

int loadAppendOnlyFile(char *filename) {
    ...
    /* Check if this AOF file has an RDB preamble. In that case we need to
     * load the RDB file and later continue loading the AOF tail. */
    char sig[5]; /* "REDIS" */
    if (fread(sig,1,5,fp) != 5 || memcmp(sig,"REDIS",5) != 0) {
        /* No RDB preamble, seek back at 0 offset. */
        if (fseek(fp,0,SEEK_SET) == -1) goto readerr;
    } else {
        /* RDB preamble. Pass loading the RDB functions. */
        rio rdb;

        serverLog(LL_NOTICE,"Reading RDB preamble from AOF file...");
        if (fseek(fp,0,SEEK_SET) == -1) goto readerr;
        rioInitWithFile(&rdb,fp);
        if (rdbLoadRio(&rdb,NULL) != C_OK) {
            serverLog(LL_WARNING,"Error reading the RDB preamble of the AOF file, AOF loading aborted");
            goto readerr;
        } else {
            serverLog(LL_NOTICE,"Reading the remaining AOF tail...");
        }
    }
    ...
}

打开AOF文件之后首先读取5个字符如果是"REDIS"那么就说明这是一个混合持久化的AOF文件正确的RDB格式一定是以"REDIS"开头而纯AOF格式则一定以"*"开头此时就会进入rdbLoadRio函数来加载数据。

rdbLoadRio函数此处就不详细展开了就是以约定好的协议解析文件内容直至遇到RDB_OPCODE_EOF结束标记返回loadAppendOnlyFile函数继续以AOF格式解析文件直到结束整个加载过程完成。

附录

1. RDB格式的文件

我们先向redis写入一些数据并生成RDB文件

$redis-cli
127.0.0.1:6379> set foo bar
OK
127.0.0.1:6379> expire foo 60
(integer) 1
127.0.0.1:6379> select 2
OK
127.0.0.1:6379[2]> set foo bar
OK
127.0.0.1:6379[2]> bgsave
Background saving started

看下RDB文件内容

$cat dump.rdb
REDIS0008    redis-ver4.0.1
redis-bits@ctimeYused-mem 
                                aof-preamblerepl-id(484f9d49a700c4b9b136f0fd40d2d6e5a8460438
                                                                                               repl-offa;^foobarfoobar^KJ_U

OMG...一堆乱码隐约可以看到一些和redis相关的字符串为了更直观的感受下RDB的内容我们用redis自带的工具redis-check-rdb来看下

redis-check-rdb dump.rdb
[offset 0] Checking RDB file dump.rdb
[offset 26] AUX FIELD redis-ver = '4.0.1'
[offset 40] AUX FIELD redis-bits = '64'
[offset 52] AUX FIELD ctime = '1504234774'
[offset 67] AUX FIELD used-mem = '2139016'
[offset 83] AUX FIELD aof-preamble = '0'
[offset 133] AUX FIELD repl-id = '484f9d49a700c4b9b136f0fd40d2d6e5a8460438'
[offset 148] AUX FIELD repl-offset = '0'
[offset 150] Selecting DB ID 0
[offset 173] Selecting DB ID 2
[offset 194] Checksum OK
[offset 194] \o/ RDB looks OK! \o/
[info] 2 keys read
[info] 1 expires
[info] 0 already expired

这下就好看多了首先可以看到是一些AUX FIELD辅助域4.0特有用来配合全新的主从同步方式PSYNC2后面会专门来介绍PSYNC2然后可以看到DB0和DB2是有内容的Checksum也OK最后是说一共有2个key其中一个设置了过期时间到目前为止还都没有过期。

2. AOF格式的文件

同样的数据我们来看下在AOF文件中是如何保存的

$cat appendonly.aof
*2
$6
SELECT
$1
0
*3
$3
set
$3
foo
$3
bar
*3
$9
PEXPIREAT
$3
foo
$13
1504255377087
*2
$6
SELECT
$1
2
*3
$3
set
$3
foo
$3
bar

很明显就是一条一条redis命令。

3. RDB-AOF混和持久化的文件

最后来看下RDB-AOF混和持久化的文件。

首先打开混合开关执行BGREWRITEAOF生成RDB-AOF混合文件再追加写入一些数据

$redis-cli
127.0.0.1:6379> config set aof-use-rdb-preamble yes
OK
127.0.0.1:6379> BGREWRITEAOF
Background append only file rewriting started
127.0.0.1:6379> set foo bar
OK
127.0.0.1:6379> quit

再来看下此时AOF文件内容

$cat appendonly.aof
REDIS0008    redis-ver4.0.1
redis-bits@ctimeYused-memP 
                                  aof-preamblerepl-id(484f9d49a700c4b9b136f0fd40d2d6e5a8460438
                                                                                                 repl-offsetfoobar?I    Y*2
$6
SELECT
$1
0
*3
$3
set
$3
foo
$3
bar

显而易见前半段是RDB格式的全量数据后半段是redis命令格式的增量数据。

后记

基于4.0的云redis已正式上线登陆阿里云控制台即可开通https://www.aliyun.com/product/kvstore

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
一文了解:Redis的RDB持久化
一文了解:Redis的RDB持久化Redis是内存数据库,为了保证数据不在故障后丢失,Redis需要将数据持久化到硬盘上。 Redis持久化有两种方式:一种是快照,全量备份。一种是AOF方式,连续增量方式。
581 0
redis 数据备份持久化方案
原文:redis 数据备份持久化方案 本文链接:http://www.cnblogs.com/zhenghongxin/p/9050219.html 使用两种备份方案 备份方案选择RDB和AOF同时进行备份,必须打开AOF的持久化机制,除非能接受在故障环境下丢失几分钟的数据。
801 0
使用Redis搭建持久化K-V存储
最近在项目中需要在多机之间共享一些集合,Hash等有类型的数据结构,如果基于Mysql来存储的话需要预先定义一系列表结构并维护表结构和数据结构的一致性,后续还需要根据时间对数据库做旧数据清理工作,所以开始调研一些能满足下列需求的存储引擎。 具有高可用,持久化的特性。 数据可以设置失效时间,方便自动数据清理。 支持常用数据结构,如集合,有序集合,Hash map,列表等。 支持事务操作,
4323 0
Redis持久化之RDB的定义及执行原理
Redis 是内存数据库,就是将数据库中的内容保存在内存中,读写效率更高,但是保存在内存中也随之带来了一个缺点,一旦断电或者宕机,那么内存数据库中的数据将会全部丢失。为了避免进程退出导致数据的永久丢失,需要定期将 Redis 中的数据以数据或命令的形式从内存保存到本地磁盘。当下次 Redis 重启时,利用持久化文件进行数据恢复。 Redis 提供了 RDB 和 AOF 两种持久化机制,前者将当前的数据保存到磁盘,后者则是将每次执行的写命令保存到磁盘(类似于 MySQL 的 Binlog)。
179 0
redis数据结构、持久化、缓存淘汰策略
redis数据结构、持久化、缓存淘汰策略Redis 单线程高性能,它所有的数据都在内存中,所有的运算都是内存级别的运算,而且单线程避免了多线程的切换性能损耗问题。redis利用epoll来实现IO多路复用,将连接信息和事件放到队列中,依次放到文件事件分派器,事件分派器将事件分发给事件处理器。
898 0
redis aof持久化
redis aof缓存数据结构  redis用于存储aof内存数据的数据结构是aof_buf数据结构,所有数据先追加到内存的aof_buf后,再通过定时任务检查是否能够持久化到磁盘文件当中。
767 0
阿里云服务器端口号设置
阿里云服务器初级使用者可能面临的问题之一. 使用tomcat或者其他服务器软件设置端口号后,比如 一些不是默认的, mysql的 3306, mssql的1433,有时候打不开网页, 原因是没有在ecs安全组去设置这个端口号. 解决: 点击ecs下网络和安全下的安全组 在弹出的安全组中,如果没有就新建安全组,然后点击配置规则 最后如上图点击添加...或快速创建.   have fun!  将编程看作是一门艺术,而不单单是个技术。
4440 0
Redis持久化存储
Redis是一个支持持久化的内存数据库,也就是说redis需要经常将内存中的数据同步到磁盘来保证持久化。redis支持四种持久化方式,一是 Snapshotting(快照)也是默认方式;二是Append-only file(缩写aof)的方式;三是虚拟内存方式;四是diskstore方式。
825 0
Retrofit+OKHttp 教你怎么持久化管理Cookie
版权声明:本文为博主原创文章,转载请标明出处。 https://blog.csdn.net/lyhhj/article/details/51345386 绪论 最近小编有点忙啊,项目比较紧,所以一直在忙活项目,继之前的自定义组件之后就没再写博客了,如果你没看到之前的自定义组件你可以看一下: Android自定义下拉刷新动画–仿百度外卖下拉刷新 Android自定义组合控件—教你如何自定义下拉刷新和左滑删除 效果还行,源码也已经传到我的Github上了。
659 0
+关注
仲肥
就职于阿里云数据库技术组,从事redis引擎开发工作,https://github.com/soloestoy
10
文章
38
问答
来源圈子
更多
阿里云数据库:帮用户承担一切数据库风险,给您何止是安心!支持关系型数据库:MySQL、SQL Server、PostgreSQL、PPAS(完美兼容Oracle)、自研PB级数据存储的分布式数据库Petadata、自研金融级云数据库OceanBase支持NoSQL数据库:MongoDB、Redis、Memcache更有褚霸、丁奇、德哥、彭立勋、玄惭、叶翔等顶尖数据库专家服务。
+ 订阅
文章排行榜
最热
最新
相关电子书
更多
文娱运维技术
立即下载
《SaaS模式云原生数据仓库应用场景实践》
立即下载
《看见新力量:二》电子书
立即下载