技巧💡
不管是RDB,还是AOF,都无法保证数据不丢失。
- AOF,AOF 模式的三种配置选项no、everysec 和 always 都会存在数据丢失的情况
- no模式
- everysec,每秒肯定存在最后一秒的数据丢失的情况
- always,基本上不丢数据(每次执行客户端命令的时候操作并没有写到aof文件中,只是写到了aof_buf内存当中,当进行下一个事件循环的时候执行beforeSleep之时,才会去fsync到disk中)。执行命令后,都要刷盘,速度慢
- RDB,在一个事务执行后,而下一次的 RDB 快照还未执行前,如果发生了实例宕机,这种情况下,事务修改的数据也是不能保证持久化的
持久化(RDB + AOF)
缓存数据库的读写都是在内存中,所以它的性能才会高,但在内存中的数据会随着服务器的重启而丢失,为了保证数据不丢失,要把内存中的数据存储到磁盘,以便缓存服务器重启之后,还能够从磁盘中恢复原有的数据,这个过程就是 Redis 的数据持久化
Redis 的数据持久化有三种方式
AOF 日志(Append Only File,文件追加方式, 主线程中执行)
通常情况下,关系型数据库(如 MySQL)的日志都是“写前日志”(Write Ahead Log, WAL),也就是说,在实际写数据之前,先把修改的数据记到日志文件中,以便当出现故障时进行恢复,比如 MySQL 的 redo log(重做日志),记录的就是修改后的数据。
而 AOF 里记录的是 Redis 收到的每一条命令,这些命令是以文本形式保存的,不同的是,Redis 的 AOF 日志的记录顺序与传统关系型数据库正好相反,它是写后日志,“写后”是指 Redis 要先执行命令,把数据写入内存,然后再记录日志到文件。
AOF 执行过程
那么面试的考察点来了:Reids 为什么先执行命令,在把数据写入日志呢?为了方便你理解,我整理了关键的记忆点:
- 因为 ,Redis 在写入日志之前,不对命令进行语法检查;
- 所以,只记录执行成功的命令,避免了出现记录错误命令的情况;
- 并且,在命令执行完之后再记录,不会阻塞当前的写操作。
当然,这样做也会带来风险(这一点你也要在面试中给出解释)。
- 数据可能会丢失: 如果 Redis 刚执行完命令,此时发生故障宕机,会导致这条命令存在丢失的风险。
- 可能阻塞其他操作: 虽然 AOF 是写后日志,避免阻塞当前命令的执行,但因为 AOF 日志也是在主线程中执行,所以当 Redis 把日志文件写入磁盘的时候,还是会阻塞后续的操作无法执行
写回磁盘的策略
- Always,同步写回:每个写命令执行完,立马同步地将日志写回磁盘;基本上不丢数据
- Everysec,每秒写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘;
- No,操作系统控制的写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘。
AOF日志文件太大怎么办?(通用方案,日志压缩,即AOF重写)
类似LSM树的方案,操作日志太大了,可以进行压缩。相同key的操作,可以只记录最后一条,这样就减少了日志文件的提交。
比如,cmd1: set key value1。cmd2: set key value2。 cmd3: set key value3.在进行整理压缩日志文件时,可以只存 cmd3: set key value3即可
AOP非阻塞重写过程
和 AOF 日志由主线程写回不同,重写过程是由后台子进程 bgrewriteaof 来完成的,这也是为了避免阻塞主线程,导致数据库性能下降。
我把重写的过程总结为“一个拷贝,两处日志”。
“一个拷贝”就是指,每次执行重写时,主线程 fork 出后台的 bgrewriteaof 子进程。此时,fork 会把主线程的内存拷贝一份给 bgrewriteaof 子进程,这里面就包含了数据库的最新数据。然后,bgrewriteaof 子进程就可以在不影响主线程的情况下,逐一把拷贝的数据写成操作,记入重写日志。
“两处日志”又是什么呢?
因为主线程未阻塞,仍然可以处理新来的操作。此时,如果有写操作,第一处日志就是指正在使用的 AOF 日志,Redis 会把这个操作写到它的缓冲区。这样一来,即使宕机了,这个 AOF 日志的操作仍然是齐全的,可以用于恢复。
而第二处日志,就是指新的 AOF 重写日志。这个操作也会被写到重写日志的缓冲区。这样,重写日志也不会丢失最新的操作。等到拷贝数据的所有操作记录重写完成后,重写日志记录的这些最新操作也会写入新的 AOF 文件,以保证数据库最新状态的记录。此时,我们就可以用新的 AOF 文件替代旧文件了。
RDB 快照(Redis DataBase):
摘要
将某一个时刻的内存数据,以二进制的方式写入磁盘。
redis 增加了 RDB 内存快照(所谓内存快照,就是将内存中的某一时刻状态以数据的形式记录在磁盘中)的操作,它即可以保证可靠性,又能在宕机时实现快速恢复
RDB 做快照时会阻塞线程吗?会,类似jvm stw
因为 Redis 的单线程模型决定了它所有操作都要尽量避免阻塞主线程,所以对于 RDB 快照也不例外,这关系到是否会降低 Redis 的性能。
- save 命令在主线程中执行,会导致阻塞
- bgsave 命令则会创建一个子进程,用于写入 RDB 文件的操作,避免了对主线程的阻塞,这也是 Redis RDB 的默认配置。在这个配置下,fork进程也会阻塞
RDB 做快照的时候数据能修改吗?
很明显,在做快照的时候,做修改会导致数据不一致。处理方案也很简单,就是把修改的数据,同步一份给bgsave进程处理就好。通过使用多进程的copy-on-write,fbgsave 子进程是由主线程 fork 生成的,可以共享主线程的所有内存数据。bgsave 子进程运行后,开始读取主线程的内存数据,并把它们写入 RDB 文件。如果主进程进行修改,此时并不是在原来的内存进行修改,而是新增一个副本,进行修改。主线程的内存空间的数据,就会指向新的内存副本。而fork出来的bgsave子进程并不收影响。还是直接原来的旧值。这样就达到了bgsave进程,读取的是快照数据。而主进程又不妨碍写。
混合持久化方式(游戏中的暂存点功能)
Redis 对 RDB 的执行频率非常重要,因为这会影响快照数据的完整性以及 Redis 的稳定性。Redis 4.0 后,增加了 AOF 和 RDB 混合的数据持久化机制: 把数据以 RDB 的方式写入文件,再将后续的操作命令以 AOF 的格式存入文件,既保证了 Redis 重启速度,又降低数据丢失风险。
AOF重写和RDB的
AOF 重写和 RDB 创建是比较类似的,它们都会创建一个子进程来遍历所有的数据库,并把数据库中的每个键值对记录到文件中。不过,AOF 重写和 RDB 文件又有两个不同的地方:
- 一是,AOF 文件中是以“命令 + 键值对”的形式,来记录每个键值对的插入操作,而 RDB 文件记录的是键值对数据本身;
- 二是,在 AOF 重写或是创建 RDB 的过程中,主进程仍然可以接收客户端写请求。不过,因为 RDB 文件只需要记录某个时刻下数据库的所有数据就行,而 AOF 重写则需要尽可能地把主进程收到的写操作,也记录到重写的日志文件中。所以,AOF 重写子进程就需要有相应的机制来和主进程进行通信,以此来接收主进程收到的写操作。
AOF和RDB,如果实现记录某个时刻的所有数据,而不影响主进程写。注意通过bgsave已经不阻塞主流程
多进程 copy-on-write💡
在_fork_出子进程后,与父进程共享内存空间,两者只是虚拟空间不同,但是其对应的物理空间是同一个。
作用就像Java中的CopyOnWriteArrayList的数据结构一样. 当有人要写一个cow对象时, 我们不会直接在该对象进行操作, 而是复制一份它的副本数据, 对该副本进行写操作, 最后将修改后的副本替换原数据. 它的作用就是可以实现对cow对象做到并发读-写不冲突, 读-读不冲突. 唯有写写才冲突。
数据快照就一定是12:00这个时刻的数据. 所以如果在12:00之后, 还有其他客户端的写操作请求, 那么Redis的父进程就会拷贝一份12:00的完整副本数据, 专门用于做数据修改。
Q/A
为什么使用进程的cow,而不是创建线程来
why does redis use fork to create child process other than create a thread to rewrite aof or rdb · Issue#6722 · redis/redis · GitHub