Redis中的数据存在内存中,如果突然宕机,那么内存中的数据将全部丢失。如果数据能从后端数据库恢复还好,如果数据只存在Redis中,那数据就全丢失了。并且如果请求量很多,MySQL服务器的压力会很大。
所以最好的方式是对数据进行持久化,并能当宕机的时候能快速恢复
在Redis中有如下两种持久化方式,rdb快照和aof日志
rdb就是对当前数据库的状态做一个快照,将某个阶段的数据通过二进制文件保存下来。你可以类比照相。内存中的数据越多,生成快照的时候就越长,同时将快照写入磁盘耗费的时间也越长。
这时我们不经要问,生成快照会阻塞主线程吗? 如果会阻塞主线程,则会影响正常请求的处理
在Redis中有两个命令可以用于生成RDB文件,一个是save,另一个是bgsave
我们当然毫不犹豫的选择bgsave,毕竟不会阻塞主线程
那当我们使用bgsave时生成镜像的时候数据还能被修改吗?
如果数据允许被修改,会有很多问题。例如,bgsave子进程刚持久化完一个key,结果主线程就把这个key给删了,会造成数据不一致。
如果数据不允许被修改,那么所有写操作只能等到rdb文件生成完才能执行,影响性能。
这时我们就不得不提到COW了,redis是使用多进程COW机制来实现快照持久化的
Redis在进行持久化的时候,会fork出一个子进程,快照持久化交给子进程来完成。子进程刚刚产生的时候,它和父进程共享里面的数据段和代码段。所以在进程分离的一瞬间,内存的增长机会没有变化。
子进程做持久化,不会修改内存中的数据,但是主线程不一样,它会持久接收客户端的修改请求,然后修改内存中的数据。
这时就会使用操作系统的COW机制来进行数据段页面的分离。数据段由很多操作系统的页面组成,当父进程对其中一个页面的数据进行修改时,会将被共享的页面复制一份分离出来,然后对这个复制的页面进行修改。这是子进程相应的页面是没有变化的,还是进程产生时的数据。
随着父进程修改操作的进行,越来越多共享的页面被分离出来,页面就会持续增长,但是不超过原有内存的2倍。
子进程中的数据一直没有变化,它就可以安心的做持久化了。
如果每隔1分钟生成一个快照,宕机后还是会丢失快照生成后所执行的操作(最多为1分钟之内的操作)。我们把生成快照的时间缩短,又会影响Redis性能,毕竟fork子进程会阻塞主线程,频繁读写磁盘,也会给磁盘带来很大压力。
这是就不得不提到另一种持久化的方式,aof日志
当我们每次执行一条命令的时候,把对应的操作记到aof日志中,当redis宕机的时候我们只要重放日志就能恢复数据。而且Redis是以文本的形式保存aof日志的
例如当我们执行如下一条命令
set key value
aof文件中就会追加如下的内容
*3
$3
set
$3
key
$5
value
*3表示当前命令有3个部分,每部分都是由“$+数字开头”,数字表示命令,键或者值由几个字节组成
需要注意的是,redis中记录的是写后日志,即先执行命令,再写日志。
那要是命令执行成功,还没有来得及写日志?那么服务宕机后这条命令不是丢失了?
应为aof日志是在主线程中写入的,如果每次写日志都刷到磁盘,岂不是很影响性能?
好在redis给我们提供了三种写aof日志的方式
always:同步写回,写命令执行完就同步到磁盘
everysec:每秒写回,每个写命令执行完,只是先把日志写到aof文件的内存缓冲区,每隔1秒将缓冲区的内容写入磁盘
no:操作系统控制写回,每个写命令执行完,只是先把日志写到aof文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回到磁盘
我们根据系统对高性能和可靠性的要求选择即可。
aof日志是通过保存被执行的写命令来记录数据库状态的,随着时间的流逝,aof日志会越来越大,使用aof文件来还原数据所需要的时间也越来越长。有没有什么优化方案呢?此时aof日志重写登场了。
假如说客户端依次执行了如下5条命令
127.0.0.1:6379> rpush list 1
(integer) 1 // [1]
127.0.0.1:6379> rpush list 2
(integer) 2 // [1, 2]
127.0.0.1:6379> rpush list 3
(integer) 3 // [1, 2, 3]
127.0.0.1:6379> lpop list
"1" // [2, 3]
127.0.0.1:6379> rpush list 1
(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日志中?
其实在aof日志重写的过程中,主线程会把操作同步到aof缓冲区和aof重写缓冲区。当子线程完成aof重写,并且将aof重写缓冲区的内容,写入新的aof日志中时,就会用新的aof日志代替旧的aof日志