元素码农
基础
UML建模
数据结构
算法
设计模式
网络
TCP/IP协议
HTTPS安全机制
WebSocket实时通信
数据库
sqlite
postgresql
clickhouse
后端
rust
go
java
php
mysql
redis
mongodb
etcd
nats
zincsearch
前端
浏览器
javascript
typescript
vue3
react
游戏
unity
unreal
C++
C#
Lua
App
android
ios
flutter
react-native
安全
Web安全
测试
软件测试
自动化测试 - Playwright
人工智能
Python
langChain
langGraph
运维
linux
docker
工具
git
svn
🌞
🌙
目录
▶
Redis核心
▶
数据结构
字符串实现
哈希表实现
列表实现
集合实现
有序集合实现
▶
内存管理
内存分配策略
淘汰算法
▶
持久化
▶
RDB机制
快照生成原理
文件格式解析
▶
AOF机制
命令追加策略
重写过程分析
▶
高可用
▶
主从复制
SYNC原理
增量复制
▶
哨兵机制
故障检测
领导选举
▶
高级特性
▶
事务系统
ACID实现
WATCH原理
▶
Lua脚本
沙盒环境
执行管道
▶
实战问题
▶
缓存问题
缓存雪崩
缓存穿透
缓存击穿
缓存预热
▶
数据一致性
读写一致性
双写一致性
▶
性能优化
大key处理
热点key优化
发布时间:
2025-03-22 10:45
↑
☰
# Redis AOF重写过程分析 ## 引言 AOF重写是Redis的一个重要特性,通过重新生成AOF文件来减小文件体积,优化持久化性能。本文将深入分析AOF重写的实现原理、过程优化以及最佳实践。 ## 基本概念 ### 1. 重写目的 - 减小AOF文件体积 - 提高文件加载效率 - 优化内存使用 ### 2. 触发机制 ```conf # 文件大小触发 auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb # 手动触发 BGREWRITEAOF ``` ## 重写原理 ### 1. 基本流程 ```c // 重写入口函数 int rewriteAppendOnlyFile(char *filename) { rio aof; FILE *fp; char tmpfile[256]; // 创建临时文件 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 failed"); return C_ERR; } // 初始化rio rioInitWithFile(&aof,fp); // 遍历数据库 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; // 写入SELECT命令 if (rioWrite(&aof,selectcmd,sizeof(selectcmd)-1) == 0) goto werr; if (rioWriteBulkLongLong(&aof,j) == 0) goto werr; // 遍历键值对 di = dictGetSafeIterator(d); while((de = dictNext(di)) != NULL) { sds keystr = dictGetKey(de); robj key, *o = dictGetVal(de); long long expire; // 生成命令 if (rewriteAppendOnlyFileRio(&aof,&key,o,expire) == 0) goto werr; } } // 替换文件 if (rename(tmpfile,filename) == -1) { serverLog(LL_WARNING,"Error moving temp append only file on the final destination"); unlink(tmpfile); return C_ERR; } return C_OK; } ``` ### 2. 命令重写 ```c // 命令重写实现 int rewriteAppendOnlyFileRio(rio *aof, robj *key, robj *value, long long expiretime) { sds cmd = createDumpPayload(value); // 根据数据类型生成命令 switch(value->type) { case OBJ_STRING: buf = sdscatprintf(sdsempty(),"*3\r\n$3\r\nSET\r\n$%lu\r\n", (unsigned long)sdslen(key->ptr)); buf = sdscatlen(buf,key->ptr,sdslen(key->ptr)); buf = sdscatprintf(buf,"\r\n$%lu\r\n",(unsigned long)sdslen(value->ptr)); buf = sdscatlen(buf,value->ptr,sdslen(value->ptr)); buf = sdscatlen(buf,"\r\n",2); break; case OBJ_LIST: // 生成RPUSH命令 buf = sdscatprintf(sdsempty(),"*%d\r\n$5\r\nRPUSH\r\n$%lu\r\n", 2+listTypeLength(value),(unsigned long)sdslen(key->ptr)); buf = sdscatlen(buf,key->ptr,sdslen(key->ptr)); buf = sdscatlen(buf,"\r\n",2); // 遍历列表元素 iter = listTypeInitIterator(value,0,LIST_TAIL); while (listTypeNext(iter,&entry)) { buf = sdscatprintf(buf,"$%lu\r\n",(unsigned long)sdslen(entry)); buf = sdscatlen(buf,entry,sdslen(entry)); buf = sdscatlen(buf,"\r\n",2); } listTypeReleaseIterator(iter); break; // 其他数据类型处理 } // 写入命令 if (rioWrite(aof,buf,sdslen(buf)) == 0) return 0; return 1; } ``` ### 3. 内存管理 ```c // 重写缓冲区 typedef struct { sds buf; // 缓冲区 list *blocks; // 大块缓存 size_t used; // 已用空间 int sync_on_write; // 写入即同步 } aofRewriteBuffer; // 缓冲区管理 void aofRewriteBufferAppend(unsigned char *s, unsigned long len) { listNode *ln; listNode *head = listFirst(server.aof_rewrite_buf_blocks); // 检查当前块是否有足够空间 if (head && head->value) { aofBlock *aobl = listNodeValue(head); unsigned long free = AOF_BLOCK_SIZE - aobl->used; if (len <= free) { memcpy(aobl->buf + aobl->used, s, len); aobl->used += len; return; } } // 分配新块 aofBlock *aobl = zmalloc(sizeof(*aobl)); aobl->used = len; memcpy(aobl->buf, s, len); listAddNodeHead(server.aof_rewrite_buf_blocks,aobl); } ``` ## 性能优化 ### 1. 写入优化 ```c // 增量同步配置 aof-rewrite-incremental-fsync yes // 缓冲区配置 aof-rewrite-buffer-size 64mb ``` ### 2. 内存优化 ```c // 重写期间新命令处理 void feedRewriteBuffer(char *s, size_t len) { if (server.aof_child_pid != -1) aofRewriteBufferAppend((unsigned char*)s,len); } ``` ### 3. 磁盘优化 ```c // 文件写入策略 int rewriteAppendOnlyFileBackground(void) { pid_t childpid; long long start; if (server.aof_child_pid != -1) return C_ERR; if (hasActiveChildProcess()) return C_ERR; start = ustime(); if ((childpid = fork()) == 0) { char tmpfile[256]; // 子进程重写AOF closeListeningSockets(0); redisSetProcTitle("redis-aof-rewrite"); snprintf(tmpfile,256,"temp-rewrite-aof-bg-%d.aof", (int)getpid()); if (rewriteAppendOnlyFile(tmpfile) == C_OK) { sendChildInfo(CHILD_INFO_TYPE_AOF, AOF_REWRITE_OK,ustime()-start); exitFromChild(0); } else { sendChildInfo(CHILD_INFO_TYPE_AOF, AOF_REWRITE_ERR,ustime()-start); exitFromChild(1); } } else { // 父进程继续服务 server.aof_child_pid = childpid; server.aof_rewrite_time_start = time(NULL); return C_OK; } return C_OK; } ``` ## 监控和维护 ### 1. 状态监控 ```redis # 查看重写状态 INFO persistence # 查看重写进度 INFO stats | grep aof_rewrite ``` ### 2. 错误处理 ```c // 重写错误处理 void backgroundRewriteDoneHandler(int exitcode, int bysignal) { if (!bysignal && exitcode == 0) { // 重写成功 serverLog(LL_NOTICE,"Background AOF rewrite finished successfully"); // 加载新AOF文件 loadAppendOnlyFile(server.aof_filename); } else { // 重写失败 serverLog(LL_WARNING, "Background AOF rewrite terminated with error"); } } ``` ### 3. 性能监控 ```redis # 监控重写时间 INFO stats | grep aof_rewrite_time # 监控重写内存使用 INFO memory | grep aof_buffer ``` ## 最佳实践 ### 1. 触发策略 - 合理设置触发阈值 - 避免频繁重写 - 选择合适的时间点 ### 2. 资源管理 - 控制重写缓冲区大小 - 监控内存使用情况 - 优化磁盘IO ### 3. 容错处理 - 备份重要数据 - 设置超时机制 - 完善错误恢复 ### 4. 运维建议 - 定期检查AOF文件大小 - 监控重写进程状态 - 及时处理异常情况 ## 常见问题 ### 1. 性能影响 - 重写过程CPU使用率 - 内存占用增加 - 磁盘IO压力 ### 2. 数据一致性 - 重写期间数据同步 - 文件切换时机 - 错误恢复机制 ### 3. 运维挑战 - 重写失败处理 - 资源占用控制 - 监控告警设置 ## 总结 AOF重写是Redis持久化机制中的一个重要优化手段,通过合理的重写策略和优化配置,可以有效控制AOF文件大小,提高系统性能。在实际应用中,需要根据业务场景选择合适的重写策略,并建立完善的监控和维护机制,确保AOF重写的可靠性和效率。