国外平面设计作品集/广州seo技术外包公司
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 重写的流程是什么?
- 创建一个子进程,该子进程读取内存中的数据集并生成新的 AOF 文件。
- 主进程继续处理客户端请求,并将新命令追加到现有的 AOF 文件中。
- 子进程完成 AOF 重写后,主进程将重写期间累积的新命令追加到新的 AOF 文件中。
- 最后,替换旧的 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(Append-Only File)持久化的核心文件。AOF 持久化通过记录服务器接收到的每个写命令来实现数据的持久化。这样,在 Redis 重启时,可以通过重放这些命令来恢复数据。 2 AOF相关配置 a…...

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

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

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

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

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

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

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

kotlin flow 使用
1 创建flow 方式1 通过携程扩展函数FlowKt中的flow扩展函数可以直接构建flow,只需要传递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的特点是数据驱动视图,在这里可以把数据理解为状态,而视图就是用户可以看到的页面,页面是动态变化的,而他的变化或是用户操作引起,或是后端数据变化引起,这些都可以说是数据的状态变…...

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

纠删码参数自适应匹配问题ECP-AMP实验方案(一)
摘要 关键词:动态参数;多属性决策;critic权重法;DBSCA聚类分析 引言 云服务存储系统是一种基于互联网的数据存储服务,它可以为用户提供大规模、低成本、高可靠的数据存储空间。云服务存储系统的核心技术之一是数据容…...

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

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

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

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

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

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

SSM框架VUE电影售票管理系统开发mysql数据库redis设计java编程计算机网页源码maven项目
一、源码特点 smm VUE电影售票管理系统是一套完善的完整信息管理类型系统,结合SSM框架和VUE、redis完成本系统,对理解vue java编程开发语言有帮助系统采用ssm框架(MVC模式开发),系 统具有完整的源代码和数据库&#…...

基于Hive和Hadoop的白酒分析系统
本项目是一个基于大数据技术的白酒分析系统,旨在为用户提供全面的白酒市场信息和深入的价格分析。系统采用 Hadoop 平台进行大规模数据存储和处理,利用 MapReduce 进行数据分析和处理,通过 Sqoop 实现数据的导入导出,以 Spark 为核…...

【软考】高速缓存的组成
目录 1. 说明2. 组成 1. 说明 1.高速缓存用来存放当前最活跃的程序和数据。2.高速缓存位于CPU 与主存之间。3.容量般在几千字节到几兆字节之间。4.速度一般比主存快 5~10 倍,由快速半导体存储器构成。5.其内容是主存局部域的副本,对程序员来说是透明的。…...

UniApp基于xe-upload实现文件上传组件
xe-upload地址:文件选择、文件上传组件(图片,视频,文件等) - DCloud 插件市场 致敬开发者!!! 感觉好用的话,给xe-upload的作者一个好评 背景:开发中经常会有…...

以太网交换安全:端口隔离
一、端口隔离 以太交换网络中为了实现报文之间的二层广播域的隔离,用户通常将不同的端口加人不同的 VLAN大型网络中,业务需求种类繁多,只通过 VLAN实现报文的二层隔离,会浪费有限的VLAN资源。而采用端口隔离功能,则可…...

望繁信科技CTO李进峰受邀在上海外国语大学开展流程挖掘专题讲座
2023年,望繁信科技联合创始人兼CTO李进峰博士受邀在上海外国语大学国际工商管理学院(以下简称“上外管院”)开展专题讲座,畅谈流程挖掘的发展及对企业数字化转型的价值。演讲吸引了上外教授和来自各行各业的领军企业学员百余人。 …...

nicegui组件button用法深度解读,源代码IDE运行和调试通过
传奇开心果微博文系列 前言一、button 组件基本用法1. 最基本用法示例2. 创建带图标按钮 二、button按钮组件样式定制1. 按钮的尺寸调整2. 改变颜色示例3. 按钮的自定义字体大小4. 圆角形状示例5. 自定义边框6. 添加阴影7. 复合按钮8. 浮动按钮9. 可扩展浮动操作按钮QFAB10. 按…...

数据结构:树(并查集)
并查集(Union-Find Disjoint Sets 或 Disjoint Set Union,简称DSU)是一种树型的数据结构,主要用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。在并查集中,通常将n个对象划分为不相交的…...

校园二手交易平台的小程序+ssm(lw+演示+源码+运行)
摘 要 随着社会的发展,社会的方方面面都在利用信息化时代的优势。互联网的优势和普及使得各种系统的开发成为必需。 本文以实际运用为开发背景,运用软件工程原理和开发方法,它主要是采用java语言技术和mysql数据库来完成对系统的设计。整个…...

代码随想录训练营第46天|回文子序列
647. 回文子串 class Solution { public:int count0;void check(string& s, int left, int right){while(left>0&&right<s.length()&&s[left]s[right]){count;left--;right;}}int countSubstrings(string s) {for(int i0; i<s.length(); i){chec…...

使用 PowerShell 命令更改 RDP 远程桌面端口(无需修改防火墙设置)
节选自原文:Windows远程桌面一站式指南 | BOBO Blog 原文目录 什么是RDP开启远程桌面 检查系统版本启用远程桌面连接Windows 在Windows电脑上在MAC电脑上在Android或iOS移动设备上主机名连接 自定义电脑名通过主机名远程桌面使用Hosts文件自定义远程主…...