likes
comments
collection
share

Redis持久化核心梳理

作者站长头像
站长
· 阅读数 48

上回手敲了几天的Redis五种基本数据类型大剖析——跳转链接

这次对Redis的持久化,也就是AOF日志RDB快照两种机制的关键部分进行梳理总结。Redis虽然是内存型数据库,但很多时候还是希望重启或者崩溃后能恢复之前的数据,这时候就需要持久化机制。话不多说,上正菜:

1.Redis是先写AOF日志再执行命令还是先执行命令再写日志
  • 和我们熟悉的MySQL数据库不一样,它是写前日志(Write Ahead Log, WAL),WAL 技术指的是MySQL的写操作并不是立刻写到磁盘上,而是先写日志,然后在合适的时间再写到磁盘上
  • 而Redis中的AOF日志正好相反,它属于写后日志,指的是Redis先执行命令,把数据写入内存,再记录日志。

这样的好处主要有两个:

  • 避免额外的检查开销,因为是先执行命令,所以能保证命令执行成功后才会记录到日志中去。
  • 不会阻塞当前写操作命令的执行,因为它是在命令执行后才记录日志

潜在的风险也有两个:

  • 因为是写后日志,如果执行完命令再将命令落盘到日志,所以中间出现宕机的话,数据会丢失
  • AOF 日志也是在主线程中执行的,如果在把日志文件写入磁盘时,磁盘写压力大,就会导致写盘很慢,进而导致后续的操作也无法执行了。即可能会给下一个命令带来阻塞风险

Redis持久化核心梳理

2. AOF日志写回磁盘的时机有几种

三种写回策略体现了“取舍之道”,在性能和可靠性之间进行取舍,通过redis.conf 配置文件中的 appendfsync 配置项进行配置。

Redis持久化核心梳理

这个配置项名称指示是通过控制fsync()函数的调用时机:

如果想要应用程序向文件写入数据后,能立马将数据同步到硬盘,就可以调用 fsync() 函数,这样内核就会将内核缓冲区的数据直接写入到硬盘,等到硬盘写操作完成后,该函数才会返回。

  • Always 策略就是每次写入 AOF 文件数据后,就执行 fsync() 函数;
  • Everysec 策略就会创建一个异步任务来执行 fsync() 函数;
  • No 策略就是永不执行 fsync() 函数;

Redis持久化核心梳理

即便是always刷盘策略也可能存在数据丢失,因为Redis是“写后日志”,当Redis内存执行完了,去刷盘的时候宕机就会导致数据丢失

3.关于AOF重写和RDB是否会阻塞主线程

AOF重写不同于主线层写入AOF日志,重写过程由后台子进程bgrewriteaof来完成的,来避免阻塞主线程。

AOF重写过程可以概括为“一次拷贝,两处日志”,具体来说:

  • 一次拷贝:重写的时候,主线程会fork出一个子进程,需要注意的是不会复制物理内存,主要是复制了一份主进程的页表,子进程共享父进程的物理内存数据,不过该物理内存的权限为只读,然后,bgrewriteaof 子进程就可以在不影响主线程的情况下,逐一把拷贝的数据写成操作,记入重写日志。

Redis持久化核心梳理

  • 两处日志:两处日志指的是原来正在使用的AOF日志新的AOF重写日志。因为主线程不阻塞,当重写发生的时候,有新的写入命令执行,会由主线程分别写入AOF缓冲AOF重写缓冲。AOF缓冲用来保证发生宕机,原来正在使用的AOF日志也是完整的,可用于恢复。AOF重写缓冲用于保证新的AOF文件也不会丢失最新的写入操作。同时需要注意这个AOF重写缓冲是个链表,不会满。除非超过设置的maxmemory,则会执行配置的内存淘汰

而对于RDB快照来说,Redis提供了两个命令,分别是save和bgsave来生成RDB文件。

  • save在主线程执行,会导致阻塞
  • bgsave会创建一个子进程,专门用于写入RDB文件,是默认的配置
4. 重写AOF也有一个重写日志,怎么不共享使用AOF本身的日志

AOF重写不复用AOF本身的日志。

  • 一个原因是父子进程写同一个文件必然会产生竞争问题,控制竞争就意味着会影响父进程的性能。
  • 二是如果AOF重写过程中失败了,那么原本的AOF文件相当于被污染了,无法做恢复使用。所以Redis AOF重写一个新文件,重写失败的话,直接删除这个文件就好了,不会对原先的AOF文件产生影响。等重写完成之后,直接替换旧文件即可。
5. AOF重写是由子进程来完成的,不阻塞主线程,但重写过程有没有阻塞风险
  • fork子进程。创建子进程的途中,由于要复制父进程的页表等数据结构,fork瞬间一定会阻塞主进程,拷贝过程会消耗大量CPU资源,阻塞时间取决于整个实例的内存大小。
  • AOF重写过程中父进程产生写入的场景。创建完子进程后,如果父进程修改了共享数据,就会发生写时复制,这期间会拷贝物理内存,如果父进程此时操作的是一个bigkey,重新申请大块内存耗时会变长,可能会产阻塞风险。(“写实复制”顾名思义,就是在写发生时,才真正拷贝内存真正的数据,这里只会复制主进程修改的物理内存数据,没修改物理内存还是与子进程共享的。)
6. 执行RDB快照时,数据能被修改吗

快照这个词取得很形象,就跟我们在给别人拍照的时候,对方最好不要动,对于内存快照来说,我们也不希望数据“动”。

尽管我们不希望数据“动”,但我们更不希望业务停滞了——在快照期间,不能修改数据。如何解决呢?

这时又需要用到上面提到的技术:写时复制

如果主线程要修改一块数据,这块数据会被复制一份,生成副本,主线程在数据副本上进行修改,同时,bgsave子进程继续把原有的数据写入RDB文件。

bgsave快照过程中,如果主线程修改了共享数据,发生了写时复制后,RDB快照保存的是原本的内存数据,而主线程刚修改的数据,是没办法在这一时间写入RDB文件的,只能交由下一次的 bgsave 快照。所以如果系统恰好在 RDB 快照文件创建完毕后崩溃了,那么 Redis 将会丢失主线程在快照期间修改的数据。

7. AOF混合持久化方案是什么

混合持久化只是对AOF rewrite做的优化。AOF混合持久化方案是Redis4.0提出的,简单来说,内存快照以一定的频率执行,在AOF rewrite时,先写一个RDB进去,再把这期间产生的写命令,追加到AOF文件中。避免RDB频繁fork对主线程的影响,同时AOF也不会那么大,它只需要记录两次快照之间的操作。

未完待续,去备考了...

下面为第一次补充:

8. AOF重写是否是通过把原有的AOF文件读取到内存中进行整合

这个在之前在面试的时候也答错了,AOF重写这个名字容易让人以为该功能是通过对原有的AOF文件进行读入、分析或写入操作。但实际上,这个功能并不需要对原有的AOF文件进行任何读入、分析或写入的操作。它是通过读取服务器当前的数据库状态来实现的。 举个例子:

Redis持久化核心梳理 Redis持久化核心梳理

(图来源于Redis设计与实现)

原有的AOF文件此时有六条关于list键的命令,AOF重写直接去数据库中读取键list的值,然后自己生成条命令代替保存原有AOF文件中的六条命令。

数据库状态指的就是当前Redis中的所有数据库以及它们的键值对

9. 是否知道AOF和RDB文件格式
①. AOF文件格式

Redis设计与实现中的AOF文件格式描述为这样子:

Redis持久化核心梳理

而在其他地方看到中的AOF文件格式又是这样:

Redis持久化核心梳理

我去看了一下我电脑中的aof文件,是和下面这种格式保持一致的。可能因为那本书籍是Redis3.0,所以有点细微的不同。

Redis持久化核心梳理

AOF好像默认是不开启的,如果需要开启可以使用redis-cli config set appendonly yes, 通过redis-cli config get appendonly

②. RDB文件格式

Redis持久化核心梳理

RDB文件相对比较复杂,一般面试也不问这个,问AOF比较多。二进制文件打开会乱码,可以使用专门分析rdb文件的工具:github.com/php-cpm/red…

开头是为五个字节的REDIS常量字符,能在载入文件的时候,快速检查所载入的文件是否为RDB文件

db_version是一个4字节字符串表示的整数,记录RDB文件版本号,比如“0006”就代表第六版,上图也是绘制第六版RDB文件结构。

databases包含0或多个数据库,以及各个数据库中的键值对。前面也提过数据库状态这个东西。实际上就是数据库状态为空的时候,这个部分为空,数据库状态非空(此时Redis中存在数据库不为空),那么这个部分非空

EOF标志RDB文件正文内容的结束

check_num为8字节长的无符号整数,保存一个检验和,是通过前面四部分内容计算得出的。

关于databases的展开部分,这里就不叙述了。详细参考:Redis设计与实现电子pdf P142-P149

10. AOF每秒刷盘,有可能阻塞住Redis吗

每秒刷盘的逻辑:

  • Redis主线程把命令写到page cache(通过调用write系统调用)
  • Redis后台线程每间隔1s,把AOF page cache持久化到磁盘 (通过fsync系统调用)

如果在2执行时,迟迟没有成功,1执行的时候就会阻塞住,在操作同一个fd时,fsync和write是互斥的。

不成功的原因可能是:其他程序在疯狂写磁盘,导致磁盘IO负载非常高,那么1在执行时,就会阻塞等待,从而影响到主线程。

11. 子进程完成RDB之后的内存回收是什么样

子进程RDB期间,父进程有新数据写入或修改,那么对这一部分key的内存做了COW, 这些key的内存父子进程各自独立,子进程退出时,会回收它指向的这些内存空间。

而对于RDB期间,指向的内存数据没有被父进程修改过,那么这块内存数据还是归父进程所有,子进程不会进行回收。

转载自:https://juejin.cn/post/7230419964301426749
评论
请登录