当前位置: 首页 > 科技观察

Redis其实有比RDB和AOF更强大的持久化方式?

时间:2023-03-13 14:10:51 科技观察

介绍Redis中的数据是存放在内存中的。如果突然停机,内存中的所有数据都会丢失。如果数据能从后端数据库恢复出来就好了,但是如果数据只存在于Redis中,那么数据就会全部丢失。而且如果请求很多的话,对MySQL服务器的压力会很大。所以最好的办法就是持久化数据,宕机了可以快速恢复。“Redis有两种持久化方式,rdbsnapshot和aoflog。”RDBrdb是对当前数据库状态进行快照,通过二进制文件保存某一阶段的数据。你可以把它比作摄影。内存中的数据越多,生成快照的时间就越长,将快照写入磁盘的时间也就越长。“这个时候我们不用问了,生成快照会不会阻塞主线程?”如果会阻塞主线程,就会影响正常请求的处理。Redis中有两个命令可以用来生成RDB文件,一个是save,一个是bgsavesave:在主线程中执行,会造成阻塞bgsave:主线程fork出一个子进程负责创建rdb文件,这不会阻塞主线程。我们当然毫不犹豫的选择了bgsave,毕竟不会阻塞主线程。我们用bgsave生成图片的时候能不能修改数据呢?”如果允许修改数据,就会出现很多问题,比如bgsave子进程刚刚持久化了一个key,但是主线程删除了key,会造成数据不一致,如果不允许修改数据,所有写操作只能在rdb文件生成后进行,影响性能。”这时候就不得不提COW了。Redis使用多进程COW机制实现快照持久化。Copy-On-Write,COWRedis在持久化时会fork出一个子进程,快照持久化交给子进程完成。当子进程刚刚生成时,与父进程共享数据段和代码段,因此在进程分离的那一刻,内存增长机会并没有改变,子进程是持久化的,不会修改内存中的数据,但是主进程线程则不同,它会永久接收客户端的修改请求,然后修改内存中的数据,此时会利用操作系统的COW机制,将数据段分页,数据段由很多操作系统的页面,当父进程修改其中一个页面的数据时,会复制共享页面并分离出来,然后修改复制的页面,此时对应的页面of子进程没有变化,还是进程生成时的数据。随着父进程修改操作的进行,分离出来的共享页面越来越多,页面会不断增长,但不会超过原来内存的两倍。“子进程中的数据没有变化,可以安心持久化。”如果每1分钟生成一次快照,则在生成快照后执行的操作在停机(最多1分钟)操作后仍然会丢失)。如果我们缩短快照生成时间,就会影响Redis的性能。毕竟fork子进程会阻塞主线程,频繁读写磁盘,也会给磁盘带来很大的压力。这是不得不提的另一种坚持方式。当我们在aoflogAOF中执行一条命令时,我们会在aoflog中记录下相应的操作。当redis宕机时,我们只需要重放日志即可。数据可以恢复。而且Redis以文本的形式保存aof日志。比如我们执行下面的命令setkeyvalueaoffile,会添加如下内容*3$3set$3key$5value*3,表示当前命令有3个部分,每个部分由"$+数字组成beginning”,数字表示命令,键或值由几个字节组成。需要注意的是“redisrecordsthepost-writelog”,即先执行命令,再写入日志。那么如果命令执行成功了,就没有时间写日志了?那么服务宕机后命令就丢失了?因为aof日志是在主线程写的,如果每次都写日志到磁盘,那岂不是影响很大?表现?幸运的是,redis为我们提供了三种写aof日志的方式“always”:同步回写,写命令执行后,会同步到磁盘“everysec”:每秒回写,每执行一次写命令后,只是先将日志写入aof文件的内存缓冲区,每隔1秒将缓冲区的内容写入磁盘“否”:操作系统控制回写,每次执行写命令后,只将日志写入aof文件的内存是第一个Buffer,操作系统决定什么时候把buffer的内容写回磁盘。当aof的flush机制为always时,redis每处理一次write命令,都会把write命令flush到磁盘再返回。整个过程都在Redismain中,如果在一个线程中进行,势必会拖慢redis的性能。当aof的diskflushing机制为everysec时,redis会在写完内存后返回。磁盘刷新操作在后台线程中执行。当aof中的数据刷入磁盘时,当aof的刷盘机制为no时,关机后可能会丢失部分数据,一般不会用到。“一般情况下,aof磁盘清理机制可以配置为everysec。”aof日志通过保存执行过的写命令来记录数据库的状态。随着时间的推移,aof日志会越来越大,使用aof文件恢复数据的时间也会越来越长。有什么优化方案吗?这时候aof日志重写就登场了。AOF日志重写如果客户端依次执行以下5条命令127.0.0.1:6379>rpushlist1(integer)1//[1]127.0.0.1:6379>rpushlist2(integer)2//[1,2]127.0.0.1:6379>rpushlist3(整数)3//[1,2,3]127.0.0.1:6379>lpoplist"1"//[2,3]127.0.0.1:6379>rpushlist1(整数)3//[2,3,1]单独记录key列表的状态,必须有5条日志。如果这5条命令可以组合成命令rpushlist231就好了。其实aof日志重写就是干这个的,那么怎么实现呢?虽然Redis将生成新的aof文件的功能命名为“aof重写”,但aof重写不需要任何读取已有aof文件的Fetch、analyze操作。而是直接读取内存中的最新值,然后保存相应的命令。比如上面的例子,redis直接读取list的值,生成一个rpushlist231命令,放到aof日志中。“可见aof改写是一个非常耗时的操作,那么会不会阻塞主线程呢?”不会,因为Redis作为一种优化手段,肯定不希望它被阻塞。因此主线程每次重写都会fork一个bgrewriteaof子进程。bgrewriteaof子进程使用Copy-On-Write技术读取内存中的数据并写入新的aof日志》那么在重写aof的过程中,主线程执行的操作应该如何写入新的aof日志日志?”其实在aof日志重写的过程中,主线程会同步操作到aofbuffer和aofrewritebuffer。当子线程完成aof重写,并将aof重写缓冲区的内容写入新的aof日志时,会用新的aof日志替换旧的aof日志》redis生成rdb文件和aof日志重写,都被执行通过主线程分叉子流程。”Redis4.0HybridPersistence《使用RDB进行持久化时,宕机后会丢失部分数据》。这时候可以缩短RDB生成快照的时间间隔,但是如果频繁生成RDB快照,就会出现下面两个问题。经常将全量数据写入磁盘,会给磁盘造成很大的压力。主线程fork子进程生成RDB快照,子进程生成RDB快照不会阻塞主线程,但是主线程通过fork创建子进程的过程会阻塞主线程。主线程内存越大,阻塞时间越长。“使用AOF进行持久化时,数据完整性更高,但宕机后的恢复时间更长。”有什么办法吗?能否实现快速恢复并保证数据的高度完整性?你别说,真的有。Redis4.0提出了一种混合持久化方式。即按照一定的频率执行快照,在两次快照之间,使用aof日志记录这段时间内所有的命令操作。当生成第二个快照时,可以清除aof文件,因为此时命令已经记录在快照中了。当Redis重启时,可以先加载rdb文件的内容,然后重放aof日志。不同的是,rdbaof持久化方式是在某个时刻生成一个快照文件,将写命令实时记录到日志中。数据完整性不完整,取决于备份周期。完整性比较高,依赖刷机机制。二进制文件比较小,保存了原命令,文件大,宕机恢复时间快,使用场景宕机需要快速恢复,允许一定量的数据丢失,对数据可靠性要求高。转载本文请联系Java石塘公众号。