当前位置: 首页 > news >正文

【Redis 源码】6AOF持久化

1 AOF功能说明

aof.c 文件是 Redis 中负责 AOF(Append-Only File)持久化的核心文件。AOF 持久化通过记录服务器接收到的每个写命令来实现数据的持久化。这样,在 Redis 重启时,可以通过重放这些命令来恢复数据。

2 AOF相关配置

  • appendonly yes: 将这一行的值从 no 改为 yes,以启用 AOF 持久化。

  • appendfilename "appendonly.aof": 设置 AOF 文件的名称,默认是 appendonly.aof

  • appendfsync <policy> 设置 fsync 策略,可以选择always、everysec、 no。推荐everyse因为它提供了较好的性能和数据安全性之间的平衡。

    • always: 每次写操作后都进行 fsync,最安全但性能较差。
    • everysec: 每秒进行一次 fsync,性能较好且数据安全性较高。
    • no: 不主动进行 fsync,由操作系统决定何时同步到磁盘,性能最好但数据安全性最低。

3 aof 文件格式

Redis 的 AOF(Append-Only File)文件格式是一种文本格式,记录了 Redis 服务器接收到的所有写命令。AOF 文件中的每条记录都是一个完整的 Redis 命令,可以直接复制到 Redis 客户端中执行。

格式如下:

*<参数数量> CR LF
$<参数1的长度> CR LF
<参数1数据> CR LF
$<参数2的长度> CR LF
<参数2数据> CR LF
...
  • * 表示参数的数量。
  • $ 表示参数的长度。
  • CR 是回车符(ASCII 13),LF 是换行符(ASCII 10)。

例如:

*2
$6
SELECT
$1
0
*3
$3
SET
$5
liyan
$8
liyan223

为执行了2个命令:

SELECT 0
SET liyan liyan223

4 核心数据结构

redisCommand

用于表示一个 Redis 命令。这个结构体包含了命令的各种属性和元数据,使得 Redis 能够正确地解析、执行和管理这些命令。

struct redisCommand {//命令的名称,例如 "SET" 或 "GET"。char *name;  /*指向实际处理该命令的函数。这个函数会执行命令的具体逻辑。示例: 对于 SET 命令,proc 会指向 setCommand 函数。*/redisCommandProc *proc;/*命令所需的参数个数。如果命令可以接受可变数量的参数,通常设置为 -N,其中 N 是最小参数个数。示例: SET 命令的 arity 为 3,因为 SET key value [EX seconds|PX milliseconds] [NX|XX] 至少需要三个参数(键、值和其他可选参数)。*/int arity;/*命令标志的字符串表示形式,每个字符代表一个标志。常见的标志包括:'w': 写操作'r': 读操作'm': 可以修改多个键'a': 管理命令'f': 快速执行's': 允许在从服务器上执行'R': 随机命令'S': 排序命令'l': 加载时使用't': 不跟踪时间'M': 使用模块'k': 键空间通知'g': 读取全局状态'F': 不记录 AOF'C': 集群模式下不支持'A': ACL 控制SET 命令的 sflags 可能是 "wm",表示写操作且可以修改多个键。*/char *sflags;   /* Flags as string representation, one char per flag. *//*从 sflags 字符串中解析出的实际标志位。每个标志位对应一个比特位。示例: 如果 sflags 是 "wm",则 flags 将包含 CMD_WRITE 和 CMD_DENYOOM 标志。*/uint64_t flags; /* The actual flags, obtained from the 'sflags' field. *//* Use a function to determine keys arguments in a command line.* Used for Redis Cluster redirect. *//*用于确定命令行中的键参数的函数。主要用于 Redis Cluster 中的重定向。示例: 对于 MGET 命令,getkeys_proc 会指向 getKeysFromMultiKeyCommand 函数。*/redisGetKeysProc *getkeys_proc;/* What keys should be loaded in background when calling this command? *//*第一个键参数的位置(从 0 开始计数)。如果命令没有键参数,则为 0。示例: SET 命令的 firstkey 为 1,因为第一个参数是键名。*/int firstkey; /* The first argument that's a key (0 = no keys) *//*最后一个键参数的位置(从 0 开始计数)。如果命令没有键参数,则与 firstkey 相同。示例: MGET 命令的 lastkey 为 -1,表示最后一个参数也是键名*/int lastkey;  /* The last argument that's a key *//*键参数之间的步长。如果命令只涉及连续的键参数,则为 1。示例: MGET 命令的 keystep 为 1,因为所有参数都是键名。*/int keystep;  /* The step between first and last key *//*microseconds: 执行该命令所花费的总微秒数。calls: 该命令被调用的次数。rejected_calls: 该命令被拒绝的次数(例如,由于权限问题)。failed_calls: 该命令执行失败的次数。*/long long microseconds, calls, rejected_calls, failed_calls;/*命令的 ID。这是一个从 0 开始递增的 ID,用于检查访问控制列表 (ACL)。连接能够执行给定命令的前提是连接关联的用户在允许命令的位图中有此命令的位。*/int id;     /* Command ID. This is a progressive ID starting from 0 thatis assigned at runtime, and is used in order to checkACLs. A connection is able to execute a given command ifthe user associated to the connection has this commandbit set in the bitmap of allowed commands. */
};

假设有一个 SET 命令的定义如下:

/*
name 为 "set"
proc 指向 setCommandImpl 函数
arity 为 3,表示 SET 命令至少需要三个参数
sflags 为 "wm",表示写操作且可以修改多个键
flags 由 sflags 解析得到
getkeys_proc 指向 getKeysFromCommand 函数
firstkey 和 lastkey 均为 1,表示第一个参数是键名
keystep 为 1,表示键参数之间没有间隔
统计信息初始为 0
id 初始为 0
*/
struct redisCommand setCommand = {"set",setCommandImpl,3,"wm",CMD_WRITE | CMD_DENYOOM,getKeysFromCommand,1,  // firstkey1,  // lastkey1,  // keystep0,  // microseconds0,  // calls0,  // rejected_calls0,  // failed_calls0   // id
};

5 核心代码

startAppendOnly

启用 AOF 持久化,并打开或创建 AOF 文件。

startAppendOnly 函数的主要任务是初始化 AOF 文件,处理可能的后台操作冲突,并设置 AOF 状态以便开始记录写命令。该函数确保 AOF 文件正确打开,并在必要时触发 AOF 重写。如果在过程中遇到任何错误,函数会记录日志并返回错误状态。

int startAppendOnly(void) {char cwd[MAXPATHLEN]; /* Current working dir path for error messages. */int newfd;//打开或创建 AOF 文件/*使用 open 系统调用以只写、追加和创建模式打开 AOF 文件。如果文件无法打开(例如权限问题),则记录警告日志并返回错误。serverAssert 用于确保 AOF 状态为 AOF_OFF,即 AOF 尚未开启。*/newfd = open(server.aof_filename,O_WRONLY|O_APPEND|O_CREAT,0644);serverAssert(server.aof_state == AOF_OFF);if (newfd == -1) {char *cwdp = getcwd(cwd,MAXPATHLEN);serverLog(LL_WARNING,"Redis needs to enable the AOF but can't open the ""append only file %s (in server root dir %s): %s",server.aof_filename,cwdp ? cwdp : "unknown",strerror(errno));return C_ERR;}// 处理后台进程./*如果有其他后台操作且不是 AOF 重写,则标记 AOF 重写待执行。如果已经有 AOF 重写在进行中,则停止当前的 AOF 重写并启动新的 AOF 重写。调用 rewriteAppendOnlyFileBackground 触发后台 AOF 重写,如果失败则关闭文件描述符并记录错误。*/if (hasActiveChildProcess() && server.child_type != CHILD_TYPE_AOF) {server.aof_rewrite_scheduled = 1;serverLog(LL_WARNING,"AOF was enabled but there is already another background operation. An AOF background was scheduled to start when possible.");} else {/* If there is a pending AOF rewrite, we need to switch it off and* start a new one: the old one cannot be reused because it is not* accumulating the AOF buffer. */if (server.child_type == CHILD_TYPE_AOF) {serverLog(LL_WARNING,"AOF was enabled but there is already an AOF rewriting in background. Stopping background AOF and starting a rewrite now.");killAppendOnlyChild();}if (rewriteAppendOnlyFileBackground() == C_ERR) {close(newfd);serverLog(LL_WARNING,"Redis needs to enable the AOF but can't trigger a background AOF rewrite operation. Check the above logs for more info about the error.");return C_ERR;}}/* We correctly switched on AOF, now wait for the rewrite to be complete* in order to append data on disk. *//*设置 AOF 状态将 AOF 状态设置为 AOF_WAIT_REWRITE,表示等待 AOF 重写完成。更新最后一次 fsync 的时间戳。设置 AOF 文件描述符。*/server.aof_state = AOF_WAIT_REWRITE;server.aof_last_fsync = server.unixtime;server.aof_fd = newfd;/* If AOF fsync error in bio job, we just ignore it and log the event. *///理之前的 AOF 错误int aof_bio_fsync_status;atomicGet(server.aof_bio_fsync_status, aof_bio_fsync_status);if (aof_bio_fsync_status == C_ERR) {serverLog(LL_WARNING,"AOF reopen, just ignore the AOF fsync error in bio job");atomicSet(server.aof_bio_fsync_status,C_OK);}/* If AOF was in error state, we just ignore it and log the event. */if (server.aof_last_write_status == C_ERR) {serverLog(LL_WARNING,"AOF reopen, just ignore the last error.");server.aof_last_write_status = C_OK;}return C_OK;
}

feedAppendOnlyFile

feedAppendOnlyFile 函数是 Redis 中用于将写命令追加到 AOF 缓冲区的关键函数。这个函数确保所有对数据库的修改操作都被记录到 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. *//*如果当前命令的目标数据库与上一个命令不同,则需要插入一个 SELECT 命令来切换数据库。使用 snprintf 和 sdscatprintf 将 SELECT 命令格式化并追加到 buf 中。更新 server.aof_selected_db 以反映当前选中的数据库。server.aof_selected_db 为文件中没有加载数据库*/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) {/* Translate EXPIRE/PEXPIRE/EXPIREAT into PEXPIREAT */buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]);//处理包含过期时间的SET命令    } 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];}/* For AOF we convert SET key value relative time in milliseconds to SET key value absolute time in* millisecond. Whenever the condition is true it implies that original SET has been transformed* to SET PX with millisecond time argument so we do not need to worry about unit here.*/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 {/* All the other commands don't need translation or need the* same translation already operated in the command vector* for the replication itself. */buf = catAppendOnlyGenericCommand(buf,argc,argv);}/* Append to the AOF buffer. This will be flushed on disk just before* of re-entering the event loop, so before the client will get a* positive reply about the operation performed. *///追加到 AOF 缓冲区if (server.aof_state == AOF_ON)server.aof_buf = sdscatlen(server.aof_buf,buf,sdslen(buf));/* If a background append only file rewriting is in progress we want to* accumulate the differences between the child DB and the current one* in a buffer, so that when the child process will do its work we* can append the differences to the new append only file. */if (server.child_type == CHILD_TYPE_AOF)aofRewriteBufferAppend((unsigned char*)buf,sdslen(buf));//释放 buf 占用的内存。sdsfree(buf);
}

feedAppendOnlyFile 函数的主要任务是将执行的写命令追加到 AOF 缓冲区中,以便稍后写入磁盘。该函数还处理了数据库选择命令、过期命令和 SET 命令的特殊情况,确保 AOF 文件中的命令能够正确地反映实际的操作。此外,如果后台正在进行 AOF 重写,它还会将命令追加到重写缓冲区中,以便在重写完成后应用这些新命令。

rewriteAppendOnlyFileBackground

rewriteAppendOnlyFileBackground 是 Redis 中用于在后台执行 AOF 重写的函数。AOF 重写是为了减小 AOF 文件的大小,通过创建一个新的 AOF 文件,该文件只包含重建当前数据集所需的最少命令。这个过程是在后台进行的,以避免阻塞主进程。

int rewriteAppendOnlyFileBackground(void) {pid_t childpid;//检查是否有活动的子进程:if (hasActiveChildProcess()) return C_ERR;//创建管道 用于父子通信if (aofCreatePipes() != C_OK) return C_ERR;//使用 redisFork 创建一个子进程。CHILD_TYPE_AOF 表示这是一个 AOF 重写子进程if ((childpid = redisFork(CHILD_TYPE_AOF)) == 0) {//子线程逻辑/*设置进程标题为 "redis-aof-rewrite"。设置 CPU 亲和性,确保子进程运行在指定的 CPU 上。生成临时文件名 tmpfile。调用 rewriteAppendOnlyFile 进行 AOF 重写。如果重写成功,发送子进程信息并退出,返回状态 0;否则返回状态 1。*/char tmpfile[256];/* Child */redisSetProcTitle("redis-aof-rewrite");redisSetCpuAffinity(server.aof_rewrite_cpulist);snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid());if (rewriteAppendOnlyFile(tmpfile) == C_OK) {sendChildCowInfo(CHILD_INFO_TYPE_AOF_COW_SIZE, "AOF rewrite");exitFromChild(0);} else {exitFromChild(1);}} else {/* Parent */if (childpid == -1) {serverLog(LL_WARNING,"Can't rewrite append only file in background: fork: %s",strerror(errno));aofClosePipes();return C_ERR;}serverLog(LL_NOTICE,"Background append only file rewriting started by pid %ld",(long) childpid);server.aof_rewrite_scheduled = 0;server.aof_rewrite_time_start = time(NULL);/* We set appendseldb to -1 in order to force the next call to the* feedAppendOnlyFile() to issue a SELECT command, so the differences* accumulated by the parent into server.aof_rewrite_buf will start* with a SELECT statement and it will be safe to merge. */server.aof_selected_db = -1;replicationScriptCacheFlush();return C_OK;}return C_OK; /* unreached */
}

rewriteAppendOnlyFile 函数是 Redis 中用于重写 AOF 文件的核心函数。该函数负责将当前内存中的数据集以最小的命令集合形式写入一个新的 AOF 文件中。这个过程通常在后台进行,以避免阻塞主进程。

int rewriteAppendOnlyFile(char *filename) {rio aof;FILE *fp = NULL;char tmpfile[256];char byte;/* 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) {serverLog(LL_WARNING, "Opening the temp file for AOF rewrite in rewriteAppendOnlyFile(): %s", strerror(errno));return C_ERR;}//初始化 AOF 缓冲区和 Rio(Redis I/O)结构体。server.aof_child_diff = sdsempty();rioInitWithFile(&aof,fp);// 如果配置了增量同步,则设置 Rio 的自动同步大小。if (server.aof_rewrite_incremental_fsync)rioSetAutoSync(&aof,REDIS_AUTOSYNC_BYTES);//开始保存操作startSaving(RDBFLAGS_AOF_PREAMBLE);/*如果启用了 RDB 前言(preamble),则使用 rdbSaveRio 将数据集保存为 RDB 格式。否则,使用 rewriteAppendOnlyFileRio 逐个键值对地重写 AOF 文件。*/if (server.aof_use_rdb_preamble) {int error;if (rdbSaveRio(&aof,&error,RDBFLAGS_AOF_PREAMBLE,NULL) == C_ERR) {errno = error;goto werr;}} else {if (rewriteAppendOnlyFileRio(&aof) == C_ERR) goto werr;}/* Do an initial slow fsync here while the parent is still sending* data, in order to make the next final fsync faster. *///初步同步文件,确保数据被写入磁盘。if (fflush(fp) == EOF) goto werr;if (fsync(fileno(fp)) == -1) goto werr;/* 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();}/* Ask the master to stop sending diffs. */if (write(server.aof_pipe_write_ack_to_parent,"!",1) != 1) goto werr;if (anetNonBlock(NULL,server.aof_pipe_read_ack_from_parent) != ANET_OK)goto werr;/* We read the ACK from the server using a 5 seconds timeout. Normally* it should reply ASAP, but just in case we lose its reply, we are sure* the child will eventually get terminated. */if (syncRead(server.aof_pipe_read_ack_from_parent,&byte,1,5000) != 1 ||byte != '!') goto werr;serverLog(LL_NOTICE,"Parent agreed to stop sending diffs. Finalizing AOF...");/* Read the final diff if any. */aofReadDiffFromParent();/* Write the received diff to the file. */serverLog(LL_NOTICE,"Concatenating %.2f MB of AOF diff received from parent.",(double) sdslen(server.aof_child_diff) / (1024*1024));/* Now we write the entire AOF buffer we received from the parent* via the pipe during the life of this fork child.* once a second, we'll take a break and send updated COW info to the parent */size_t bytes_to_write = sdslen(server.aof_child_diff);const char *buf = server.aof_child_diff;long long cow_updated_time = mstime();long long key_count = dbTotalServerKeyCount();while (bytes_to_write) {/* We write the AOF buffer in chunk of 8MB so that we can check the time in between them */size_t chunk_size = bytes_to_write < (8<<20) ? bytes_to_write : (8<<20);if (rioWrite(&aof,buf,chunk_size) == 0)goto werr;bytes_to_write -= chunk_size;buf += chunk_size;/* Update COW info */long long now = mstime();if (now - cow_updated_time >= 1000) {sendChildInfo(CHILD_INFO_TYPE_CURRENT_INFO, key_count, "AOF rewrite");cow_updated_time = now;}}/* Make sure data will not remain on the OS's output buffers */if (fflush(fp)) goto werr;if (fsync(fileno(fp))) goto werr;if (fclose(fp)) { fp = NULL; goto werr; }fp = NULL;/* Use RENAME to make sure the DB file is changed atomically only* if the generate DB file is ok. */if (rename(tmpfile,filename) == -1) {serverLog(LL_WARNING,"Error moving temp append only file on the final destination: %s", strerror(errno));unlink(tmpfile);stopSaving(0);return C_ERR;}serverLog(LL_NOTICE,"SYNC append only file rewrite performed");stopSaving(1);return C_OK;werr:serverLog(LL_WARNING,"Write error writing append only file on disk: %s", strerror(errno));if (fp) fclose(fp);unlink(tmpfile);stopSaving(0);return C_ERR;
}

rewriteAppendOnlyFileRio 函数是 Redis 中用于将当前数据库中的所有键值对重写到 AOF 文件的核心函数。该函数通过遍历每个数据库(db)中的键值对,并生成相应的 Redis 命令,将其写入 rio 结构体中。

int rewriteAppendOnlyFileRio(rio *aof) {dictIterator *di = NULL;dictEntry *de;size_t processed = 0;int j;long key_count = 0;long long updated_time = 0;//遍历每个数据库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);/* SELECT the new DB *//*发送 SELECT 命令来选择当前数据库。写入数据库编号。*/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;/*获取键和对应的值。初始化静态字符串对象 key。获取键的过期时间。*/keystr = dictGetKey(de);o = dictGetVal(de);initStaticStringObject(key,keystr);expiretime = getExpire(db,&key);/* Save the key and associated value */// 保存键值对if (o->type == OBJ_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 == OBJ_LIST) {if (rewriteListObject(aof,&key,o) == 0) goto werr;} else if (o->type == OBJ_SET) {if (rewriteSetObject(aof,&key,o) == 0) goto werr;} else if (o->type == OBJ_ZSET) {if (rewriteSortedSetObject(aof,&key,o) == 0) goto werr;} else if (o->type == OBJ_HASH) {if (rewriteHashObject(aof,&key,o) == 0) goto werr;} else if (o->type == OBJ_STREAM) {if (rewriteStreamObject(aof,&key,o) == 0) goto werr;} else if (o->type == OBJ_MODULE) {if (rewriteModuleObject(aof,&key,o) == 0) goto werr;} else {serverPanic("Unknown object type");}/* Save the expire time */// 如果键有设置过期时间,则发送 PEXPIREAT 命令。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;}/* Read some diff from the parent process from time to time. */// 定期从父进程读取数据差异,以确保子进程不会遗漏新的写操作。if (aof->processed_bytes > processed+AOF_READ_DIFF_INTERVAL_BYTES) {processed = aof->processed_bytes;aofReadDiffFromParent();}/* Update info every 1 second (approximately).* in order to avoid calling mstime() on each iteration, we will* check the diff every 1024 keys */if ((key_count++ & 1023) == 0) {long long now = mstime();if (now - updated_time >= 1000) {sendChildInfo(CHILD_INFO_TYPE_CURRENT_INFO, key_count, "AOF rewrite");updated_time = now;}}}dictReleaseIterator(di);di = NULL;}return C_OK;werr:if (di) dictReleaseIterator(di);return C_ERR;
}

6 总结

AOF 和 RDB 的主要区别是什么?

答:

  • RDB (快照)
    • 定期保存内存中的数据到磁盘。
    • 文件紧凑,适合备份和灾难恢复。
    • 恢复速度快,但可能丢失最后一次快照之后的数据。
  • AOF (日志)
    • 记录每个写操作,并追加到文件中。
    • 可以设置不同的 fsync 策略来平衡性能和数据安全性。
    • 数据更完整,但文件较大,恢复速度较慢。

AOF 重写 (Rewrite) 是什么?为什么需要它?

答: AOF 重写是一个后台进程,用于压缩 AOF 文件。随着时间推移,AOF 文件可能会变得非常大,因为每个写操作都被记录下来。AOF 重写通过创建一个新的 AOF 文件,只包含重建当前数据集所需的最少命令,从而减小 AOF 文件的体积。这有助于提高恢复速度并减少磁盘空间占用。

AOF 重写的流程是什么?

  1. 创建一个子进程,该子进程读取内存中的数据集并生成新的 AOF 文件。
  2. 主进程继续处理客户端请求,并将新命令追加到现有的 AOF 文件中。
  3. 子进程完成 AOF 重写后,主进程将重写期间累积的新命令追加到新的 AOF 文件中。
  4. 最后,替换旧的 AOF 文件为新的 AOF 文件。

AOF 的优点和缺点是什么?

  • 优点
    • 数据完整性高,丢失数据的可能性较小。
    • 可以通过调整 appendfsync 参数来平衡性能和数据安全性。
    • 更容易理解和实现。
  • 缺点
    • AOF 文件通常比 RDB 文件大,可能导致恢复时间较长。
    • 频繁的写操作会导致 AOF 文件持续增长,需要定期进行重写。

AOF 在极端情况下如何处理?

  • 如果 AOF 文件损坏或不完整,Redis 提供了 aof-load-truncated 选项,允许加载部分有效的 AOF 文件。
  • 如果 AOF 文件太大,可以通过 AOF 重写来减小文件大小。
  • 如果 AOF 文件写入失败,Redis 会尝试修复并继续运行,但会记录错误信息以便后续检查。

相关文章:

【Redis 源码】6AOF持久化

1 AOF功能说明 aof.c 文件是 Redis 中负责 AOF&#xff08;Append-Only File&#xff09;持久化的核心文件。AOF 持久化通过记录服务器接收到的每个写命令来实现数据的持久化。这样&#xff0c;在 Redis 重启时&#xff0c;可以通过重放这些命令来恢复数据。 2 AOF相关配置 a…...

6.MySQL基本查询

目录 表的增删查改Insert&#xff08;插入&#xff09;插入替换插入替换2 Retrieve&#xff08;查找&#xff09;SELECT 列全列查找指定列查询查询字段为表达式为查询结果指定别名结果去重 WHERE 条件order by子句筛选分页结果 Update&#xff08;更新&#xff09;delete&#…...

Linux字符设备驱动开发

Linux 字符设备驱动开发是内核模块开发中的一个重要部分&#xff0c;主要用于处理字节流数据设备&#xff08;如串口、键盘、鼠标等&#xff09;。字符设备驱动的核心任务是定义如何与用户空间程序交互&#xff0c;通常通过一组文件操作函数进行。这些函数会映射到 open、read、…...

HTML5+JavaScript绘制闪烁的网格错觉

HTML5JavaScript绘制闪烁的网格错觉 闪烁的网格错觉&#xff08;scintillating grid illusion&#xff09;是一种视觉错觉&#xff0c;通过简单的黑白方格网格和少量的精心设计&#xff0c;能够使人眼前出现动态变化的效果。 闪烁的栅格错觉&#xff0c;是一种经典的视觉错觉…...

每日OJ题_牛客_拼三角_枚举/DFS_C++_Java

目录 牛客_拼三角_枚举/DFS 题目解析 C代码1 C代码2 Java代码 牛客_拼三角_枚举/DFS 拼三角_枚举/DFS 题目解析 简单枚举&#xff0c;不过有很多种枚举方法&#xff0c;这里直接用简单粗暴的枚举方式。 C代码1 #include <iostream> #include <algorithm> …...

[uni-app]小兔鲜-01项目起步

项目介绍 效果演示 技术架构 创建项目 HBuilderX创建 下载HBuilderX编辑器 HBuilderX/创建项目: 选择模板/选择Vue版本/创建 安装插件: 工具/插件安装/uni-app(Vue3)编译器 vue代码不能直接运行在小程序环境, 编译插件帮助我们进行代码转换 绑定微信开发者工具: 指定微信开…...

安全的价值:构建现代企业的基础

物理安全对于组织来说并不是事后才考虑的问题&#xff1a;它是关键的基础设施。零售商、医疗保健提供商、市政当局、学校和所有其他类型的组织都依赖安全系统来保障其人员和场所的安全。 随着安全技术能力的不断发展&#xff0c;许多组织正在以更广泛的视角看待他们的投资&am…...

门面(外观)模式

简介 门面模式&#xff08;Facade Pattern&#xff09;又叫作外观模式&#xff0c;提供了一个统一的接口&#xff0c;用来访问子系统中的一群接口。其主要特征是定义了一个高层接口&#xff0c;让子系统更容易使用&#xff0c;属于结构型设计模式。 通用模板 创建子系统角色类…...

kotlin flow 使用

1 创建flow 方式1 通过携程扩展函数FlowKt中的flow扩展函数可以直接构建flow&#xff0c;只需要传递FlowCollector收集器实现类就可以了 private fun create1(){val intFlow createFlow()println("创建int flow: $intFlow")runBlocking {println("开始收集&…...

vue3 实现文本内容超过N行折叠并显示“...展开”组件

1. 实现效果 组件内文字样式取决与外侧定义 组件大小发生变化时,文本仍可以省略到指定行数 文本不超过时, 无展开,收起按钮 传入文本发生改变后, 组件展示新的文本 2. 代码 文件名TextEllipsis.vue <template><div ref"compRef" class"wq-text-ellip…...

根据源码解析Vue2中对于对象的变化侦测

UI render(state) VUE的特点是数据驱动视图&#xff0c;在这里可以把数据理解为状态&#xff0c;而视图就是用户可以看到的页面&#xff0c;页面是动态变化的&#xff0c;而他的变化或是用户操作引起&#xff0c;或是后端数据变化引起&#xff0c;这些都可以说是数据的状态变…...

爬虫技术深潜:探究 JsonPath 与 XPath 的语法海洋与实战岛屿

Python爬虫中JSON与XML字符串的XPath和JsonPath过滤语法区别对比 在信息爆炸的互联网时代&#xff0c;数据抓取成为了获取宝贵信息的关键技能。对于技术爱好者&#xff0c;特别是Python程序员来说&#xff0c;熟练掌握JSON和XML数据解析方法至关重要。本文旨在深入探讨这两种格…...

纠删码参数自适应匹配问题ECP-AMP实验方案(一)

摘要 关键词&#xff1a;动态参数&#xff1b;多属性决策&#xff1b;critic权重法&#xff1b;DBSCA聚类分析 引言 云服务存储系统是一种基于互联网的数据存储服务&#xff0c;它可以为用户提供大规模、低成本、高可靠的数据存储空间。云服务存储系统的核心技术之一是数据容…...

五、人物持有武器攻击

一、手部添加预制体&#xff08;武器&#xff09; 1、骨骼&#xff08;手&#xff09; 由于人物模型有骨骼和动画&#xff0c;在添加预制体后&#xff0c;会抓握武器 建一个预制体在手部位置 二、添加武器拖尾 下载拖尾特效 赋值特效中的代码&#xff0c;直接使用 清空里面…...

mysql索引 -- 全文索引介绍(如何创建,使用),explain关键字

目录 全文索引 引入 介绍 创建 使用 表数据 简单搜索 explain关键字 使用全文索引 mysql索引结构详细介绍 -- mysql索引 -- 索引的硬件理解(磁盘,磁盘与系统),软件理解(mysql,与系统io,buffer pool),索引结构介绍和理解(page内部,page之间,为什么是b树)-CSDN博客 全文…...

Wayfair封号的常见原因及解决方案解析

近期关于Wayfair账号封禁的问题引发了广泛讨论。许多用户报告称&#xff0c;他们的Wayfair账户被突然封禁&#xff0c;这一现象不仅影响了用户的购物体验&#xff0c;也对Wayfair的品牌形象造成了一定的冲击。本文将深入探讨Wayfair封号的原因&#xff0c;并提出相应的解决方案…...

计算机视觉方面的一些模块

# __all__ 是一个可选的列表&#xff0c;定义在模块级别。当使用 from ... import * 语句时&#xff0c;如果模块中定义了 # __all__&#xff0c;则只有 __all__ 列表中的名称会被导入。这是模块作者控制哪些公开API被导入的一种方式。 # 使用 * 导入的行为 # 如果模块中有 __a…...

进阶美颜功能技术开发方案:探索视频美颜SDK

视频美颜SDK&#xff08;SoftwareDevelopmentKit&#xff09;作为提升视频质量的重要工具&#xff0c;越来越多地被开发者关注与应用。接下俩&#xff0c;笔者将深入探讨进阶美颜功能的技术开发方案&#xff0c;助力开发者更好地利用视频美颜SDK。 一、视频美颜SDK的核心功能 …...

【重学 MySQL】三十八、group by的使用

【重学 MySQL】三十八、group by的使用 基本语法示例示例 1: 计算每个部门的员工数示例 2: 计算每个部门的平均工资示例 3: 结合 WHERE 子句 WITH ROLLUP基本用法示例注意事项 注意事项 GROUP BY 是 SQL 中一个非常重要的子句&#xff0c;它通常与聚合函数&#xff08;如 COUNT…...

SSM框架VUE电影售票管理系统开发mysql数据库redis设计java编程计算机网页源码maven项目

一、源码特点 smm VUE电影售票管理系统是一套完善的完整信息管理类型系统&#xff0c;结合SSM框架和VUE、redis完成本系统&#xff0c;对理解vue java编程开发语言有帮助系统采用ssm框架&#xff08;MVC模式开发&#xff09;&#xff0c;系 统具有完整的源代码和数据库&#…...

ETLCloud可能遇到的问题有哪些?常见坑位解析

数据集成平台ETLCloud&#xff0c;主要用于支持数据的抽取&#xff08;Extract&#xff09;、转换&#xff08;Transform&#xff09;和加载&#xff08;Load&#xff09;过程。提供了一个简洁直观的界面&#xff0c;以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...

Spring Boot面试题精选汇总

&#x1f91f;致敬读者 &#x1f7e9;感谢阅读&#x1f7e6;笑口常开&#x1f7ea;生日快乐⬛早点睡觉 &#x1f4d8;博主相关 &#x1f7e7;博主信息&#x1f7e8;博客首页&#x1f7eb;专栏推荐&#x1f7e5;活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...

Python如何给视频添加音频和字幕

在Python中&#xff0c;给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加&#xff0c;包括必要的代码示例和详细解释。 环境准备 在开始之前&#xff0c;需要安装以下Python库&#xff1a;…...

laravel8+vue3.0+element-plus搭建方法

创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...

在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案

这个问题我看其他博主也写了&#xff0c;要么要会员、要么写的乱七八糟。这里我整理一下&#xff0c;把问题说清楚并且给出代码&#xff0c;拿去用就行&#xff0c;照着葫芦画瓢。 问题 在继承QWebEngineView后&#xff0c;重写mousePressEvent或event函数无法捕获鼠标按下事…...

JS设计模式(4):观察者模式

JS设计模式(4):观察者模式 一、引入 在开发中&#xff0c;我们经常会遇到这样的场景&#xff1a;一个对象的状态变化需要自动通知其他对象&#xff0c;比如&#xff1a; 电商平台中&#xff0c;商品库存变化时需要通知所有订阅该商品的用户&#xff1b;新闻网站中&#xff0…...

LabVIEW双光子成像系统技术

双光子成像技术的核心特性 双光子成像通过双低能量光子协同激发机制&#xff0c;展现出显著的技术优势&#xff1a; 深层组织穿透能力&#xff1a;适用于活体组织深度成像 高分辨率观测性能&#xff1a;满足微观结构的精细研究需求 低光毒性特点&#xff1a;减少对样本的损伤…...

破解路内监管盲区:免布线低位视频桩重塑停车管理新标准

城市路内停车管理常因行道树遮挡、高位设备盲区等问题&#xff0c;导致车牌识别率低、逃费率高&#xff0c;传统模式在复杂路段束手无策。免布线低位视频桩凭借超低视角部署与智能算法&#xff0c;正成为破局关键。该设备安装于车位侧方0.5-0.7米高度&#xff0c;直接规避树枝遮…...

嵌入式常见 CPU 架构

架构类型架构厂商芯片厂商典型芯片特点与应用场景PICRISC (8/16 位)MicrochipMicrochipPIC16F877A、PIC18F4550简化指令集&#xff0c;单周期执行&#xff1b;低功耗、CIP 独立外设&#xff1b;用于家电、小电机控制、安防面板等嵌入式场景8051CISC (8 位)Intel&#xff08;原始…...

pycharm 设置环境出错

pycharm 设置环境出错 pycharm 新建项目&#xff0c;设置虚拟环境&#xff0c;出错 pycharm 出错 Cannot open Local Failed to start [powershell.exe, -NoExit, -ExecutionPolicy, Bypass, -File, C:\Program Files\JetBrains\PyCharm 2024.1.3\plugins\terminal\shell-int…...