Redis核心技术与实战
蒋德钧
中科院计算所副研究员
立即订阅
5337 人已学习
课程目录
已更新 6 讲 / 共 50 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 这样学Redis,才能技高一筹
免费
基础篇 (5讲)
01 | 基本架构:一个键值数据库包含什么?
02 | 数据结构:快速的Redis有哪些慢操作?
03 | 高性能IO模型:为什么单线程Redis能那么快?
04 | AOF日志:宕机了,Redis如何避免数据丢失?
05 | 内存快照:宕机后,Redis如何实现快速恢复?
Redis核心技术与实战
15
15
1.0x
00:00/00:00
登录|注册

04 | AOF日志:宕机了,Redis如何避免数据丢失?

蒋德钧 2020-08-12
你好,我是蒋德钧。
如果有人问你:“你会把 Redis 用在什么业务场景下?”我想你大概率会说:“我会把它当作缓存使用,因为它把后端数据库中的数据存储在内存中,然后直接从内存中读取数据,响应速度会非常快。”没错,这确实是 Redis 的一个普遍使用场景,但是,这里也有一个绝对不能忽略的问题:一旦服务器宕机,内存中的数据将全部丢失。
我们很容易想到的一个解决方案是,从后端数据库恢复这些数据,但这种方式存在两个问题:一是,需要频繁访问数据库,会给数据库带来巨大的压力;二是,这些数据是从慢速数据库中读取出来的,性能肯定比不上从 Redis 中读取,导致使用这些数据的应用程序响应变慢。所以,对 Redis 来说,实现数据的持久化,避免从后端数据库中进行恢复,是至关重要的。
目前,Redis 的持久化主要有两大机制,即 AOF 日志和 RDB 快照。在接下来的两节课里,我们就分别学习一下吧。这节课,我们先重点学习下 AOF 日志。

AOF 日志是如何实现的?

说到日志,我们比较熟悉的是数据库的写前日志(Write Ahead Log, WAL),也就是说,在实际写数据前,先把修改的数据记到日志文件中,以便故障时进行恢复。不过,AOF 日志正好相反,它是写后日志,“写后”的意思是 Redis 是先执行命令,把数据写入内存,然后才记录日志,如下图所示:
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Redis核心技术与实战》,如需阅读全部文章,
请订阅文章所属专栏新⼈⾸单¥29.9
立即订阅
登录 后留言

精选留言(33)

  • Kaito
    问题1,Redis采用fork子进程重写AOF文件时,潜在的阻塞风险包括:fork子进程 和 AOF重写过程中父进程产生写入的场景,下面依次介绍。

    a、fork子进程,fork这个瞬间一定是会阻塞主线程的(注意,fork时并不会一次性拷贝所有内存数据给子进程,老师文章写的是拷贝所有内存数据给子进程,我个人认为是有歧义的),fork采用操作系统提供的写实复制(Copy On Write)机制,就是为了避免一次性拷贝大量内存数据给子进程造成的长时间阻塞问题,但fork子进程需要拷贝进程必要的数据结构,其中有一项就是拷贝内存页表(虚拟内存和物理内存的映射索引表),这个拷贝过程会消耗大量CPU资源,拷贝完成之前整个进程是会阻塞的,阻塞时间取决于整个实例的内存大小,实例越大,内存页表越大,fork阻塞时间越久。拷贝内存页表完成后,子进程与父进程指向相同的内存地址空间,也就是说此时虽然产生了子进程,但是并没有申请与父进程相同的内存大小。那什么时候父子进程才会真正内存分离呢?“写实复制”顾名思义,就是在写发生时,才真正拷贝内存真正的数据,这个过程中,父进程也可能会产生阻塞的风险,就是下面介绍的场景。

    b、fork出的子进程指向与父进程相同的内存地址空间,此时子进程就可以执行AOF重写,把内存中的所有数据写入到AOF文件中。但是此时父进程依旧是会有流量写入的,如果父进程操作的是一个已经存在的key,那么这个时候父进程就会真正拷贝这个key对应的内存数据,申请新的内存空间,这样逐渐地,父子进程内存数据开始分离,父子进程逐渐拥有各自独立的内存空间。因为内存分配是以页为单位进行分配的,默认4k,如果父进程此时操作的是一个bigkey,重新申请大块内存耗时会变长,可能会产阻塞风险。另外,如果操作系统开启了内存大页机制(Huge Page,页面大小2M),那么父进程申请内存时阻塞的概率将会大大提高,所以在Redis机器上需要关闭Huge Page机制。Redis每次fork生成RDB或AOF重写完成后,都可以在Redis log中看到父进程重新申请了多大的内存空间。

    问题2,AOF重写不复用AOF本身的日志,一个原因是父子进程写同一个文件必然会产生竞争问题,控制竞争就意味着会影响父进程的性能。二是如果AOF重写过程中失败了,那么原本的AOF文件相当于被污染了,无法做恢复使用。所以Redis AOF重写一个新文件,重写失败的话,直接删除这个文件就好了,不会对原先的AOF文件产生影响。等重写完成之后,直接替换旧文件即可。
    2020-08-12
    11
    42
  • 徐鹏
    有几个问题想请教一下:
    1、文中多处提到bgrewriteaof 子进程,这个有点迷糊,主线程fork出来的bgrewriteaof是子线程还是子进程?
    2、AOF重写会拷贝一份完整的内存数据,这个会导致内存占用直接翻倍吗?
    3、如果一个key设置了过期时间,在利用AOF文件恢复数据时,key已经过期了这个是如何处理的呢?
    2020-08-12
    3
    10
  • Darren
    AOF工作原理:
    1、Redis 执行 fork() ,现在同时拥有父进程和子进程。
    2、子进程开始将新 AOF 文件的内容写入到临时文件。
    3、对于所有新执行的写入命令,父进程一边将它们累积到一个内存缓存中,一边将这些改动追加到现有 AOF 文件的末尾,这样样即使在重写的中途发生停机,现有的 AOF 文件也还是安全的。
    4、当子进程完成重写工作时,它给父进程发送一个信号,父进程在接收到信号之后,将内存缓存中的所有数据追加到新 AOF 文件的末尾。
    5、搞定!现在 Redis 原子地用新文件替换旧文件,之后所有命令都会直接追加到新 AOF 文件的末尾。

    试着讨论下留言童鞋的几个问题

    一、其中老师在文中提到:“因为主线程未阻塞,仍然可以处理新来的操作。此时,如果有写操作,第一处日志就是指正在使用的 AOF 日志,Redis 会把这个操作写到它的缓冲区。这样一来,即使宕机了,这个 AOF 日志的操作仍然是齐全的,可以用于恢复。”
    这里面说到 “Redis 会把这个操作写到它的缓冲区,这样一来,即使宕机了,这个 AOF 日志的操作仍然是齐全的”,其实对于有些人有理解起来可能不是那么好理解,因为写入缓冲区为什么还不都是数据;

    我的理解其实这个就是写入缓冲区,只不过是由appendfsync策略决定的,所以说的不丢失数据指的是不会因为子进程额外丢失数据。

    二、AOF重新只是回拷贝引用(指针),不会拷贝数据本身,因此量其实不大,那写入的时候怎么办呢,写时复制,即新开辟空间保存修改的值,因此需要额外的内存,但绝对不是redis现在占有的2倍。

    三、AOF对于过期key不会特殊处理,因为Redis keys过期有两种方式:被动和主动方式。
    当一些客户端尝试访问它时,key会被发现并主动的过期。

    当然,这样是不够的,因为有些过期的keys,永远不会访问他们。 无论如何,这些keys应该过期,所以定时随机测试设置keys的过期时间。所有这些过期的keys将会从删除。
    具体就是Redis每秒10次做的事情:
    测试随机的20个keys进行相关过期检测。
    删除所有已经过期的keys。
    如果有多于25%的keys过期,重复步奏1.

    至于课后问题,看了 @与路同飞 童鞋的答案,没有更好的答案,就不回答了
    2020-08-12
    4
    6
  • 曾轼麟
    问题一:在AOF重写期间,Redis运行的命令会被积累在缓冲区,待AOF重写结束后会进行回放,在高并发情况下缓冲区积累可能会很大,这样就会导致阻塞,Redis后来通过Linux管道技术让aof期间就能同时进行回放,这样aof重写结束后只需要回放少量剩余的数据即可

    问题二:对于任何文件系统都是不推荐并发修改文件的,例如hadoop的租约机制,Redis也是这样,避免重写发生故障,导致文件格式错乱最后aof文件损坏无法使用,所以Redis的做法是同时写两份文件,最后通过修改文件名的方式,保证文件切换的原子性

    这里需要纠正一下老师前面的口误,就是Redis是通过使用操作系统的fork()方式创建进程,不是线程,也由于这个原因,主进程和fork出来的子进程的资源是不共享的,所以也出现Redis使用pipe管道技术来同步主子进程的aof增量数据
    2020-08-12
    1
    3
  • Anony
    上节说redis单线程处理网络IO和键值对的读写,持久化是由额外线程处理的。那AOF由额外线程处理的话,为什么会影响主线程呢?和主线程之间是什么关系?
    2020-08-12
    1
    2
  • 与路同飞
    1.子线程重新AOF日志完成时会向主线程发送信号处理函数,会完成 (1)将AOF重写缓冲区的内容写入到新的AOF文件中。(2)将新的AOF文件改名,原子地替换现有的AOF文件。完成以后才会重新处理客户端请求。
    2.不共享AOF本身的日志是防止锁竞争,类似于redis rehash。
    2020-08-12
    4
    2
  • 杨逸林
    老师没有说什么时候会触发重写 ROF 文件,我 Google 一下, 重写的触发方式有下面两个。

    1. 手动执行 bgrewriteaof 触发 AOF 重写

    2. 在 redis.conf 文件中配置重写的条件,如:
    auto-aof-rewrite-min-size 64MB // 当文件小于64M时不进行重写
    auto-aof-rewrite-min-percenrage 100 // 当文件比上次重写后的文件大100%时进行重写

    [原文](https://www.jianshu.com/p/f72008c4d49f)

    还有传统关系型数据库并不是很"慢",其实很多数据库都会做优化,把热点数据放入内存。例如 Mysql 的buffer_pool,oracle 也会在你分页查询的时候,缓存下一页,只要你数据没改过,也是很快的返回的。

    老师说的 AOF 文件,和 mysql binlog_format=row 格式很像,都是保存执行命令/语句,只不过 mysql 是照搬 sql,不会做这种最终数据一致的操作。还有 RDB 应该和 binlog_format= statement 很像吧,我看都是教一起开着用的。redolog undolog binlog 难道天下数据库是一家,思想理念照着抄吗?������
    2020-08-12
    1
    1
  • 脱缰的野马__
    文章前面说到redo log日志记录的是修改后的数据,但是在丁奇老师的MySQL实战中讲解的是redo log记录是对数据的操作记录,修改后的数据是保存在内存的change buffer中的
    2020-08-12
    1
  • 秋梵
    老师我有个问题想问。文章里说,在子进程进行重写期间,如果有新的写操作,Redis 会把这个操作写到它的缓冲区。但是缓冲区也是存在内存中的,如果宕机,内存的数据会丢失,新的写操作同样会丢失,这样的话要怎么保证AOF的日志操作是齐全的?
    2020-08-12
    1
  • test
    1.fork时候的开销
    2.如果使用同一个文件会需要有文件锁的竞争
    2020-08-12
    1
  • 吴小智
    两个日志的合并,是分离后,主进程写的日志,往子进程的重写日志里合并吧?感觉文中这点没写清楚?
    2020-08-13
  • Leslie_Lamport
    “一个拷贝”就是指,每次执行重写时,主线程 fork 出后台的 bgrewriteaof 子进程。此时,fork 会把主线程的内存拷贝一份给 bgrewriteaof 子进程,这里面就包含了数据库的最新数据。然后,bgrewriteaof 子进程就可以在不影响主线程的情况下,逐一把拷贝的数据写成操作,记入重写日志。


    — 拷贝数据的cost多大?
    2020-08-13
  • AstonPutting
    既然aof是以文本的形式记录redis的操作,是不是也可以用于不同Redis版本之间的备份与恢复,比如说redis4的aof日志恢复到redis3上
    2020-08-13
  • ruier
    有个Django写的Redis管理小系统,有兴趣的朋友可以看一看,名字叫repoll
    https://github.com/NaNShaner/repoll
    2020-08-12
  • d
    AOF 是什么的缩写, 还是说就是这个名字?
    2020-08-12
    1
  • 青生先森
    redis重写机制只针对还没有写入磁盘的信息吗?
    2020-08-12
    1
  • 寰宇寰宇
    aof重写的触发条件:
    1. 手动执行bgrewriteaof触发aof重写
    2. 配置redis.conf文件的aof重写条件,如auto-aof-rewrite-min-size和auto-aof-rewrite-min-percenrage
    2020-08-12
    1
  • Archer30
    问题1回答:如果子进程写入事件过长,并且这段事件,会导致AOF重写日志,积累过多,当新的AOF文件完成后,还是需要写入大量AOF重写日志里的内容,可能会导致阻塞。

    问题2回答:我觉得评论区里的大部分回答 防止锁竞争 ,应该是把问题理解错了,父子两个进程本来就没有需要竞争的数据,老师所指的两个日志应该是“AOF缓冲区”和"AOF重写缓冲区",而不是磁盘上的AOF文件,之所有另外有一个"AOF重写缓冲区",是因为重写期间,主进程AOF还在继续工作,还是会同步到旧的AOF文件中,同步成功后,“AOF缓冲区”会被清除,会被清除,会被清除!
    2020-08-12
  • Mr.蜜
    一开始说主线程fork一个子进程处理AOF,后面又说是子线程,到底是进程还是线程?AOF 会复制一份内存给子进程保存数据?如果内存不够怎么办?麻烦作者解释清楚,谢谢。
    2020-08-12
    3
  • ikel
    1、主线程的新数据同步到子进程时候可能会有socket阻塞
    2、可能是考虑到加锁会有效率降低吧
    2020-08-12
收起评论
33
返回
顶部