Redis源码剖析之AOF
liebian365 2024-11-20 18:25 4 浏览 0 评论
书接上回,上回我们详细讲解了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的优势
- RDB是压缩的后紧凑数据格式,比较很适合备份,
- 同样的数据量下,rdb的文件大小会很小,比较适合传输和数据恢复。
- RDB对Redis的读写性能影响小,生成RDB的时redis主进程会fork出一个子进程,不会影响到主进程的读写。
- RDB数据加载更快,恢复起来更快。
RDB的缺点
- RDB是定期备份,如果备份前发生宕机,数据可能会丢失。
- RDB的生成依赖于linux的fork,如果数据量比较大的话,很影响服务器性能。
AOF的优势
- AOF是持续性备份,可以尽可能保证数据不丢失。
- Redis太大时,Redis可以在后台自动重写AOF。重写是完全安全的,因为Redis继续追加到旧文件时,会生成一个全新的文件,其中包含创建当前数据集所需的最少操作集,一旦准备好第二个文件,Redis会切换这两个文件并开始追加到新的那一个。
- AOF文件格式简单,易于解析。
AOF的缺点
- 对于同一数据集,AOF文件大小通常大于等效的RDB文件。
- 如果使用fsync策略,AOF可能比RDB慢。
RDB和AOF该如何选
如果是要求极致的性能,但对数据恢复不敏感,二者可以都不要,如果是关注性能且关注数据可用性,但不要求数据完整性,可以选用RDB。如果说非常关注数据完整性和宕机恢复的能力,可以RDB+AOF同时开启。
参考资料
本文是Redis源码剖析系列博文,同时也有与之对应的Redis中文注释版,有想深入学习Redis的同学,欢迎star和关注。 Redis中文注解版仓库:https://github.com/xindoo/Redis
Redis源码剖析专栏:https://zxs.io/s/1h
如果觉得本文对你有用,欢迎一键三连。本文来自https://blog.csdn.net/xindoo
相关推荐
- 快递查询教程,批量查询物流,一键管理快递
-
作为商家,每天需要查询许许多多的快递单号,面对不同的快递公司,有没有简单一点的物流查询方法呢?小编的回答当然是有的,下面随小编一起来试试这个新技巧。需要哪些工具?安装一个快递批量查询高手快递单号怎么快...
- 一键自动查询所有快递的物流信息 支持圆通、韵达等多家快递
-
对于各位商家来说拥有一个好的快递软件,能够有效的提高自己的工作效率,在管理快递单号的时候都需要对单号进行表格整理,那怎么样能够快速的查询所有单号信息,并自动生成表格呢?1、其实方法很简单,我们不需要一...
- 快递查询单号查询,怎么查物流到哪了
-
输入单号怎么查快递到哪里去了呢?今天小编给大家分享一个新的技巧,它支持多家快递,一次能查询多个单号物流,还可对查询到的物流进行分析、筛选以及导出,下面一起来试试。需要哪些工具?安装一个快递批量查询高手...
- 3分钟查询物流,教你一键批量查询全部物流信息
-
很多朋友在问,如何在短时间内把单号的物流信息查询出来,查询完成后筛选已签收件、筛选未签收件,今天小编就分享一款物流查询神器,感兴趣的朋友接着往下看。第一步,运行【快递批量查询高手】在主界面中点击【添...
- 快递单号查询,一次性查询全部物流信息
-
现在各种快递的查询方式,各有各的好,各有各的劣,总的来说,还是有比较方便的。今天小编就给大家分享一个新的技巧,支持多家快递,一次能查询多个单号的物流,还能对查询到的物流进行分析、筛选以及导出,下面一起...
- 快递查询工具,批量查询多个快递快递单号的物流状态、签收时间
-
最近有朋友在问,怎么快速查询单号的物流信息呢?除了官网,还有没有更简单的方法呢?小编的回答当然是有的,下面一起来看看。需要哪些工具?安装一个快递批量查询高手多个京东的快递单号怎么快速查询?进入快递批量...
- 快递查询软件,自动识别查询快递单号查询方法
-
当你拥有多个快递单号的时候,该如何快速查询物流信息?比如单号没有快递公司时,又该如何自动识别再去查询呢?不知道如何操作的宝贝们,下面随小编一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号若干...
- 教你怎样查询快递查询单号并保存物流信息
-
商家发货,快递揽收后,一般会直接手动复制到官网上一个个查询物流,那么久而久之,就会觉得查询变得特别繁琐,今天小编给大家分享一个新的技巧,下面一起来试试。教程之前,我们来预览一下用快递批量查询高手...
- 简单几步骤查询所有快递物流信息
-
在高峰期订单量大的时候,可能需要一双手当十双手去查询快递物流,但是由于逐一去查询,效率极低,追踪困难。那么今天小编给大家分享一个新的技巧,一次能查询多个快递单号的物流,下面一起来学习一下,希望能给大家...
- 物流单号查询,如何查询快递信息,按最后更新时间搜索需要的单号
-
最近有很多朋友在问,如何通过快递单号查询物流信息,并按最后更新时间搜索出需要的单号呢?下面随小编一起来试试吧。需要哪些工具?安装一个快递批量查询高手快递单号若干怎么快速查询?运行【快递批量查询高手】...
- 连续保存新单号功能解析,导入单号查询并自动识别批量查快递信息
-
快递查询已经成为我们日常生活中不可或缺的一部分。然而,面对海量的快递单号,如何高效、准确地查询每一个快递的物流信息,成为了许多人头疼的问题。幸运的是,随着科技的进步,一款名为“快递批量查询高手”的软件...
- 快递查询教程,快递单号查询,筛选更新量为1的单号
-
最近有很多朋友在问,怎么快速查询快递单号的物流,并筛选出更新量为1的单号呢?今天小编给大家分享一个新方法,一起来试试吧。需要哪些工具?安装一个快递批量查询高手多个快递单号怎么快速查询?运行【快递批量查...
- 掌握批量查询快递动态的技巧,一键查找无信息记录的两种方法解析
-
在快节奏的商业环境中,高效的物流查询是确保业务顺畅运行的关键。作为快递查询达人,我深知时间的宝贵,因此,今天我将向大家介绍一款强大的工具——快递批量查询高手软件。这款软件能够帮助你批量查询快递动态,一...
- 从复杂到简单的单号查询,一键清除单号中的符号并批量查快递信息
-
在繁忙的商务与日常生活中,快递查询已成为不可或缺的一环。然而,面对海量的单号,逐一查询不仅耗时费力,还容易出错。现在,有了快递批量查询高手软件,一切变得简单明了。只需一键,即可搞定单号查询,一键处理单...
- 物流单号查询,在哪里查询快递
-
如果在快递单号多的情况,你还在一个个复制粘贴到官网上手动查询,是一件非常麻烦的事情。于是乎今天小编给大家分享一个新的技巧,下面一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号怎么快速查询?...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- wireshark怎么抓包 (75)
- qt sleep (64)
- cs1.6指令代码大全 (55)
- factory-method (60)
- sqlite3_bind_blob (52)
- hibernate update (63)
- c++ base64 (70)
- nc 命令 (52)
- wm_close (51)
- epollin (51)
- sqlca.sqlcode (57)
- lua ipairs (60)
- tv_usec (64)
- 命令行进入文件夹 (53)
- postgresql array (57)
- statfs函数 (57)
- .project文件 (54)
- lua require (56)
- for_each (67)
- c#工厂模式 (57)
- wxsqlite3 (66)
- dmesg -c (58)
- fopen参数 (53)
- tar -zxvf -c (55)
- 速递查询 (52)