Redis如何进行持久化Redis中的数据存在内存中如果突然宕机那么内存中的数据将全部丢失。如果数据能从后端数据库恢复还好如果数据只存在Redis中那数据就全丢失了。并且如果请求量很多MySQL服务器的压力会很大。所以最好的方式是对数据进行持久化并能当宕机的时候能快速恢复Redis提供了三种主要的持久化机制RDBRedis DataBase、AOFAppend-Only File 以及自 Redis 4.0 引入的混合持久化RDBrdb就是对当前数据库的状态做一个快照将某个阶段的数据通过二进制文件保存下来。你可以类比照相。内存中的数据越多生成快照的时候就越长同时将快照写入磁盘耗费的时间也越长。这时我们不经要问生成快照会阻塞主线程吗如果会阻塞主线程则会影响正常请求的处理在Redis中有两个命令可以用于生成RDB文件一个是save另一个是bgsavesave在主线程中执行会导致阻塞bgsave主线程fork出一个子进程负责创建rdb文件不会阻塞主线程我们当然毫不犹豫的选择bgsave毕竟不会阻塞主线程那当我们使用bgsave时生成镜像的时候数据还能被修改吗如果数据允许被修改会有很多问题。例如bgsave子进程刚持久化完一个key结果主线程就把这个key给删了会造成数据不一致。如果数据不允许被修改那么所有写操作只能等到rdb文件生成完才能执行影响性能。这时我们就不得不提到COW了redis是使用多进程COW机制来实现快照持久化的Copy-On-WriteCOWRedis在进行持久化的时候会fork出一个子进程快照持久化交给子进程来完成。子进程刚刚产生的时候它和父进程共享里面的数据段和代码段。所以在进程分离的一瞬间内存的增长机会没有变化。子进程做持久化不会修改内存中的数据但是主线程不一样它会持久接收客户端的修改请求然后修改内存中的数据。这时就会使用操作系统的COW机制来进行数据段页面的分离。数据段由很多操作系统的页面组成当父进程对其中一个页面的数据进行修改时会将被共享的页面复制一份分离出来然后对这个复制的页面进行修改。这时子进程相应的页面是没有变化的还是进程产生时的数据。随着父进程修改操作的进行越来越多共享的页面被分离出来页面就会持续增长但是不超过原有内存的2倍。子进程中的数据一直没有变化它就可以安心的做持久化了。如果每隔1分钟生成一个快照宕机后还是会丢失快照生成后所执行的操作最多为1分钟之内的操作。我们把生成快照的时间缩短又会影响Redis性能毕竟fork子进程会阻塞主线程频繁读写磁盘也会给磁盘带来很大压力。这是就不得不提到另一种持久化的方式aof日志AOF当我们每次执行一条命令的时候把对应的操作记到aof日志中当redis宕机的时候我们只要重放日志就能恢复数据。而且Redis是以文本的形式保存aof日志的例如当我们执行如下一条命令setkey valueaof文件中就会追加如下的内容这个文件保存的是RESP协议的内容Redis客户端和服务单是通过RESP协议进行交互的*3$3set$3key$5value需要注意的是redis中记录的是写后日志即先执行命令再写日志。那要是命令执行成功还没有来得及写日志那么服务宕机后这条命令不是丢失了因为aof日志是在主线程中写入的如果每次写日志都刷到磁盘岂不是很影响性能好在redis给我们提供了三种写aof日志的方式always同步写回写命令执行完就同步到磁盘everysec每秒写回每个写命令执行完只是先把日志写到aof文件的内存缓冲区每隔1秒将缓冲区的内容写入磁盘no操作系统控制写回每个写命令执行完只是先把日志写到aof文件的内存缓冲区由操作系统决定何时将缓冲区内容写回到磁盘当aof的刷盘机制为alwaysredis每处理一次写命令都会把写命令刷到磁盘中才返回整个过程是在Redis主线程中进行的势必会拖慢redis的性能当aof的刷盘机制为everysecredis写完内存后就返回刷盘操作是放到后台线程中去执行的后台线程每隔1秒把内存中的数据刷到磁盘中当aof的刷盘机制为no宕机后可能会造成部分数据丢失一般不采用。一般情况下aof刷盘机制配置为everysec即可aof日志是通过保存被执行的写命令来记录数据库状态的随着时间的流逝aof日志会越来越大使用aof文件来还原数据所需要的时间也越来越长。有没有什么优化方案呢此时aof日志重写登场了。AOF日志重写AOF 日志重写AOF Rewrite是 Redis 用来解决 “AOF 文件体积膨胀” 的核心机制。它的本质是不读取旧的 AOF 文件而是直接读取当前内存中的键值对用一条命令代替此前对该键的多条修改命令生成一个新的 AOF 文件来替换旧文件假如说客户端依次执行了如下5条命令127.0.0.1:6379rpush list1(integer)1// [1]127.0.0.1:6379rpush list2(integer)2// [1, 2]127.0.0.1:6379rpush list3(integer)3// [1, 2, 3]127.0.0.1:6379lpop list1// [2, 3]127.0.0.1:6379rpush list1(integer)3// [2, 3, 1]单独记list这个key的状态就得有5条日志。要是能把这5条命令合并成 rpush list 2 3 1这个命令就好了。其实这就是aof日志重写要干的事情那么如何实现呢虽然Redis将生成新的aof文件的功能命名为aof重写但是aof重写并不需要对现有aof文件进行任何读取分析操作。而是直接读取读取内存中的最新值然后保存对应的命令。例如上面的例子redis直接读取list的值并生成一条rpush list 2 3 1命令放到aof日志中。可以看到aof重写是一个非常耗时的操作那么它会阻塞主线程吗不会因为作为一种优化手段Redis肯定不希望它被阻塞。所以每次重写的时候主线程fork出一个bgrewriteaof子进程。bgrewriteaof子进程使用Copy-On-Write技术来读取内存中的数据写新的aof日志那在重写aof日志的过程中主线程执行的操作该怎么写到新的aof日志中1. 在子进程重写期间主线程仍在接收新的写命令。这些新命令会被同时写入到两个缓冲区AOF 缓冲区aof_buf照常写入旧的 AOF 文件保证即使重写期间崩溃旧 AOF 依然完整可用。AOF 重写缓冲区aof_rewrite_buf暂存重写期间产生的新写命令防止重写期间的数据丢失2. 子进程将内存数据全部写完到临时文件后向主线程发送一个信号Signal主进程收到信号后会短暂阻塞将 AOF 重写缓冲区aof_rewrite_buf中的增量写命令追加写入到新的临时 AOF 文件中。此时新 AOF 文件的数据状态便与当前内存完全一致了3. 主线程使用新的 AOF 文件原子地替换rename掉旧的 AOF 文件。至此AOF 重写宣告完成Redis生成rdb文件和aof日志重写都是通过主线程fork子进程的方式让子进程来执行的Redis4.0混合持久化当使用RDB做持久化时宕机后会造成一部分数据的丢失此时可以缩短生成RDB快照的时间间隔但是如果频繁的生成RDB快照有会有如下两方面的问题频繁的将全量数据写到磁盘会给磁盘造成很大的压力主线程fork子进程来生成rdb快照子进程生成rdb快照不会阻塞主线程但是主线程通过fork创建子进程的过程会阻塞主线程主线程的内存越大阻塞时间越长。当使用AOF做持久化的时候数据完整性较高但是宕机后恢复时间比较长。那有没有什么方法即能做到快速恢复又能保证数据完整性较高你别说还真有。Redis4.0提出了一种混合持久化的方式。混合持久化同样也是通过bgrewriteaof完成的即aof重写的进程不同的是当开启混合持久化时fork出的子进程先将共享的内存副本全量的以RDB方式写入aof文件然后在将重写缓冲区的增量命令以AOF方式写入到文件写入完成后通知主进程更新统计信息并将新的含有RDB格式和AOF格式的AOF文件替换旧的的AOF文件。简单的说新的AOF文件前半段是RDB格式的全量数据后半段是AOF格式的增量数据如下图在Redis重启的时候可以先加载rdb文件的内容然后重放aof日志即可。区别rdbaof持久化方式生成某一时刻快照文件实时记录写命令到日志数据完整性不完整取决于备份周期完整性相对较高取决于刷盘机制文件大小二进制文件相对较小保存原始命令文件较大宕机恢复时间快慢使用场景宕机需要快速恢复允许一定数量的数据丢失对数据可靠性要求较高生产环境如何选择混合持久化首选配置开启 AOF并启用混合持久化。这在保证数据高安全性的同时提供了极快的恢复速度。双开经典防线如果使用的是较老版本的 Redis建议 RDB 和 AOF 同时开启。Redis 重启时会优先加载 AOF因为其数据更完整而 RDB 则作为定期的冷备数据。