Redis高级之底层源码7——数据持久化(AOF方式)

本文详细介绍了Redis的AOF(AppendOnlyFile)和RDB持久化机制,包括AOF的工作原理、触发机制、重写操作,以及RDB和AOF的优缺点,讨论了AOF+RDB混合模式的配置和数据恢复流程。
摘要由CSDN通过智能技术生成

1 概述

        RDB全量备份是非常耗时的,而且不能提供强一致性,如果Redis进程崩溃,那么在最近一次RDB备份之后的数据也会随之消失。AOF以独立日志的方式记录每次的写命令,可以很好的解决数据持久化的实时性。系统重启时可以重新执行AOF文件中的密令来恢复数据。AOF会先把命令追加在AOF缓冲区,然后根据对应的策略写入硬盘。

2 AOF持久化流程

AOF持久化流程如下图:

        AOF的实现流程有三个步骤(append——write——fsync):

首先append把命令追加到AOF缓冲区,然后write将缓冲区的命令写入到程序缓冲区,最后fsync将程序缓冲区的内容写入文件。当AOF持久化功能处于开启状态时,服务器每执行完一个命令就会将命令以协议格式追加写入redisServer结构体的aof_buf缓冲区。而在服务重启的时候会把AOF文件加载到缓冲区中。

        AOF有以下三种触发机制:

        always:每次发生数据变更都会被记录到磁盘,性能差,但数据完整性比较好。

        everysec:每秒将aof_buf缓冲区的内容写入AOF文件,如果宕机,就会有1秒内的数据丢失。

        no:将数据同步操作交给操作系统来处理,性能最好,但数据可靠性最差。在配置文件中设置appendonly yes 后,若没有指定appendfsync,默认会使用everysec选项。

AOF持久化源码如下:

    //AOF定时触发的源码
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {

    /* AOF延迟刷新,如果fsync速度慢,则在每个cron周期内尝试完成 */
    if (server.aof_flush_postponed_start) flushAppendOnlyFile(0);

    /* AOF些错误,在这种情况下,还有一个缓冲区要刷新,如果成功,清除AOF错误,是数据库重新可写 */
    run_with_period(1000) {
        if (server.aof_last_write_status == C_ERR)
            flushAppendOnlyFile(0);
    }

}
//执行write和fsync操作
void flushAppendOnlyFile(int force) {
    ssize_t nwritten;
    int sync_in_progress = 0;
    mstime_t latency;
    //检查是否有数据
    if (sdslen(server.aof_buf) == 0) {
        /* 使用bio_pending来判断是否有后台fsync操作正在进行 */
        if (server.aof_fsync == AOF_FSYNC_EVERYSEC &&
            server.aof_fsync_offset != server.aof_current_size &&
            server.unixtime > server.aof_last_fsync &&
            !(sync_in_progress = aofFsyncInProgress())) {
            goto try_fsync;
        } else {
            return;
        }
    }

    if (server.aof_fsync == AOF_FSYNC_EVERYSEC)
        sync_in_progress = aofFsyncInProgress();

    //如果没有设置强制刷盘的选项,可能不会立即进行,而是延迟执行AOF刷盘
    if (server.aof_fsync == AOF_FSYNC_EVERYSEC && !force) {
        
        if (sync_in_progress) {
            if (server.aof_flush_postponed_start == 0) {
                server.aof_flush_postponed_start = server.unixtime;
                return;
            } 
            //如果距离上次执行刷盘操作没有超过2秒,直接返回
            else if (server.unixtime - server.aof_flush_postponed_start < 2) {
                
                return;
            }
            /* 如果后台还有fsync在执行,并且write已经推迟2秒以上,那么执行写操作(write)被阻塞
             * 如果此时出现死机等故障,可能会导致2秒左右的AOF日志数据丢失*/
            server.aof_delayed_fsync++;
            serverLog(LL_NOTICE,"Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis.");
        }
    }

    if (server.aof_flush_sleep && sdslen(server.aof_buf)) {
        usleep(server.aof_flush_sleep);
    }
    //将server.aof_buf中缓存的AOF日志数据写入磁盘
    latencyStartMonitor(latency);
    nwritten = aofWrite(server.aof_fd,server.aof_buf,sdslen(server.aof_buf));
    latencyEndMonitor(latency);
    if (sync_in_progress) {
        latencyAddSampleIfNeeded("aof-write-pending-fsync",latency);
    } else if (hasActiveChildProcess()) {
        latencyAddSampleIfNeeded("aof-write-active-child",latency);
    } else {
        latencyAddSampleIfNeeded("aof-write-alone",latency);
    }
    latencyAddSampleIfNeeded("aof-write",latency);

    /* 重置延迟刷盘时间*/
    server.aof_flush_postponed_start = 0;
    //如果write失败,那么尝试将该情况写入日志
    if (nwritten != (ssize_t)sdslen(server.aof_buf)) {
        static time_t last_write_error_log = 0;
        int can_log = 0;

        if ((server.unixtime - last_write_error_log) > AOF_WRITE_LOG_ERROR_RATE) {
            can_log = 1;
            last_write_error_log = server.unixtime;
        }

        if (nwritten == -1) {
            if (can_log) {
                serverLog(LL_WARNING,"Error writing to the AOF file: %s",
                    strerror(errno));
                server.aof_last_write_errno = errno;
            }
        } else {
            if (can_log) {
                serverLog(LL_WARNING,"Short write while writing to "
                                       "the AOF file: (nwritten=%lld, "
                                       "expected=%lld)",
                                       (long long)nwritten,
                                       (long long)sdslen(server.aof_buf));
            }

            //尝试删除新追加到AOF中的不完整的数据内容
            if (ftruncate(server.aof_fd, server.aof_current_size) == -1) {
                if (can_log) {
                    serverLog(LL_WARNING, "Could not remove short write "
                             "from the append-only file.  Redis may refuse "
                             "to load the AOF the next time it starts.  "
                             "ftruncate: %s", strerror(errno));
                }
            } else {
                /* If the ftruncate() succeeded we can set nwritten to
                 * -1 since there is no longer partial data into the AOF. */
                nwritten = -1;
            }
            server.aof_last_write_errno = ENOSPC;
        }

        /* 处理写入AOF文件时出现的错误 */
        if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
           
            serverLog(LL_WARNING,"Can't recover from AOF write error when the AOF fsync policy is 'always'. Exiting...");
            exit(1);
        } else {
            
            server.aof_last_write_status = C_ERR;

            /* 如果已经写入了部分数据,是不能通过ftruncate进行撤销
             * 通过sdsrange清除aof_buf中已经写入磁盘的那部分数据*/
            if (nwritten > 0) {
                server.aof_current_size += nwritten;
                sdsrange(server.aof_buf,nwritten,-1);
            }
            return; /* We'll try again on the next call... */
        }
    } else {
        /* Successful write(2). If AOF was in error state, restore the
         * OK state and log the event. */
        if (server.aof_last_write_status == C_ERR) {
            serverLog(LL_WARNING,
                "AOF write error looks solved, Redis can write again.");
            server.aof_last_write_status = C_OK;
        }
    }
    //更新写入后的AOF文件大小
    server.aof_current_size += nwritten;

    /* 当server.aof_buf足够小时,就重新利用内存空间,放置频繁的内存分配 */
    //当server.aof_buf 占据大量内存空间时,采取的策略是释放空间
    if ((sdslen(server.aof_buf)+sdsavail(server.aof_buf)) < 4000) {
        sdsclear(server.aof_buf);
    } else {
        sdsfree(server.aof_buf);
        server.aof_buf = sdsempty();
    }

try_fsync:
    /* Don't fsync if no-appendfsync-on-rewrite is set to yes and there are
     * children doing I/O in the background. */
    if (server.aof_no_fsync_on_rewrite && hasActiveChildProcess())
        return;

    /* Perform the fsync if needed. */
    if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
        /* redis_fsync is defined as fdatasync() for Linux in order to avoid
         * flushing metadata. */
        latencyStartMonitor(latency);
        redis_fsync(server.aof_fd); /* Let's try to get this data on the disk */
        latencyEndMonitor(latency);
        latencyAddSampleIfNeeded("aof-fsync-always",latency);
        server.aof_fsync_offset = server.aof_current_size;
        server.aof_last_fsync = server.unixtime;
    } else if ((server.aof_fsync == AOF_FSYNC_EVERYSEC &&
                server.unixtime > server.aof_last_fsync)) {
        if (!sync_in_progress) {
            aof_background_fsync(server.aof_fd);
            server.aof_fsync_offset = server.aof_current_size;
        }
        server.aof_last_fsync = server.unixtime;
    }
}

        在flushAppendOnlyFile函数中,通过write写盘之后根据apendfsync选项来执行刷盘策略:如果是AOFFSYNCALWAYS,就立即执行刷盘操作;如果是AOFFSYNCENERYSEC,就创建一个后台异步刷盘任务。在bioCreateBackgroundJob函数中会创建bit后台任务,在bioProcessBackGroundJobs函数中会执行bio后台任务的处理。当flushAppendOnlyFile函数被调用时,可能会出现以下四种情况:

        1、save操作的执行时间未超过2秒,那么程序直接返回,并不执行write操作或新的save操作。

        2、save操作的执行时间超过2秒,那么程序执行write操作,但不执行新的save操作。注意,这时执行write操作必须等待子线程先完成save操作,因此这里执行write操作会阻塞更长时间。

        3、距上次成功执行save操作不超过1秒,那么程序执行write操作,但不执行save操作。

        4、距上次成功执行save操作已经超过1秒,那么程序执行write操作和save操作。

        执行save操作时的流程图如下:

        下图是一个简单的AOF文件内容:

 

        可以把此文件内容分解成两个命令:

        第一个命令:包含两个参数,第一个参数6字节,为select;第二个参数1字节;原始命令为select 0。

        第二个命令:包含三个参数,第一个参数3字节,为set;第二个参数4字节,为name;第三个参数为4自己,为clay;原始命令为 set name clay。

3 相关参数配置

        开启AOF持久化的相关配置:

# 开始AOF持久化
appendonly yes

# AOF文件名
appendfilename "appendonly.aof"

#aof文件比上次重写时增长100%(配置可以大于100%)时触发重写
auto-aof-rewrite-percentage 100

#aof文件大小超过64mb时触发重写
auto-aof-rewrite-min-size 64mb

#aof持久化策略,任选一个,默认是everysec
# appendfsync always
appendfsync everysec
# appendfsync no

4 重写操作

        在AOF持久化模式下,每个写命令都会追加到AOF文件。随着对数据库的不断操作,AOF文件会越来越大,为了避免AOF产生的文件太大,服务器会对AOF文件进行重写,将操作相同键的相同命令合并,从而减少文件的大小。AOF文件重写流程如下;

         重写流程解析如下:

        bgrewriteaof 触发重写,判断是否有bgsave或者bgrewriteaof在运行,如果有,则等待该命令结束后在继续执行。

        主进程复刻(fork)出子进程执行重写操作,保证主进程不会被阻塞。注意:fork也可称为派生、分支等。这里指创建出子进程。

        子进程遍历Redis内存中的数据同时写入新的AOF临时文件,在写入新文件的过程中,客户端的写请求在写入aof_buf缓冲区的同时也写入aof_rewrite_buf重写缓冲区,这是为了保证原AOF文件的完整以及新AOF文件生成期间的新数据修改操作不会被遗漏。

        子进程写完新的AOF文件后,向主进程发信号,然后把aof_rewrite_buf中的数据写入新的AOF文件。

        最后使用新的AOF文件覆盖旧的AOF文件,完成AOF重写。

      

5 AOF和RDB的数据恢复顺序

        Redis服务重启时的数据恢复流程如下图:

        流程解析如下:

        1、首先判断是否开启AOF 持久化,如果开启AOF ,则使用AOF 文件恢复数据,否则使用RDB恢复数据。需要注意的是,如果AOF 和RDB文件都不存在,则直接启动Redis。

5 RDB和AOF的优缺点

        5.1 RDB持久化的优缺点

        RDB持久化后的文件是二进制文件 ,更适合用于备份、全量复制和灾难恢复,而且RDB恢复数据的性能要优于AOF恢复数据的性能。当数据库操作越来越频繁、数据量不断增大时,RDB需要经常复刻出子进程,让子进程将数据持久化在磁盘上。如果数据集很大,就很有可能导致Redis停止为客户端服务器几毫秒甚至一秒。AOF也需要复刻出子进程,不过可以调整要重写日志的频率,这样就无须在持久化的选选择上进行权衡。在默认情况下,RDB数据持久化实时性比较差,而配置为高时效性时,频繁操作的成本则会很高。

        5.2 AOF持久化的优缺点

        使用AOF持久化可以根据不同的fsync策略来备份数据,因为AOF采用的是追加日志的方式,因此即使断电也不会出现磁盘寻道或磁盘被损坏的问题。如果由于某种原因日志只记录了一半,那么可以使用redis-check-aof工具轻松修复。当数据量太大时,Redis能够在后台自动重写AOF,并生成一个全新的文件,其中包含创建当前数据集所需的最少操作集,一旦准备好新的文件,Redis就会切换新的文件并开始把日志追加到新的文件。

        AOF文件包含了所有操作的日志,而且容易看懂,当用户不小心使用flushall命令(删除所有数据),可以根据AOF文件找到错误的命令,把这些错误的命令删除,然后重新启动Redis,就可以恢复 对应的业务数据。但是在此期间,AOF文件不能被重写,重写之后的AOF文件不再是可以让用户理解的内容。

        AOF文件会以文本格式保存所有写操作命令,且未经压缩,因此对于同一数据集,AOF文件通常大于等效的RDB文件。

        恢复数据时会重放AOF文件中的所有命令(即重新执行),因此数据恢复的性能要弱于RDB方式时数据恢复的性能。

        RDB和AOF持久化的区别:

6 AOF+RDB混合模式配置

        在介绍了上面两种持久化方式后,使用RDB持久化会有数据丢失的风险,当时数据恢复的速度快;使用AOF持久化可以保证数据的完整性,但数据恢复的速度慢。在Redis 4 之后新增了AOF+RDB混合模式,先使用RDB存储快照,然后使用AOF持久化记录所有的写操作,当满足重写策略或手动触发重写的时候,将最新的数据存储为新的RDB记录。重启服务时会从RDB和AOF两部分恢复数据,既保证了数据的完整性,又提高了数据恢复的性能。

        开始混合模式后,在bgrewriteaof命令之后会在AOF文件中以RDB格式写入当前最新的数据,之后的写操作继续以 AOF的追加形式追加写命令。当Redis重启的时候,会加载RDB的部分再加载剩余的AOF部分。AOF+RDB混合模式持久化的流程图如下:

        开始混合持久化模式后,重写之后的AOF文件和RDB文件都存储的是二进制数据,继续往Redis中进行写操作时,在AOF中仍然已追加的方式追加命令。因此,重写后的AOF文件由两部分组成,一部分是类似RDB文件的二进制快照,另一部分是追加的命令文本,完美地结合AOF和RDB持久化的优势。

        开始AOF+RDB混合模式持久化的配置如下:

aof-use-rdb-preamble yes

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

geminigoth

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值