Redis 7.0 Multi Part AOF的设计和实现

简介:本文将详解Redis中现有AOF机制的一些不足以及Redis 7.0中引入的Multi Part AOF的设计和实现细节。

Redis 作为一种非常流行的内存数据库,通过将数据保存在内存中,Redis 得以拥有极高的读写性能。但是一旦进程退出,Redis 的数据就会全部丢失。

为了解决这个问题,Redis 提供了 RDB 和 AOF 两种持久化方案,将内存中的数据保存到磁盘中,避免数据丢失。本文将重点讨论AOF持久化方案,以及其存在的一些问题,并探讨在Redis 7.0 (已发布RC1) 中Multi Part AOF(下文简称为MP-AOF,本特性由阿里云数据库Tair团队贡献)设计和实现细节。

AOF

AOF( append only file )持久化以独立日志文件的方式记录每条写命令,并在 Redis 启动时回放 AOF 文件中的命令以达到恢复数据的目的。

由于AOF会以追加的方式记录每一条redis的写命令,因此随着Redis处理的写命令增多,AOF文件也会变得越来越大,命令回放的时间也会增多,为了解决这个问题,Redis引入了AOF rewrite机制(下文称之为AOFRW)。AOFRW会移除AOF中冗余的写命令,以等效的方式重写、生成一个新的AOF文件,来达到减少AOF文件大小的目的。

AOFRW

图1展示的是AOFRW的实现原理。当AOFRW被触发执行时,Redis首先会fork一个子进程进行后台重写操作,该操作会将执行fork那一刻Redis的数据快照全部重写到一个名为temp-rewriteaof-bg-pid.aof的临时AOF文件中。

由于重写操作为子进程后台执行,主进程在AOF重写期间依然可以正常响应用户命令。因此,为了让子进程最终也能获取重写期间主进程产生的增量变化,主进程除了会将执行的写命令写入aof_buf,还会写一份到aof_rewrite_buf中进行缓存。在子进程重写的后期阶段,主进程会将aof_rewrite_buf中累积的数据使用pipe发送给子进程,子进程会将这些数据追加到临时AOF文件中(详细原理可参考这里)。

当主进程承接了较大的写入流量时,aof_rewrite_buf中可能会堆积非常多的数据,导致在重写期间子进程无法将aof_rewrite_buf中的数据全部消费完。此时,aof_rewrite_buf剩余的数据将在重写结束时由主进程进行处理。

当子进程完成重写操作并退出后,主进程会在backgroundRewriteDoneHandler 中处理后续的事情。首先,将重写期间aof_rewrite_buf中未消费完的数据追加到临时AOF文件中。其次,当一切准备就绪时,Redis会使用rename 操作将临时AOF文件原子的重命名为server.aof_filename,此时原来的AOF文件会被覆盖。至此,整个AOFRW流程结束。

图1 AOFRW实现原理

AOFRW存在的问题

内存开销

由图1可以看到,在AOFRW期间,主进程会将fork之后的数据变化写进aof_rewrite_buf中,aof_rewrite_buf和aof_buf中的内容绝大部分都是重复的,因此这将带来额外的内存冗余开销。

在Redis INFO中的aof_rewrite_buffer_length字段可以看到当前时刻aof_rewrite_buf占用的内存大小。如下面显示的,在高写入流量下aof_rewrite_buffer_length几乎和aof_buffer_length占用了同样大的内存空间,几乎浪费了一倍的内存。

aof_pending_rewrite:0
aof_buffer_length:35500
aof_rewrite_buffer_length:34000
aof_pending_bio_fsync:0

当aof_rewrite_buf占用的内存大小超过一定阈值时,我们将在Redis日志中看到如下信息。可以看到,aof_rewrite_buf占用了100MB的内存空间且主进程和子进程之间传输了2135MB的数据(子进程在通过pipe读取这些数据时也会有内部读buffer的内存开销)。对于内存型数据库Redis而言,这是一笔不小的开销。

3351:M 25 Jan 2022 09:55:39.655 * Background append only file rewriting started by pid 6817
3351:M 25 Jan 2022 09:57:51.864 * AOF rewrite child asks to stop sending diffs.
6817:C 25 Jan 2022 09:57:51.864 * Parent agreed to stop sending diffs. Finalizing AOF...
6817:C 25 Jan 2022 09:57:51.864 * Concatenating 2135.60 MB of AOF diff received from parent.
3351:M 25 Jan 2022 09:57:56.545 * Background AOF buffer size: 100 MB

AOFRW带来的内存开销有可能导致Redis内存突然达到maxmemory限制,从而影响正常命令的写入,甚至会触发操作系统限制被OOM Killer杀死,导致Redis不可服务。

CPU开销

CPU的开销主要有三个地方,分别解释如下:

  1. 在AOFRW期间,主进程需要花费CPU时间向aof_rewrite_buf写数据,并使用eventloop事件循环向子进程发送aof_rewrite_buf中的数据:
/* Append data to the AOF rewrite buffer, allocating new blocks if needed. */
void aofRewriteBufferAppend(unsigned char *s, unsigned long len) {// 此处省略其他细节.../* Install a file event to send data to the rewrite child if there is* not one already. */if (!server.aof_stop_sending_diff &&aeGetFileEvents(server.el,server.aof_pipe_write_data_to_child) == 0){aeCreateFileEvent(server.el, server.aof_pipe_write_data_to_child,AE_WRITABLE, aofChildWriteDiffData, NULL);} // 此处省略其他细节...
}
  1. 在子进程执行重写操作的后期,会循环读取pipe中主进程发送来的增量数据,然后追加写入到临时AOF文件:
int rewriteAppendOnlyFile(char *filename) {// 此处省略其他细节.../* Read again a few times to get more data from the parent.* We can't read forever (the server may receive data from clients* faster than it is able to send data to the child), so we try to read* some more data in a loop as soon as there is a good chance more data* will come. If it looks like we are wasting time, we abort (this* happens after 20 ms without new data). */int nodata = 0;mstime_t start = mstime();while(mstime()-start < 1000 && nodata < 20) {if (aeWait(server.aof_pipe_read_data_from_parent, AE_READABLE, 1) <= 0){nodata++;continue;}nodata = 0; /* Start counting from zero, we stop on N *contiguous*timeouts. */aofReadDiffFromParent();}// 此处省略其他细节...
}
  1. 在子进程完成重写操作后,主进程会在backgroundRewriteDoneHandler 中进行收尾工作。其中一个任务就是将在重写期间aof_rewrite_buf中没有消费完成的数据写入临时AOF文件。如果aof_rewrite_buf中遗留的数据很多,这里也将消耗CPU时间。
void backgroundRewriteDoneHandler(int exitcode, int bysignal) {// 此处省略其他细节.../* Flush the differences accumulated by the parent to the rewritten AOF. */if (aofRewriteBufferWrite(newfd) == -1) {serverLog(LL_WARNING,"Error trying to flush the parent diff to the rewritten AOF: %s", strerror(errno));close(newfd);goto cleanup;}// 此处省略其他细节...
}

AOFRW带来的CPU开销可能会造成Redis在执行命令时出现RT上的抖动,甚至造成客户端超时的问题。

磁盘IO开销

如前文所述,在AOFRW期间,主进程除了会将执行过的写命令写到aof_buf之外,还会写一份到aof_rewrite_buf中。aof_buf中的数据最终会被写入到当前使用的旧AOF文件中,产生磁盘IO。同时,aof_rewrite_buf中的数据也会被写入重写生成的新AOF文件中,产生磁盘IO。因此,同一份数据会产生两次磁盘IO。

代码复杂度

Redis使用下面所示的六个pipe进行主进程和子进程之间的数据传输和控制交互,这使得整个AOFRW逻辑变得更为复杂和难以理解。

/* AOF pipes used to communicate between parent and child during rewrite. */int aof_pipe_write_data_to_child;int aof_pipe_read_data_from_parent;int aof_pipe_write_ack_to_parent;int aof_pipe_read_ack_from_child;int aof_pipe_write_ack_to_child;int aof_pipe_read_ack_from_parent;

MP-AOF实现

方案概述

顾名思义,MP-AOF就是将原来的单个AOF文件拆分成多个AOF文件。在MP-AOF中,我们将AOF分为三种类型,分别为:

  • BASE:表示基础AOF,它一般由子进程通过重写产生,该文件最多只有一个。
  • INCR:表示增量AOF,它一般会在AOFRW开始执行时被创建,该文件可能存在多个。
  • HISTORY:表示历史AOF,它由BASE和INCR AOF变化而来,每次AOFRW成功完成时,本次AOFRW之前对应的BASE和INCR AOF都将变为HISTORY,HISTORY类型的AOF会被Redis自动删除。

为了管理这些AOF文件,我们引入了一个manifest(清单)文件来跟踪、管理这些AOF。同时,为了便于AOF备份和拷贝,我们将所有的AOF文件和manifest文件放入一个单独的文件目录中,目录名由appenddirname配置(Redis 7.0新增配置项)决定。

图2 MP-AOF Rewrite原理

图2展示的是在MP-AOF中执行一次AOFRW的大致流程。在开始时我们依然会fork一个子进程进行重写操作,在主进程中,我们会同时打开一个新的INCR类型的AOF文件,在子进程重写操作期间,所有的数据变化都会被写入到这个新打开的INCR AOF中。子进程的重写操作完全是独立的,重写期间不会与主进程进行任何的数据和控制交互,最终重写操作会产生一个BASE AOF。新生成的BASE AOF和新打开的INCR AOF就代表了当前时刻Redis的全部数据。AOFRW结束时,主进程会负责更新manifest文件,将新生成的BASE AOF和INCR AOF信息加入进去,并将之前的BASE AOF和INCR  AOF标记为HISTORY(这些HISTORY AOF会被Redis异步删除)。一旦manifest文件更新完毕,就标志整个AOFRW流程结束。

由图2可以看到,我们在AOFRW期间不再需要aof_rewrite_buf,因此去掉了对应的内存消耗。同时,主进程和子进程之间也不再有数据传输和控制交互,因此对应的CPU开销也全部去掉。对应的,前文提及的六个pipe及其对应的代码也全部删除,使得AOFRW逻辑更加简单清晰。

关键实现

Manifest

在内存中的表示

MP-AOF强依赖manifest文件,manifest在内存中表示为如下结构体,其中:

  • aofInfo:表示一个AOF文件信息,当前仅包括文件名、文件序号和文件类型
  • base_aof_info:表示BASE AOF信息,当不存在BASE AOF时,该字段为NULL
  • incr_aof_list:用于存放所有INCR AOF文件的信息,所有的INCR AOF都会按照文件打开顺序排放
  • history_aof_list:用于存放HISTORY AOF信息,history_aof_list中的元素都是从base_aof_info和incr_aof_list中move过来的
typedef struct {sds           file_name;  /* file name */long long     file_seq;   /* file sequence */aof_file_type file_type;  /* file type */
} aofInfo;
typedef struct {aofInfo     *base_aof_info;       /* BASE file information. NULL if there is no BASE file. */list        *incr_aof_list;       /* INCR AOFs list. We may have multiple INCR AOF when rewrite fails. */list        *history_aof_list;    /* HISTORY AOF list. When the AOFRW success, The aofInfo contained in`base_aof_info` and `incr_aof_list` will be moved to this list. Wewill delete these AOF files when AOFRW finish. */long long   curr_base_file_seq;   /* The sequence number used by the current BASE file. */long long   curr_incr_file_seq;   /* The sequence number used by the current INCR file. */int         dirty;                /* 1 Indicates that the aofManifest in the memory is inconsistent withdisk, we need to persist it immediately. */
} aofManifest;

为了便于原子性修改和回滚操作,我们在redisServer结构中使用指针的方式引用aofManifest。

struct redisServer {// 此处省略其他细节...aofManifest *aof_manifest;       /* Used to track AOFs. */// 此处省略其他细节...
}

在磁盘上的表示

Manifest本质就是一个包含多行记录的文本文件,每一行记录对应一个AOF文件信息,这些信息通过key/value对的方式展示,便于Redis处理、易于阅读和修改。下面是一个可能的manifest文件内容:

file appendonly.aof.1.base.rdb seq 1 type b
file appendonly.aof.1.incr.aof seq 1 type i
file appendonly.aof.2.incr.aof seq 2 type i

Manifest格式本身需要具有一定的扩展性,以便将来添加或支持其他的功能。比如可以方便的支持新增key/value和注解(类似AOF中的注解),这样可以保证较好的forward compatibility。

file appendonly.aof.1.base.rdb seq 1 type b newkey newvalue
file appendonly.aof.1.incr.aof type i seq 1 
# this is annotations
seq 2 type i file appendonly.aof.2.incr.aof

文件命名规则

在MP-AOF之前,AOF的文件名为appendfilename参数的设置值(默认为appendonly.aof)。

在MP-AOF中,我们使用basename.suffix的方式命名多个AOF文件。其中,appendfilename配置内容将作为basename部分,suffix则由三个部分组成,格式为seq.type.format ,其中:

  • seq为文件的序号,由1开始单调递增,BASE和INCR拥有独立的文件序号
  • type为AOF的类型,表示这个AOF文件是BASE还是INCR
  • format用来表示这个AOF内部的编码方式,由于Redis支持RDB preamble机制,因此BASE AOF可能是RDB格式编码也可能是AOF格式编码:
#define BASE_FILE_SUFFIX           ".base"
#define INCR_FILE_SUFFIX           ".incr"
#define RDB_FORMAT_SUFFIX          ".rdb"
#define AOF_FORMAT_SUFFIX          ".aof"
#define MANIFEST_NAME_SUFFIX       ".manifest"

因此,当使用appendfilename默认配置时,BASE、INCR和manifest文件的可能命名如下:

appendonly.aof.1.base.rdb // 开启RDB preamble
appendonly.aof.1.base.aof // 关闭RDB preamble
appendonly.aof.1.incr.aof
appendonly.aof.2.incr.aof

兼容老版本升级

由于MP-AOF强依赖manifest文件,Redis启动时会严格按照manifest的指示加载对应的AOF文件。但是在从老版本Redis(指Redis 7.0之前的版本)升级到Redis 7.0时,由于此时并无manifest文件,因此如何让Redis正确识别这是一个升级过程并正确、安全的加载旧AOF是一个必须支持的能力。

识别能力是这一重要过程的首要环节,在真正加载AOF文件之前,我们会检查Redis工作目录下是否存在名为server.aof_filename的AOF文件。如果存在,那说明我们可能在从一个老版本Redis执行升级,接下来,我们会继续判断,当满足下面三种情况之一时我们会认为这是一个升级启动:

  1. 如果appenddirname目录不存在
  2. 或者appenddirname目录存在,但是目录中没有对应的manifest清单文件
  3. 如果appenddirname目录存在且目录中存在manifest清单文件,且清单文件中只有BASE AOF相关信息,且这个BASE AOF的名字和server.aof_filename相同,且appenddirname目录中不存在名为server.aof_filename的文件
/* Load the AOF files according the aofManifest pointed by am. */
int loadAppendOnlyFiles(aofManifest *am) {// 此处省略其他细节.../* If the 'server.aof_filename' file exists in dir, we may be starting* from an old redis version. We will use enter upgrade mode in three situations.** 1. If the 'server.aof_dirname' directory not exist* 2. If the 'server.aof_dirname' directory exists but the manifest file is missing* 3. If the 'server.aof_dirname' directory exists and the manifest file it contains*    has only one base AOF record, and the file name of this base AOF is 'server.aof_filename',*    and the 'server.aof_filename' file not exist in 'server.aof_dirname' directory* */if (fileExist(server.aof_filename)) {if (!dirExists(server.aof_dirname) ||(am->base_aof_info == NULL && listLength(am->incr_aof_list) == 0) ||(am->base_aof_info != NULL && listLength(am->incr_aof_list) == 0 &&!strcmp(am->base_aof_info->file_name, server.aof_filename) && !aofFileExist(server.aof_filename))){aofUpgradePrepare(am);}}// 此处省略其他细节...}

一旦被识别为这是一个升级启动,我们会使用aofUpgradePrepare 函数进行升级前的准备工作。

升级准备工作主要分为三个部分:

  1. 使用server.aof_filename作为文件名来构造一个BASE AOF信息
  2. 将该BASE AOF信息持久化到manifest文件
  3. 使用rename 将旧AOF文件移动到appenddirname目录中
void aofUpgradePrepare(aofManifest *am) {// 此处省略其他细节.../* 1. Manually construct a BASE type aofInfo and add it to aofManifest. */if (am->base_aof_info) aofInfoFree(am->base_aof_info);aofInfo *ai = aofInfoCreate();ai->file_name = sdsnew(server.aof_filename);ai->file_seq = 1;ai->file_type = AOF_FILE_TYPE_BASE;am->base_aof_info = ai;am->curr_base_file_seq = 1;am->dirty = 1;/* 2. Persist the manifest file to AOF directory. */if (persistAofManifest(am) != C_OK) {exit(1);}/* 3. Move the old AOF file to AOF directory. */sds aof_filepath = makePath(server.aof_dirname, server.aof_filename);if (rename(server.aof_filename, aof_filepath) == -1) {sdsfree(aof_filepath);exit(1);;}// 此处省略其他细节...
}

升级准备操作是Crash Safety的,以上三步中任何一步发生Crash我们都能在下一次的启动中正确的识别并重试整个升级操作。

多文件加载及进度计算

Redis在加载AOF时会记录加载的进度,并通过Redis INFO的loading_loaded_perc字段展示出来。在MP-AOF中,loadAppendOnlyFiles 函数会根据传入的aofManifest进行AOF文件加载。在进行加载之前,我们需要提前计算所有待加载的AOF文件的总大小,并传给startLoading 函数,然后在loadSingleAppendOnlyFile 中不断的上报加载进度。

接下来,loadAppendOnlyFiles 会根据aofManifest依次加载BASE AOF和INCR AOF。当前加载完所有的AOF文件,会使用stopLoading 结束加载状态。

int loadAppendOnlyFiles(aofManifest *am) {// 此处省略其他细节.../* Here we calculate the total size of all BASE and INCR files in* advance, it will be set to `server.loading_total_bytes`. */total_size = getBaseAndIncrAppendOnlyFilesSize(am);startLoading(total_size, RDBFLAGS_AOF_PREAMBLE, 0);/* Load BASE AOF if needed. */if (am->base_aof_info) {aof_name = (char*)am->base_aof_info->file_name;updateLoadingFileName(aof_name);loadSingleAppendOnlyFile(aof_name);}/* Load INCR AOFs if needed. */if (listLength(am->incr_aof_list)) {listNode *ln;listIter li;listRewind(am->incr_aof_list, &li);while ((ln = listNext(&li)) != NULL) {aofInfo *ai = (aofInfo*)ln->value;aof_name = (char*)ai->file_name;updateLoadingFileName(aof_name);loadSingleAppendOnlyFile(aof_name);}}server.aof_current_size = total_size;server.aof_rewrite_base_size = server.aof_current_size;server.aof_fsync_offset = server.aof_current_size;stopLoading();// 此处省略其他细节...
}

AOFRW Crash Safety

当子进程完成重写操作,子进程会创建一个名为temp-rewriteaof-bg-pid.aof的临时AOF文件,此时这个文件对Redis而言还是不可见的,因为它还没有被加入到manifest文件中。要想使得它能被Redis识别并在Redis启动时正确加载,我们还需要将它按照前文提到的命名规则进行rename 操作,并将其信息加入到manifest文件中。

AOF文件rename 和manifest文件修改虽然是两个独立操作,但我们必须保证这两个操作的原子性,这样才能让Redis在启动时能正确的加载对应的AOF。MP-AOF使用两个设计来解决这个问题:

  1. BASE AOF的名字中包含文件序号,保证每次创建的BASE AOF不会和之前的BASE AOF冲突
  2. 先执行AOF的rename 操作,再修改manifest文件

为了便于说明,我们假设在AOFRW开始之前,manifest文件内容如下:

file appendonly.aof.1.base.rdb seq 1 type b
file appendonly.aof.1.incr.aof seq 1 type i

则在AOFRW开始执行后manifest文件内容如下:

file appendonly.aof.1.base.rdb seq 1 type b
file appendonly.aof.1.incr.aof seq 1 type i
file appendonly.aof.2.incr.aof seq 2 type i

子进程重写结束后,在主进程中,我们会将temp-rewriteaof-bg-pid.aof重命名为appendonly.aof.2.base.rdb,并将其加入manifest中,同时会将之前的BASE和INCR AOF标记为HISTORY。此时manifest文件内容如下:

file appendonly.aof.2.base.rdb seq 2 type b
file appendonly.aof.1.base.rdb seq 1 type h
file appendonly.aof.1.incr.aof seq 1 type h
file appendonly.aof.2.incr.aof seq 2 type i

此时,本次AOFRW的结果对Redis可见,HISTORY AOF会被Redis异步清理。

backgroundRewriteDoneHandler 函数通过七个步骤实现了上述逻辑:

  1. 在修改内存中的server.aof_manifest前,先dup一份临时的manifest结构,接下来的修改都将针对这个临时的manifest进行。这样做的好处是,一旦后面的步骤出现失败,我们可以简单的销毁临时manifest从而回滚整个操作,避免污染server.aof_manifest全局数据结构
  2. 从临时manifest中获取新的BASE AOF文件名(记为new_base_filename),并将之前(如果有)的BASE AOF标记为HISTORY
  3. 将子进程产生的temp-rewriteaof-bg-pid.aof临时文件重命名为new_base_filename
  4. 将临时manifest结构中上一次的INCR  AOF全部标记为HISTORY类型
  5. 将临时manifest对应的信息持久化到磁盘(persistAofManifest内部会保证manifest本身修改的原子性)
  6. 如果上述步骤都成功了,我们可以放心的将内存中的server.aof_manifest指针指向临时的manifest结构(并释放之前的manifest结构),至此整个修改对Redis可见
  7. 清理HISTORY类型的AOF,该步骤允许失败,因为它不会导致数据一致性问题
void backgroundRewriteDoneHandler(int exitcode, int bysignal) {snprintf(tmpfile, 256, "temp-rewriteaof-bg-%d.aof",(int)server.child_pid);/* 1. Dup a temporary aof_manifest for subsequent modifications. */temp_am = aofManifestDup(server.aof_manifest);/* 2. Get a new BASE file name and mark the previous (if we have)* as the HISTORY type. */new_base_filename = getNewBaseFileNameAndMarkPreAsHistory(temp_am);/* 3. Rename the temporary aof file to 'new_base_filename'. */if (rename(tmpfile, new_base_filename) == -1) {aofManifestFree(temp_am);goto cleanup;}/* 4. Change the AOF file type in 'incr_aof_list' from AOF_FILE_TYPE_INCR* to AOF_FILE_TYPE_HIST, and move them to the 'history_aof_list'. */markRewrittenIncrAofAsHistory(temp_am);/* 5. Persist our modifications. */if (persistAofManifest(temp_am) == C_ERR) {bg_unlink(new_base_filename);aofManifestFree(temp_am);goto cleanup;}/* 6. We can safely let `server.aof_manifest` point to 'temp_am' and free the previous one. */aofManifestFreeAndUpdate(temp_am);/* 7. We don't care about the return value of `aofDelHistoryFiles`, because the history* deletion failure will not cause any problems. */aofDelHistoryFiles();
}

支持AOF truncate

在进程出现Crash时AOF文件很可能出现写入不完整的问题,如一条事务里只写了MULTI,但是还没写EXEC时Redis就Crash。默认情况下,Redis无法加载这种不完整的AOF,但是Redis支持AOF truncate功能(通过aof-load-truncated配置打开)。其原理是使用server.aof_current_size跟踪AOF最后一个正确的文件偏移,然后使用ftruncate 函数将该偏移之后的文件内容全部删除,这样虽然可能会丢失部分数据,但可以保证AOF的完整性。

在MP-AOF中,server.aof_current_size已经不再表示单个AOF文件的大小而是所有AOF文件的总大小。因为只有最后一个INCR AOF才有可能出现不完整写入的问题,因此我们引入了一个单独的字段server.aof_last_incr_size用于跟踪最后一个INCR AOF文件的大小。当最后一个INCR AOF出现不完整写入时,我们只需要将server.aof_last_incr_size之后的文件内容删除即可。

if (ftruncate(server.aof_fd, server.aof_last_incr_size) == -1) {//此处省略其他细节...}

AOFRW限流

Redis在AOF大小超过一定阈值时支持自动执行AOFRW,当出现磁盘故障或者触发了代码bug导致AOFRW失败时,Redis将不停的重复执行AOFRW直到成功为止。在MP-AOF出现之前,这看似没有什么大问题(顶多就是消耗一些CPU时间和fork开销)。但是在MP-AOF中,因为每次AOFRW都会打开一个INCR AOF,并且只有在AOFRW成功时才会将上一个INCR和BASE转为HISTORY并删除。因此,连续的AOFRW失败势必会导致多个INCR AOF并存的问题。极端情况下,如果AOFRW重试频率很高我们将会看到成百上千个INCR AOF文件。

为此,我们引入了AOFRW限流机制。即当AOFRW已经连续失败三次时,下一次的AOFRW会被强行延迟1分钟执行,如果下一次AOFRW依然失败,则会延迟2分钟,依次类推延迟4、8、16...,当前最大延迟时间为1小时。

在AOFRW限流期间,我们依然可以使用bgrewriteaof命令立即执行一次AOFRW。

if (server.aof_state == AOF_ON &&!hasActiveChildProcess() &&server.aof_rewrite_perc &&server.aof_current_size > server.aof_rewrite_min_size &&!aofRewriteLimited())
{long long base = server.aof_rewrite_base_size ?server.aof_rewrite_base_size : 1;long long growth = (server.aof_current_size*100/base) - 100;if (growth >= server.aof_rewrite_perc) {rewriteAppendOnlyFileBackground();}
}

AOFRW限流机制的引入,还可以有效的避免AOFRW高频重试带来的CPU和fork开销。Redis中很多的RT抖动都和fork有关系。

总结

MP-AOF的引入,成功的解决了之前AOFRW存在的内存和CPU开销对Redis实例甚至业务访问带来的不利影响。同时,在解决这些问题的过程中,我们也遇到了很多未曾预料的挑战,这些挑战主要来自于Redis庞大的使用群体、多样化的使用场景,因此我们必须考虑用户在各种场景下使用MP-AOF可能遇到的问题。如兼容性、易用性以及对Redis代码尽可能的减少侵入性等。这都是Redis社区功能演进的重中之重。

同时,MP-AOF的引入也为Redis的数据持久化带来了更多的想象空间。如在开启aof-use-rdb-preamble时,BASE AOF本质是一个RDB文件,因此我们在进行全量备份的时候无需在单独执行一次BGSAVE操作。直接备份BASE AOF即可。MP-AOF支持关闭自动清理HISTORY AOF的能力,因此那些历史的AOF有机会得以保留,并且目前Redis已经支持在AOF中加入timestamp annotation,因此基于这些我们甚至可以实现一个简单的PITR能力( point-in-time recovery)。

MP-AOF的设计原型来自于Tair for redis企业版的binlog实现,这是一套在阿里云Tair服务上久经验证的核心功能,在这个核心功能上阿里云Tair成功构建了全球多活、PITR等企业级能力,使用户的更多业务场景需求得到满足。今天我们将这个核心能力贡献给Redis社区,希望社区用户也能享受这些企业级特性,并通过这些企业级特性更好的优化,创造自己的业务代码。有关MP-AOF的更多细节,请移步参考相关PR(#9788),那里有更多的原始设计和完整代码。

原文链接

本文为阿里云原创内容,未经允许不得转载。

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

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

相关文章

面向B端算法实时业务支撑的工程实践

简介&#xff1a;在营销场景下&#xff0c;算法同学会对广告主提供个性化的营销工具&#xff0c;帮助广告主更好的精细化营销&#xff0c;在可控成本内实现更好的ROI提升。我们在这一段时间支持了多个实时业务场景&#xff0c;比如出价策略的实时化预估、关键词批量服务同步、实…

中间表是如何被消灭的?

作者 | 不吃西红柿来源 | CSDN博客中间表的产生中间表是数据库中专门存放中间计算结果的数据表&#xff0c;往往是为了前端查询统计更快或更方便而在数据库中建立的汇总表&#xff0c;由于是由原始数据加工而成的中间结果&#xff0c;因此被称为中间表。在某些大型机构中&#…

自定义控件android.r,Android控件架构与自定义控件

前言最近在开发的路上越走越远了&#xff0c;每天在看各位大神公众号更新内容是自定义View的时候&#xff0c;一些小的内容有点模具&#xff0c;决定回过头来温习一下过往的内容。此篇也是根据android群英传来总结的一篇文章。1 Android控件架构Android的每个控件都是占一块矩形…

基于 PTS 压测轻松玩转问题诊断

简介&#xff1a;性能测试 PTS&#xff08;Performance Testing Service&#xff09;是具备强大的分布式压测能力的 SaaS 压测平台&#xff0c;可模拟海量用户的真实业务场景&#xff0c;全方位验证业务站点的性能、容量和稳定性。 作者&#xff1a;智云 为什么要做压测的问题…

阿里云开源业内首个应用多活项目 AppActive,与社区共建云原生容灾标准

简介&#xff1a;继高可用架构团队的 Sentinel、Chaosblade 开源后&#xff0c;第三个重磅高可用产品&#xff1a;应用多活 AppActive 正式开源&#xff0c;形成高可用的三架马车&#xff0c;帮助企业构建稳定可靠的企业级生产系统&#xff0c;提高企业面对容灾、容错、容量等问…

清晰还原31年前现场,火山引擎超清修复Beyond经典演唱会

7月3日晚&#xff0c;抖音携手环球音乐旗下厂牌宝丽金&#xff0c;直播经过火山引擎超清修复的Beyond Live1991生命接触演唱会及纪念音乐会精选内容&#xff0c;吸引了超1.4亿人次观看。 Beyond是一支成立于1983年的摇滚乐队&#xff0c;随着粤语音乐的兴起&#xff0c;Beyond…

如何定位并修复 HttpCore5 中的 HTTP2 流量控制问题

简介&#xff1a;开篇吹一波阿里云性能测试服务 PTS&#xff0c;PTS 在 2021 年 5 月份已经上线了对 HTTP2 协议的支持&#xff08;底层依赖 httpclient5&#xff09;&#xff0c;在压测时会通过与服务端协商的结果来决定使用 HTTP1.1 或者 HTTP2 协议。 作者&#xff1a;风起…

全链路灰度之 RocketMQ 灰度

简介&#xff1a;本文将以上次介绍过的《如何用 20 分钟就能获得同款企业级全链路灰度能力&#xff1f;》中的场景为基础&#xff0c;来进一步介绍消息场景的全链路灰度。 作者&#xff1a;亦盏 之前的系列文章中&#xff0c;我们已经通过全链路金丝雀发布这个功能来介绍了 M…

普洛斯数据中心发布DC Brain系统,科技赋能智慧化运营管理

7月5日&#xff0c;普洛斯数据中心发布了DC Brain智慧化运营管理系统。该系统由普洛斯历时两年自主研发&#xff0c;契合现代化数据中心平台的发展趋势。目前已应用于普洛斯旗下数据中心&#xff0c;并有对外输出的成功案例&#xff0c;面向行业&#xff0c;赋能中小规模运营商…

mi6 android版本,小米6:我依旧是王,MIUI10.4.2稳定版与AndroidP同时到来

原标题&#xff1a;小米6&#xff1a;我依旧是王&#xff0c;MIUI10.4.2稳定版与AndroidP同时到来小米6作为小米数字系列最受欢迎的机型之一&#xff0c;从上市到下架热度一直未减&#xff0c;它也是众多米粉心目中小米数字系列最成功的机型没有之一。但是&#xff0c;再怎么讲…

如何利用 AHAS 保障 Web 服务稳如磐石?

简介&#xff1a;应用高可用服务 AHAS (Application High Availability Service) 是经阿里巴巴内部多年高可用体系沉淀下来的云产品&#xff0c;基于阿里开源流控降级组件 Sentinel&#xff0c;以流量与容错为切入点&#xff0c;从流量控制、不稳定调用隔离、熔断降级、热点流量…

KubeDL HostNetwork:加速分布式训练通信效率

简介&#xff1a;ubeDL 为分布式训练作业带来了 HostNetwork 网络模式&#xff0c;支持计算节点之间通过宿主机网络相互通信以提升网络性能&#xff0c;同时适应 RDMA/SCC 等新型高性能数据中心架构的网络环境&#xff0c;此外&#xff0c;KubeDL 针对 HostNetwork 模式带来的 …

阿里云容器服务差异化 SLO 混部技术实践

简介&#xff1a;阿里巴巴在“差异化 SLO 混合部署”上已经有了多年的实践经验&#xff0c;目前已达到业界领先水平。所谓“差异化 SLO”&#xff0c;就是将不同类型的工作负载混合运行在同一节点&#xff0c;充分利用工作负载对资源 SLO 需求特征的不同&#xff0c;提升资源整…

鸿蒙系统被烧毁,华为鸿蒙操作系统再次被质疑 国产是原罪

国产是原罪&#xff0c;国际驰名双标现象严重&#xff0c;为何对待国产的东西要格外刻薄&#xff1f;华为手机版鸿蒙系统正式发布&#xff0c;但却引来一片嘲讽&#xff0c;这些人简直是刷新三观。如果一个产品是相同的价格&#xff0c;国产的用料更足但是还不够成熟&#xff1…

云原生落地大爆发,企业和开发者如何把握先机?

简介&#xff1a;回顾 2021 年&#xff0c;云原生有哪些重大技术突破&#xff1f;云原生时代下开发模式、技术标准等不断变化&#xff0c;企业应该如何落地云原生&#xff1f;开发者应掌握哪些能力&#xff1f;本文将为你一一解说。 作者&#xff1a;伍杏玲 随着云计算产业走…

Gartner发布中国人工智能软件市场指南,激烈竞争下走向差异化

作者 | Gartner高级研究总监 方琦 供稿 | Gartner 人工智能&#xff08;AI&#xff09;软件是中国企业投资和关注的重点。中国的AI软件市场在持续快速增长&#xff0c;竞争非常激烈。AI软件企业使用一种或多种AI技术&#xff08;见图1&#xff09;&#xff0c;帮助企业解读事件…

华为Mate是鸿蒙系统,华为mate30概念新机:4500mAh+5G网络+“鸿蒙”系统 这才是华为...

对于现在的华为手机来说&#xff0c;很多消费者的态度不一样&#xff0c;有的觉得华为手机性价比不高&#xff0c;有的则觉得华为手机质量很好&#xff0c;配置也非常的高。而我觉得&#xff0c;华为手机最值得敬佩&#xff0c;敬佩的是华为不屈的精神&#xff0c;敬佩的是远见…

平安保险基于 SPI 机制的 RocketMQ 定制化应用

简介&#xff1a;本文讲讲述平安保险为何选择 RocketMQ&#xff0c;以及在确定使用消息中间件后&#xff0c;又是如何去选择哪款消息中间件的。 作者&#xff1a;孙园园&#xff5c;平安人寿资深开发 为什么选用 RocketMQ 首先跟大家聊聊我们为什么会选用 RocketMQ&#xff…

Redis 内存优化神技,小内存保存大数据

作者 | 码哥呀来源 | 码哥字节这次跟大家分享一些优化神技&#xff0c;当你面试或者工作中你遇到如下问题&#xff0c;那就使出今天学到的绝招&#xff0c;一招定乾坤&#xff01;❝如何用更少的内存保存更多的数据&#xff1f;我们应该从 Redis 是如何保存数据的原理展开&…

SchedulerX 如何帮助用户解决分布式任务调度难题?

简介&#xff1a;本文分别对任务调度平台的资源定义、可视化管控能力、分布式批处理能力进行了简述&#xff0c;并基于 SchedulerX 的能力结合实际业务场景提供了一些基础参考案例。希望通过上述内容能让大家方便地熟悉任务调度平台接入使用概况&#xff0c;对于现有用户也可结…