Loading... # redis的持久化RDB与AOF ## RDB持久化 REDIS是一个内存数据库,对于内存有一个很大的问题就是断电易失,当redis重启后,想还原之前的数据,就需要将数据持久化。 ### 持久化方案一 ![01.png][1] 该方案为了保证时点性,在持久化时,发生阻塞,不对外提供服务,直到持久化完成。真实环境中,由于redis中数据量可能很大,所以持久化消耗时间可能很长,不对外提供服务肯定不现实。 ### 持久化方案二 ![02.png][2] 该方案不阻塞服务,同时将数据持久化落地,但是持久化的同时,数据有可能被修改,比如,我在8:00执行的持久化,需要半个小时执行完,过程中,某个数据被修改了,那么持久化的数据就已经不是8:00这个时间节点的了,这种方式造成时点混乱。 ### Linux父子进程 Linux的进程可以创建子进程,通常情况下,父子进程的数据是相互隔离的,也就是互相不可见。但是父进程可以通过export命令将数据放入环境变量,这样子进程就可以看到该数据了,而且父进程修改不会破坏子进程,子进程修改也不会破坏父进程。 ### Linux管道 - 管道是用来衔接两个命令,前一个命令的输出作为下一个命令的输入 - 管道会触发创建子进程,如:echo $BASHPID | more命令会创建两个子进程分别执行|前后的命令。 ### 进阶方案 了解了父子进程就管道之后,在redis持久化的时候,可以直接用管道创建一个子进程,将数据落地,因为父子进程数据互不影响,这样子进程落地的数据一定是管道创建时那一个时间节点的数据,而且父进程同时对外提供服务,写入数据也不会影响子进程。 #### 弊端 该方案相当于对内存中数据进行了一次复制,当数据量非常大的时候,子进程的创建速度会很慢,毕竟要从头到尾copy一份数据出来,而且还会占用双倍的内存空间。 ### fork fork是Linux内核提供的系统调用,计算机有一个物理内存,可以储存数据,同时,每一个进程也都有一个自己的虚拟内存,通过虚拟内存地址与物理内存地址的映射获取数据。进程通过变量名,得到虚拟内存地址,再通过映射获取物理内存地址,从而拿到该地址对应的数据。fork可以在创建子进程时,不复制内存数据,只将子内存的虚拟内存映射到物理内存相同的位置,也就是只操作指针。这样,数据只有一份,不会占用双倍空间,而且复制指针时间会很快。但是同样会有问题,大家都指向同一个物理地址,其中一个进程更改了数据,必然后影响到其它的进程。 ### copy-on-write 写入时复制,这是Linux提供的内核机制。使用fork创建子进程时并不发生复制数据操作,只在当前进程写入数据时,对数据进行复制。 ![03.png][3] 当某一个进程有数据写入时,将该key对应的物理内存中的数据复制一份,并改变当前进程虚拟内存的映射,映射到复制出来的数据上,然后进行写入或修改。玩的就是指针。 ### 最终方案 有了fork和copy-on-write机制,就可以实现redis的RDB持久化方案。 ![04.png][4] ### RDB使用方式与弊端 - save命令,该命令会阻塞服务,然后执行持久化,直到持久化完成。(场景明确,如:需要关机维护) - bgsave命令,该命令会通过fork创建子进程进行持久化,不会阻塞服务。 配置文件中可以配置bgsave规则(但是给的是save标识)。 ``` save 900 1 save 300 10 save 60 10000 ``` 可以增加多条记录 save seconds changes,当60s写入了10000次时触发,否则到300秒时,如果写入了10次触发,否则到900秒时写入了1次触发。 ``` dbfilename dump.rdb # rdb文件名称 dir /var/lib/redis/6379 # 保存的路径 ``` #### 弊端 - 不支持拉链,只有一个dump.rdb - 丢失数据相对多一些,时点与时点之间窗口数据容易丢失 ### 优点 - 类似java中的序列化,恢复的速度相对快 ## AOF持久化 APPPEND-ONLY-FILE.redis将写操作记录到文件中,类似于日志记录。通过名称可以知道,append-only,只做追加操作,也就是说,每次执行写操作,都会在文件中加入一条执行记录。 ![05.png][5] 优点:数据丢失量少。 缺点:日志体量无限大,恢复数据慢。 redis中,RDB与AOF可以同时开启,但是**如果开启了AOF只会用AOF模式恢复数据**,redis4.0之后,AOF中包含RDB全量数据,与新增加的写操作记录。 试想如果一个redis运行了10年,然后挂了,AOF文件有10个T,那么恢复需要多长时间?会不会内存溢出?内存并不会溢出,命令全都还原之后就是原来redis中的数据量,如果溢出的话,10年前就溢出了。但是恢复所需时间肯定无比漫长,如何解决这个问题? 实际上redis会重写aof文件,重写方案 #### redis4.0以前 删除抵消的命令,合并重复的命令,也就是如果我执行set k1 a,set k1 b,set k1 c,当重写之后,日志中只会剩下set k1 c,这种重写方式最终生成的依然是一个纯指令日志文件。 #### redis4.0以后 将之前的命令生成RDB保存到AOF文件中,并删除之前的日志命令,接下来到下次重写之间的写操作以指令的方式增量添加到AOF文件中。该AOF文件是一个混合体,包含一个该时点全量的RDB与之后的所有指令,这样既拥有RDB的快和AOF日志的全量。 ### AOF配置 redis归根结底还是一个内存数据库,它的特性就是快,但是AOF每条命令都需要写入文件,也就是触发I/O,这势必会影响效率,因此redis对AOF文件写操作设置了几个级别。 ``` appendonly yes # 是否开启aof appendfilename "appendonly.aof" # aof文件名称,会保存在RDB配置项配置的dir中 auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb appendfsync always appendfsync everysec appendfsync no ``` 重写触发条件是AOF文件的大小大于auto-aof-rewrite-min-size设置的大小,并且(当前文件大小-上一次重写后文件的大小)/上一次重写后文件的大小>=auto-aof-rewrite-percentage,每次重写结束redis都会记录文件的大小。第一次执行时,上一次重写后文件的大小是0,所以达到64MB就会重写,假如重写之后是50MB,计算(x-50)/50等于100%,得出x=100,也就是下一次需要大于等于100MB才会执行重写。 appendfsync表示buffer方案,当我们用java开启I/O,对文件进行写操作的时候,在close之前先需要flush,flush就是将内核缓冲区中的数据写入磁盘,如果不执行flush,缓冲区满了之后也会自动写入磁盘(但是最后一波没满的数据会丢失)。always表示每一次写操作都执行一次flush,该级别数据安全性最高,但是效率最低。no表示从不执行flush,直到buffer满时自动写入,该级别数据安全性最低,但是效率最高。everysec表示每秒执行一次flush,该级别是介于always与no之间的一种折中方案,也是redis的默认配置。 [1]: https://www.princelei.club/usr/uploads/2019/11/2292211055.png [2]: https://www.princelei.club/usr/uploads/2019/11/2161855970.png [3]: https://www.princelei.club/usr/uploads/2019/11/183889511.png [4]: https://www.princelei.club/usr/uploads/2019/11/3047567329.png [5]: https://www.princelei.club/usr/uploads/2019/11/2936332112.png Last modification:June 11th, 2020 at 06:13 pm © 允许规范转载