Redis源码剖析之AOF

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: Redis源码剖析之AOF

书接上回,上回我们详细讲解了Redis的RDB机制,RDB解决了redis数据持久化一部分的问题,为什么说一部分?因为rdb是redis中某一时刻的快照,那么在这次快照后如果数据有新的变更,它是不会被持久化下来的,必须得等到下次rdb备份。然而,生成rdb是和消耗性能的,所以它就不适合很频繁生成。Redis为了弥补这一不足提供了AOF。

AOF的全称是AppendOnlyFile,源码在aof.c。其实关键就是Append(追加),核心原理很简单,就是如果执行完命令(set,del,expire……)后,发现有数据变动,就将这次操作作为一条日志记录到aof文件里,如果有宕机就重新加载aof文件,重放所有的改动命令就可以恢复数据了。只要日志被完整刷到了磁盘上,数据就不会丢失。

配置

AOF的配置比较简单,只有如下几项。

复制

appendonly no  # aof开关,默认关闭
appendfilename "appendonly.aof"  # 保存的文件名,默认appendonly.aof
# 有三种刷数据的策略
appendfsync always  # always是只要有数据改动,就把数据刷到磁盘里,最安全但性能也最差
appendfsync everysec  # 每隔一秒钟刷一次数据,数据安全性和性能折中,这也是redis默认和推荐的配置。 
appendfsync no # 不主动刷,什么时候数据刷到磁盘里取决于操作系统,在大多数Linux系统中每30秒提交一次,性能最好,但数据安全性最差。

源码

AOF的触发

aof如何实现,又是怎么被触发的,让我们详细看下源码。

server.c中的void call(client *c, int flags) 是redis接受到client请求后处理请求的入口,其中会检测Redis中的数据有没有发生变化。如果有变化就会执行propagate()函数。

复制

dirty = server.dirty;
    prev_err_count = server.stat_total_error_replies;
    updateCachedTime(0);
    elapsedStart(&call_timer);
    c->cmd->proc(c); // 执行命令
    const long duration = elapsedUs(call_timer);
    c->duration = duration;
    dirty = server.dirty-dirty;
    if (dirty < 0) dirty = 0;

复制

void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc,
               int flags)
{
    if (server.in_exec && !server.propagate_in_transaction)
        execCommandPropagateMulti(dbid);
    /* This needs to be unreachable since the dataset should be fixed during 
     * client pause, otherwise data may be lossed during a failover. */
    serverAssert(!(areClientsPaused() && !server.client_pause_in_transaction));
    if (server.aof_state != AOF_OFF && flags & PROPAGATE_AOF)
        feedAppendOnlyFile(cmd,dbid,argv,argc); // 如果aof开启了,就会向aof传播该命令。
    if (flags & PROPAGATE_REPL)
        replicationFeedSlaves(server.slaves,dbid,argv,argc);
}

propagate函数的作用就是将带来数据改动的命令传播给slave和AOF,这里我们只关注AOF,我们来详细看下feedAppendOnlyFile()函数。

AOF数据生成

复制

void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) {
    sds buf = sdsempty();
    /* The DB this command was targeting is not the same as the last command
     * we appended. To issue a SELECT command is needed. */
    if (dictid != server.aof_selected_db) {
        char seldb[64];
        snprintf(seldb,sizeof(seldb),"%d",dictid);
        buf = sdscatprintf(buf,"*2\r\n$6\r\nSELECT\r\n$%lu\r\n%s\r\n",
            (unsigned long)strlen(seldb),seldb);
        server.aof_selected_db = dictid;
    }
    if (cmd->proc == expireCommand || cmd->proc == pexpireCommand ||
        cmd->proc == expireatCommand) {
        /* 把 EXPIRE/PEXPIRE/EXPIREAT 命令转化为 PEXPIREAT 命令*/
        buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]);
    } else if (cmd->proc == setCommand && argc > 3) {
        robj *pxarg = NULL;
        /* When SET is used with EX/PX argument setGenericCommand propagates them with PX millisecond argument.
         * So since the command arguments are re-written there, we can rely here on the index of PX being 3. */
        if (!strcasecmp(argv[3]->ptr, "px")) {
            pxarg = argv[4];
        }
        /* 把set命令的expired所带的相对时间转化为绝对时间(ms). */
        if (pxarg) {
            robj *millisecond = getDecodedObject(pxarg);
            long long when = strtoll(millisecond->ptr,NULL,10);
            when += mstime();
            decrRefCount(millisecond);
            robj *newargs[5];
            newargs[0] = argv[0];
            newargs[1] = argv[1];
            newargs[2] = argv[2];
            newargs[3] = shared.pxat;
            newargs[4] = createStringObjectFromLongLong(when);
            buf = catAppendOnlyGenericCommand(buf,5,newargs);
            decrRefCount(newargs[4]);
        } else {
            buf = catAppendOnlyGenericCommand(buf,argc,argv);
        }
    } else {
        /* 其他的命令都不需要转化 */
        buf = catAppendOnlyGenericCommand(buf,argc,argv);
    }
    /* 追加到AOF缓冲区。在重新进入事件循环之前,数据将被刷新到磁盘上,因此在客户端在执行前就会得到回复。*/
    if (server.aof_state == AOF_ON)
        server.aof_buf = sdscatlen(server.aof_buf,buf,sdslen(buf));
    /* 如果后台正在进行AOF重写,我们希望将子数据库和当前数据库之间的差异累积到缓冲区中,
     * 以便在子进程执行其工作时,我们可以将这些差异追加到新的只追加文件中。 */
    if (server.child_type == CHILD_TYPE_AOF)
        aofRewriteBufferAppend((unsigned char*)buf,sdslen(buf));
    sdsfree(buf);
}

这里没有啥太复杂的逻辑,就是将命令转化为RESP协议格式的字符串(RESP协议后续会详解),然后追加到server.aof_buf中,这时候AOF数据还都在缓冲区中,并没有写入到磁盘中,那buf中的数据何时写入磁盘呢?

刷数据

刷数据的核心代码在flushAppendOnlyFile()中,flushAppendOnlyFile在serverCron、beforeSleep和prepareForShutdown中都有被调用,它的作用就是将缓冲区的数据写到磁盘中,代码比较长且复杂,但大部分都是异常处理和性能监控,忽略掉这部分后代码也比较容易理解,这里就不再罗列了,详见aof.c

RDB vs AOF

最后,我们来对比下RDB和AOF,他们各自都有啥优缺点,该如何选用。

RDB的优势

  1. RDB是压缩的后紧凑数据格式,比较很适合备份,
  2. 同样的数据量下,rdb的文件大小会很小,比较适合传输和数据恢复。
  3. RDB对Redis的读写性能影响小,生成RDB的时redis主进程会fork出一个子进程,不会影响到主进程的读写。
  4. RDB数据加载更快,恢复起来更快。

RDB的缺点

  1. RDB是定期备份,如果备份前发生宕机,数据可能会丢失。
  2. RDB的生成依赖于linux的fork,如果数据量比较大的话,很影响服务器性能。

AOF的优势

  1. AOF是持续性备份,可以尽可能保证数据不丢失。
  2. Redis太大时,Redis可以在后台自动重写AOF。重写是完全安全的,因为Redis继续追加到旧文件时,会生成一个全新的文件,其中包含创建当前数据集所需的最少操作集,一旦准备好第二个文件,Redis会切换这两个文件并开始追加到新的那一个。
  3. AOF文件格式简单,易于解析。

AOF的缺点

  1. 对于同一数据集,AOF文件大小通常大于等效的RDB文件。
  2. 如果使用fsync策略,AOF可能比RDB慢。

RDB和AOF该如何选

如果是要求极致的性能,但对数据恢复不敏感,二者可以都不要,如果是关注性能且关注数据可用性,但不要求数据完整性,可以选用RDB。如果说非常关注数据完整性和宕机恢复的能力,可以RDB+AOF同时开启。

参考资料

  1. Redis persistence demystified
  2. Redis Persistence

本文是Redis源码剖析系列博文,同时也有与之对应的Redis中文注释版,有想深入学习Redis的同学,欢迎star和关注。

Redis中文注解版仓库:https://github.com/xindoo/Redis

Redis源码剖析专栏:https://zxs.io/s/1h

如果觉得本文对你有用,欢迎一键三连

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
11天前
|
存储 NoSQL Redis
Redis 持久化揭秘:选择 RDB、AOF 还是混合持久化?
Redis 是一个内存数据库,意味着它主要将数据存储在内存中,从而能够提供极高的性能。然而,作为内存数据库,Redis 默认情况下的数据不会永久保存。为了确保数据在重启或故障后能够恢复,Redis 提供了几种 **持久化机制**。这些机制允许 Redis 将内存中的数据保存到硬盘上,从而实现数据持久化。
74 22
Redis 持久化揭秘:选择 RDB、AOF 还是混合持久化?
|
1月前
|
监控 NoSQL 测试技术
【赵渝强老师】Redis的AOF数据持久化
Redis 是内存数据库,提供数据持久化功能,支持 RDB 和 AOF 两种方式。AOF 以日志形式记录每个写操作,支持定期重写以压缩文件。默认情况下,AOF 功能关闭,需在 `redis.conf` 中启用。通过 `info` 命令可监控 AOF 状态。AOF 重写功能可有效控制文件大小,避免性能下降。
|
2月前
|
存储 缓存 NoSQL
大数据-45 Redis 持久化概念 RDB AOF机制 持久化原因和对比
大数据-45 Redis 持久化概念 RDB AOF机制 持久化原因和对比
55 2
大数据-45 Redis 持久化概念 RDB AOF机制 持久化原因和对比
|
3月前
|
存储 缓存 NoSQL
Redis中的rdb和aof
本文深入探讨了Redis的持久化机制,包括RDB和AOF两种方式。详细解释了RDB的工作原理、优势和劣势,以及AOF的实现原理、配置选项、文件重写机制和三种数据同步方式,还介绍了AOF文件修复工具redis-check-aof的使用,并通过实例展示了如何开启和配置AOF持久化方式。
Redis中的rdb和aof
|
2月前
|
缓存 NoSQL Ubuntu
大数据-39 Redis 高并发分布式缓存 Ubuntu源码编译安装 云服务器 启动并测试 redis-server redis-cli
大数据-39 Redis 高并发分布式缓存 Ubuntu源码编译安装 云服务器 启动并测试 redis-server redis-cli
65 3
|
2月前
|
存储 缓存 NoSQL
大数据-46 Redis 持久化 RDB AOF 配置参数 混合模式 具体原理 触发方式 优点与缺点
大数据-46 Redis 持久化 RDB AOF 配置参数 混合模式 具体原理 触发方式 优点与缺点
74 1
|
3月前
|
存储 NoSQL Redis
SpringCloud基础7——Redis分布式缓存,RDB,AOF持久化+主从+哨兵+分片集群
Redis持久化、RDB和AOF方案、Redis主从集群、哨兵、分片集群、散列插槽、自动手动故障转移
SpringCloud基础7——Redis分布式缓存,RDB,AOF持久化+主从+哨兵+分片集群
|
4月前
|
缓存 NoSQL Linux
【Azure Redis 缓存】Windows和Linux系统本地安装Redis, 加载dump.rdb中数据以及通过AOF日志文件追加数据
【Azure Redis 缓存】Windows和Linux系统本地安装Redis, 加载dump.rdb中数据以及通过AOF日志文件追加数据
145 1
【Azure Redis 缓存】Windows和Linux系统本地安装Redis, 加载dump.rdb中数据以及通过AOF日志文件追加数据
|
3月前
|
存储 缓存 NoSQL
深入探究Redis的AOF持久化:保障数据安全与恢复性能的关键机制
深入探究Redis的AOF持久化:保障数据安全与恢复性能的关键机制
100 0
|
4月前
|
Web App开发 前端开发 关系型数据库
基于SpringBoot+Vue+Redis+Mybatis的商城购物系统 【系统实现+系统源码+答辩PPT】
这篇文章介绍了一个基于SpringBoot+Vue+Redis+Mybatis技术栈开发的商城购物系统,包括系统功能、页面展示、前后端项目结构和核心代码,以及如何获取系统源码和答辩PPT的方法。