Redis从入门到精通【进阶篇】之持久化 AOF详解

news2024/9/25 1:18:17

文章目录

  • 0.前言
  • 1.详解
    • 1.1 AOF 文件的创建
    • 1.2. AOF 文件的写入
    • 1.3. AOF 文件的同步
      • 1. 3.1 同步磁盘上的所有数据
      • 1. 3.2 定期同步磁盘上的数据
    • 1.4. AOF 文件的重写
    • 1.5. AOF 文件的恢复
    • 1.6. 小结
  • 2. RDB和AOF混合方式
  • 3. Redis从入门到精通系列文章

在这里插入图片描述

0.前言

Redis 支持多种持久化方式来保证数据的可靠性和持久性。其中AOF(Append Only File)机制是一种常用的持久化方式,它记录了所有对 Redis 数据库进行修改的命令,在 Redis 重启时可以使用这些命令来重构数据库状态。本文将详细介绍 Redis AOF 持久化机制的实现原理。
在这里插入图片描述

1.详解

在 Redis 的配置文件中,可以通过以下配置项来设置 AOF 持久化相关的参数

1. appendonly:设置是否开启 AOF 持久化,默认为 no(关闭状态)。

2. appendfilename:设置 AOF 文件名,默认为 appendonly.aof。

3. appendfsync:设置 AOF 文件同步策略,有以下三种可选值:

- always:每次写入都会同步到磁盘,最安全但性能最差;
- everysec:每秒同步一次到磁盘,安全性和性能的折中方案;
- no:由操作系统决定何时同步,最不安全但性能最好。

4. auto-aof-rewrite-percentage:设置 AOF 重写触发的条件之一,即 AOF 文件大小增长率达到该值时触发 AOF 重写,默认为 1005. auto-aof-rewrite-min-size:设置 AOF 重写触发的条件之一,即 AOF 文件大小达到该值时触发 AOF 重写,默认为 64MB。

6. aof-load-truncated:设置当 Redis 以 AOF 模式启动时,是否自动修复截断的 AOF 文件,默认为 yes。

7. aof-use-rdb-preamble:设置是否在 AOF 文件中使用 RDB 文件的前缀,默认为 yes。

需要注意的是,修改 Redis 配置文件后,需要重启 Redis 才能使配置生效。同时,对于 AOF
文件同步策略的选择,需要根据实际情况进行权衡和选择。如果对数据的安全性要求非常高,可以选择 always
策略;如果对性能的要求比较高,可以选择 everysec 策略。

1.1 AOF 文件的创建

当启用 AOF 持久化机制时,Redis 会在启动时创建一个 AOF 文件,用于记录所有写命令。AOF 文件的默认文件名为 appendonly.aof,可以通过配置文件中的 appendfilename 参数进行修改。如果 AOF 文件已经存在,则 Redis 会在启动时读取文件中的内容,并将其加载到内存中。

1.2. AOF 文件的写入

当 Redis 执行写命令时,会将命令追加到 AOF 文件的末尾。如果 AOF 文件不存在,则 Redis 会自动创建一个新的 AOF 文件。如果 AOF 文件已经存在,则 Redis 会将命令追加到文件的末尾。

Redis 为了提高写入性能,通常会将多个命令缓存到内存中,然后在满足一定条件时批量写入到 AOF 文件中。具体来说,Redis 会维护一个 AOF 缓冲区,将每个写命令追加到缓冲区的末尾。当缓冲区的大小超过一定阈值时,或者缓冲区中的命令已经等待了一定时间后,Redis 会将缓冲区中的命令批量写入到 AOF 文件中。
在这里插入图片描述

1.3. AOF 文件的同步

为了保证写命令的可靠性和持久性,Redis 会在写入 AOF 文件后进行同步操作,将数据从内存中刷到磁盘中。这就是我通常所说的刷盘操作。刷盘操作可以分为两种方式:

1. 3.1 同步磁盘上的所有数据

每次写入 AOF 文件时,Redis 会调用 fsync 系统调用将数据从内存刷到磁盘中。这种方式可以保证数据的可靠性,但是会带来较大的性能损耗。

1. 3.2 定期同步磁盘上的数据

Redis 也可以通过配置文件中的 appendfsync 参数来控制 AOF 文件的同步方式。其中,有三种常用的方式:

  • always:每次写入 AOF 文件时都进行同步操作,可以保证数据的可靠性,但是会带来较大的性能损耗。
  • everysec:每秒钟对 AOF 文件进行一次同步操作,可以在一定程度上平衡数据安全和性能需求。
  • no:不进行同步操作,只在 Redis 进程退出时进行一次同步操作,可以提高性能,但是会带来一定的数据风险。
    在这里插入图片描述
    总结一下
  1. 如果要高性能,就选择 No 策略;
  2. 如果要高可靠,就选择 Always 策略;
  3. 如果允许数据丢失一点,但又想性能高,就选择 Everysec策略。

其实在源码中这三种策略只是在控制 fsync() 函数的调用时机
当应用程序向文件写入数据时,内核通常先将数据复制到内核缓冲区中,然后排入队列,然后由内核决定何时写入硬盘。

// AOF 持久化主函数。只在rewriteAppendOnlyFileBackground() 中会调用此函数
/* Write a sequence of commands able to fully rebuild the dataset into
* "filename". Used both by REWRITEAOF and BGREWRITEAOF.
**
In order to minimize the number of commands needed in the rewritten
* log Redis uses variadic commands when possible, such as RPUSH, SADD
* and ZADD. However at max REDIS_AOF_REWRITE_ITEMS_PER_CMD items per time
* are inserted using a single command. */
    int rewriteAppendOnlyFile(char *filename) {
    dictIterator *di = NULL;
    dictEntry *de;
    rio aof;
    FILE *fp;
    char tmpfile[256];
    int j;
    long long now = mstime();
    /* Note that we have to use a different temp name here compared to the
    * one used by rewriteAppendOnlyFileBackground() function. */

    snprintf(tmpfile,256,"temp-rewriteaof-%d.aof", (int) getpid());
    // 打开文件
    fp = fopen(tmpfile,"w");
    if (!fp) {
        redisLog(REDIS_WARNING, "Opening the temp file for AOF rewrite in"
        "rewriteAppendOnlyFile(): %s", strerror(errno));
        return REDIS_ERR;
    }
        // 初始化rio 结构体
        rioInitWithFile(&aof,fp);
        // 如果设置了自动备份参数,将进行设置
    if (server.aof_rewrite_incremental_fsync)
        rioSetAutoSync(&aof,REDIS_AOF_AUTOSYNC_BYTES);
        // 备份每一个数据集
    for (j = 0; j < server.dbnum; j++) {
        char selectcmd[] = "*2\r\n$6\r\nSELECT\r\n";
        redisDb *db = server.db+j;
        dict *d = db->dict;
    if (dictSize(d) == 0) continue;
        // 获取数据集的迭代器
        di = dictGetSafeIterator(d);
    if (!di) {
        fclose(fp);
        return REDIS_ERR;
    }
    // 写入AOF 操作码
    /* SELECT the new DB */
    if (rioWrite(&aof,selectcmd,sizeof(selectcmd)-1) == 0) goto werr;
    // 写入数据集序号
    if (rioWriteBulkLongLong(&aof,j) == 0) goto werr;
    // 写入数据集中每一个数据项
    /* Iterate this DB writing every entry */
    while((de = dictNext(di)) != NULL) {
        sds keystr;
        robj key, *o;
        long long expiretime;
        keystr = dictGetKey(de);
        o = dictGetVal(de);
        // 将keystr 封装在robj 里
        initStaticStringObject(key,keystr);
        // 获取过期时间
        expiretime = getExpire(db,&key);

        // 如果已经过期,放弃存储
        /* If this key is already expired skip it */
    if (expiretime != -1 && expiretime < now) continue;
        // 写入键值对应的写操作
        /* Save the key and associated value */
    if (o->type == REDIS_STRING) {
        /* Emit a SET command */
        char cmd[]="*3\r\n$3\r\nSET\r\n";
    if (rioWrite(&aof,cmd,sizeof(cmd)-1) == 0) goto werr;
        /* Key and value */
    if (rioWriteBulkObject(&aof,&key) == 0) goto werr;
    if (rioWriteBulkObject(&aof,o) == 0) goto werr;
    } else if (o->type == REDIS_LIST) {
    if (rewriteListObject(&aof,&key,o) == 0) goto werr;
    } else if (o->type == REDIS_SET) {
    if (rewriteSetObject(&aof,&key,o) == 0) goto werr;
    } else if (o->type == REDIS_ZSET) {
    if (rewriteSortedSetObject(&aof,&key,o) == 0) goto werr;
    } else if (o->type == REDIS_HASH) {
    if (rewriteHashObject(&aof,&key,o) == 0) goto werr;
    } else {
        redisPanic("Unknown object type");
    }
    // 写入过期时间
    /* Save the expire time */
    if (expiretime != -1) {
        char cmd[]="*3\r\n$9\r\nPEXPIREAT\r\n";
    if (rioWrite(&aof,cmd,sizeof(cmd)-1) == 0) goto werr;
    if (rioWriteBulkObject(&aof,&key) == 0) goto werr;
    if (rioWriteBulkLongLong(&aof,expiretime) == 0) goto werr;
    }
}
    // 释放迭代器
    dictReleaseIterator(di);
}
    // 写入磁盘
    /* Make sure data will not remain on the OS's output buffers */
    fflush(fp);
    aof_fsync(fileno(fp));
    fclose(fp);
    // 重写文件名
    /* Use RENAME to make sure the DB file is changed atomically only
    * if the generate DB file is ok. */
    if (rename(tmpfile,filename) == -1) {
        redisLog(REDIS_WARNING,"Error moving temp append only file on the "
        "final destination: %s", strerror(errno));
        unlink(tmpfile);
        return REDIS_ERR;
    }
    redisLog(REDIS_NOTICE,"SYNC append only file rewrite performed");
    return REDIS_OK;
    werr:
    // 清理工作
    fclose(fp);
    unlink(tmpfile);
    redisLog(REDIS_WARNING,"Write error writing append only file on disk: "
    "%s", strerror(errno));
    if (di) dictReleaseIterator(di);
        return REDIS_ERR;
}

1.4. AOF 文件的重写

Redis AOF 文件在长时间运行后可能会变得非常大,不仅占用磁盘空间,还会影响 Redis 的性能。为了解决这个问题,Redis 提供了 AOF 文件重写机制,可以将 AOF 文件中的写命令进行压缩,生成一条等效的命令序列,从而减少 AOF 文件的大小。
在这里插入图片描述
AOF 文件重写机制的实现原理如下:

(1)Redis 会启动一个新的子进程,用于执行 AOF 文件重写操作。

(2)Redis 主进程会将所有写命令发送到子进程中,子进程会执行这些命令,并生成一条等效的命令序列。

(3)子进程会将等效命令序列写入到一个新的 AOF 文件中,同时在写入过程中进行同步操作,保证数据的可靠性。

(4)当子进程完成 AOF 文件的重写操作后,Redis 主进程会将新的 AOF 文件重命名为原来的 AOF 文件,并删除旧的 AOF 文件。

需要注意的是,AOF 文件重写操作只会对已经执行过的写命令进行压缩,新的写命令仍然会被追加到 AOF 文件的末尾。Redis 会周期性地检查 AOF 文件的大小,并在满足一定条件时触发 AOF 文件重写操作。

1.5. AOF 文件的恢复

AOF 的数据恢复过程设计很巧妙,它模拟一个 Redis 的服务过程。Redis 首先虚拟一个客户端,读取 AOF 文件恢复 Redis 命令和参数;接着过程就和服务客户端一样执行命令相应的函数,从而恢复数据,这样做的目的无非是提高代码的复用率。这些过程主要在 loadAppendOnlyFile() 中实现。我们可以理解为假的重放。

真实的过程当 Redis 重启时,会根据 AOF 文件中记录的命令重构数据库状态。具体来说,Redis 会按照 AOF 文件中记录的命令顺序,逐个执行命令,并在执行过程中重建数据库状态。需要注意的是,AOF 文件的恢复过程可能会比较耗时,因此需要在 Redis 配置文件中设置 AOF 文件的恢复方式,可以选择同步方式(always、everysec)或异步方式(no)。

所以,Redis AOF 持久化机制,其实说白了就是redis启动的时候创建一个server.aof_buf的文件,通过一个文件,实现了将写命令追加到 AOF 文件中,并在需要时同步到磁盘中,从而保证了数据的可靠性和持久性。 当异常宕机需要恢复数据的时候又通过读取文件,回放之前的命令,进行数据还原。
由此大家也可以推断出它存在的弊端。

1.6. 小结

  1. 对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。

  2. 根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。

  3. 在一般情况下, 每秒 fsync 的性能依然非常高, 而关闭 fsync 可以让 AOF 的速度和 RDB 一样快, 即使在高负荷之下也是如此。 不过在处理巨大的写入载入时,RDB 可以提供更有保证的最大延迟时间(latency)

到此我们学完了 Redis 的持久化方式 RDB 和AOF 。那么我们对比一下。整理出如下表格

RDBAOF
启动优先级
体积比 AOF 小比 RDB 大
恢复速度比 AOF 快比 RDB 慢
数据安全性在定时备份时可能会丢失一定量的数据根据同步策略决定,相对更安全
数据一致性RDB 持久化的数据可能不是最新的,但数据一致性较高AOF 持久化的数据较新,但数据一致性较低
运维复杂度相对简单,只需要定期备份 RDB 文件即可相对复杂,需要配置 AOF 缓冲区大小、同步策略等参数
写入性能相对较高,因为 RDB 只需要生成一次快照文件即可完成持久化操作相对较低,因为 AOF 需要将每一次写操作都写入到 AOF 文件中
读取性能相对较高,因为 RDB 文件读取速度快相对较低,因为需要将 AOF 文件中的所有写操作都执行一遍才能恢复
文件格式二进制格式,比较紧凑文本格式,可读性好
恢复方式通过加载 RDB 文件来恢复数据库通过加载 AOF 文件并执行其中的写操作来恢复数据库
文件重写方式通过 BGSAVE 命令生成新的 RDB 文件来实现文件重写通过执行 BGREWRITEAOF 命令来生成新的 AOF 文件实现文件重写

2. RDB和AOF混合方式

Redis 4.0 中提出了一个混合使用 AOF 日志和内存快照的方法。简单来说,内存快照以一定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作。

混合方式可以同时利用 RDB 的快速恢复和 AOF 的数据安全性。在这种混合方式下,Redis 会同时生成 RDB 文件和 AOF 文件,来保证数据的安全性和可靠性。

具体来说,可以采用如下的持久化配置:

  1. 将 AOF 模式设置为 always,并设置合适的同步策略,以确保 Redis 在执行写操作时,都将对应的操作写入 AOF 文件中。

  2. 在 Redis 中启用 RDB 持久化,并设置一个合适的持久化间隔,以确保 Redis 在每隔一定时间内,执行一次 RDB 文件的快照生成操作。

  3. 将 Redis 配置为在启动时,先尝试使用 AOF 文件进行恢复,如果 AOF 文件不存在或损坏,则使用 RDB 文件进行恢复。
    从网上找个图方便大家理解
    在这里插入图片描述

通过这种方式,可以将 RDB 和 AOF 的优点结合起来,从而达到更加全面的数据保护和恢复能力。同时也需要注意,这种方式会带来一定的存储和性能开销,需要根据实际情况进行权衡和优化。

好了,本次的分享就到这儿,下次见,如果对本文有疑惑或者争议都可以评论区留言。看到我会回复。大家如果想了解其他的,可以看我的专栏其他文章如下。

3. Redis从入门到精通系列文章

《Redis从入门到精通【进阶篇】之持久化RDB》
《Redis从入门到精通【高阶篇】之底层数据结构字典(Dictionary)详解》
《Redis从入门到精通【高阶篇】之底层数据结构快表QuickList详解》
《Redis从入门到精通【高阶篇】之底层数据结构简单动态字符串(SDS)详解》
《Redis从入门到精通【高阶篇】之底层数据结构压缩列表(ZipList)详解》
《Redis从入门到精通【进阶篇】之数据类型Stream详解和使用示例》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/712983.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Axure设计之滑动验证组件(动态面板)

一、案例效果 1、默认显示滑块、背景及提示文案&#xff1b; 2、滑块仅允许向右水平拖动&#xff0c;且不能超过背景区域&#xff1b; 3、滑块移动过程中&#xff0c;左侧区域样式跟随变化&#xff0c;右侧区域保持不变&#xff1b; 4、滑块为未拖动到最右侧时释放&#xff…

后书《乡村振兴战略下传统村落文化旅游设计》,交浅而言深者,愚也

后书《乡村振兴战略下传统村落文化旅游设计》&#xff0c;交浅而言深者&#xff0c;愚也

初学Spring boot (六) 自定义starter

学习回顾&#xff1a;初学Spring boot &#xff08;五&#xff09; 自动配置原理 自定义Starter 我们分析完毕了源码以及自动装配的过程&#xff0c;我们可以尝试自定义一个启动器来玩玩&#xff01; 1、说明 启动器模块是一个 空 jar 文件&#xff0c;仅提供辅助性依赖管理&am…

B2a实例学习记录

B2a简介 只是把hit存到了hitcollection&#xff0c;只是统计了各自event结果&#xff0c;将每次event的运行结果存起来了&#xff0c;并没有做总的求和 如何在B2a的基础上&#xff0c;实现对某一个chamber的能量的累加 1 hit和SD&#xff08;sensitive detector) 每一个s…

越权访问漏洞

越权漏洞 越权访问漏洞示意图 一、越权访问漏洞简介 1.类型 水平越权&#xff1a;通过更换的某个ID之类的身份标识&#xff0c;从而使A账号获得&#xff08;增删查改&#xff09;B账号的数据 垂直越权&#xff1a;使用低权限身份的账号&#xff0c;发送高权限账号的请求&…

Java——内部类

一、成员内部类 类中的东西都是成员&#xff0c; 1.1、成员内部类定义 定义成员内部类的格式&#xff1a; class OuterClass {//外部类class InnerClass{//内部类&#xff08;内部类实际是外部类的一个属性&#xff09;} }示例 public class Outer {private static int r…

使用Selenium-PO设计模式提高Web自动化测试效率

PO&#xff08;page object&#xff09;设计模式是在自动化中已经流行起来的一种易于维护和减少代码的设计模式。在自动化测试中&#xff0c;PO对象作为一个与页面交互的接口。测试中需要与页面的UI进行交互时&#xff0c;便调用PO的方法。这样做的好处是&#xff0c;如果页面的…

.Net之AOP - 使用Fody的代码静态编织实现AOP

简介&#xff08;好久没写博客了&#xff09; 万物皆可AOP&#xff0c;本篇文章主要讲解在.Net7中使用Fody的代码静态编织实现AOP。 一、前言 AOP AOP是指面向切面编程 &#xff08;Aspect Oriented Programming&#xff09;&#xff0c;相信大家都再熟悉不过了&#xff0c;…

上海市“小巨人”竞争力指数榜单发布!上海三思居全市总榜第四!分项NO.1!

6月25日下午&#xff0c;2022 年上海市专精特新“小巨人”市场竞争力指数榜单”在上海市上海联合产权交易所正式发布。上海三思电子工程有限公司多项荣登指数榜单&#xff1a; ●指数总榜TOP10&#xff0c;上海三思以83.69的高分位居全市8072家“小巨人”企业第四位&#xff0…

军用电子设备人工智能时代正在到来

源自&#xff1a; 战略前沿技术 Al的应用快速增长 更大的图景 人工智能技术的多面性 增强作战人员能力 数据依赖 人工智能和机器学习:前面的路 对人工智能应用保持警惕 声明:公众号转载的文章及图片出于非商业性的教育和科研目的供大家参考和探讨&#xff0c;并不意味着支持其观…

vue+elementui实现联想购物商城,样式美观大方

目录 一、首页效果图对比 1.联想商城首页截图&#xff1a; 2.作者项目效果图&#xff1a; 二、商品详情效果图对比 1.联想官方截图&#xff1a; 2.作者项目截图&#xff1a; 三、项目实现 1.数据分离维护 2.首页推荐列表数据处理 3.商品详情数据动态获取完成交互 4.商品详…

MySQL原理探索——20幻读

20 幻读是什么&#xff1f;幻读会造成什么后果&#xff1f; 在上一篇文章最后&#xff0c;遗留了一个关于加锁规则的问题。今天&#xff0c;我们就从这个问题说起。 为了便于说明问题&#xff0c;这篇文章&#xff0c;我们就先使用一个小一点的表。建表和初始化语句如下&#…

【MySQL】MySQL PHP 语法,PHP MySQL 简介,查询,下载 MySQL 数据库, SQL 教程

作者简介&#xff1a; 辭七七&#xff0c;目前大一&#xff0c;正在学习C/C&#xff0c;Java&#xff0c;Python等 作者主页&#xff1a; 七七的个人主页 文章收录专栏&#xff1a; 七七的闲谈 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01;&#x1f496;&#x1f…

探究Vue源码:mustache模板引擎(4) 了解mustache转换概念,简述tokens转换

上文 探究Vue源码:mustache模板引擎(3) 通过编写简单正则了解mustache转换思路我们用正则表达式构建了一个简易版的render模板编译函数 但是 我们有特意强调过 mustache的render函数并非用简单正则实现的 因为这样无法实现循环和一些比较复杂的逻辑处理 它的实现基理可以参考这…

Ubuntu系统安装JDK教程

今天新买了一台阿里云服务器&#xff0c;因为centos 不提供了更新支持&#xff0c;所以Linux系统选择了Ubuntu 系统&#xff0c;今天就出一期 Ubuntu上安装的一系列教程&#xff0c;今天就先从JDK开始。 Ubuntu系统安装JDK教程 1、 jdk下载2、安装 lrzsz 命令 &#xff08;仅限…

FreeRTOS_系统内核控制函数

目录 1. 系统内核控制函数预览 2. 系统内核函数详解 2.1 函数 taskYIELD() 2.2 函数 taskENTER_CRITICAL() 2.3 函数 taskEXIT_CRITICAL() 2.4 函数 taskENTER_CRITICAL_FROM_ISR() 2.5 函数 taskEXIT_CRITICAL_FROM_ISR() 2.6 函数 taskDISABLE_INTERRUPTS() 2.7 函数…

1. 数字mic驱动分析

一般遇到的音频硬件都是这样的 由于项目不需要播放只需要录音&#xff0c;于是将模拟的mic换成了数字mic&#xff0c;直接通过i2s连接到soc 由于还要使用alsa架构进行录音&#xff0c;所以这里不能简单的写个代码读i2s数据&#xff0c;需要虚拟出一个codec 上面就是我们这次要分…

第九十六天学习记录:Linux基础:实用操作Ⅰ

注&#xff1a;第一张图与学习记录无关&#xff0c;是为了参与CSDN的AI绘图活动 CtrlC强制停止 1、Linux某些程序的运行&#xff0c;如果想要强制停止它&#xff0c;可以使用快捷键CtrlC中止 2、在命令输入错误时&#xff0c;也可以通过快捷键CtrlC快速退出当前输入 CtrlD…

projection介绍及EPSG:4326和EPSG:3857的投射转换

每个地图数据在Web端加载显示时&#xff0c;都需要设罝其投影坐标系。众所周知&#xff0c;地图是不规则的椭球体&#xff0c;如果我们将其展开到二维平面上&#xff0c;会发现地图与实际情况有出入。所以&#xff0c;人们提出 投影的方式来尽量减小失真的程度。 openlayers的…

技术驱动美丽:动态贴纸与美颜SDK的应用实践与创新

随着科技的迅速发展&#xff0c;智能手机的普及以及社交媒体的兴起&#xff0c;人们对于美颜和创意贴纸的需求日益增长。动态贴纸和美颜技术的应用已经成为当今互联网时代的一种趋势。本文将重点讨论动态贴纸与美颜SDK的应用实践与创新&#xff0c;探讨它们对美容美妆行业和社交…