Redis的数据是保存在内存中的,内存里面的数据是不持久的,要想做到持久化,必须要把在内存中的数据储存到硬盘上。
Redis速度非常快,数据只有在内存中才有这样的速度,但是为了持久,数据还是要想办法保存到硬盘上去。于是,Redis决定,内存中也存数据,硬盘上也存数据,这样的两份数据理论上是完全一样的。
当需要插入一个新的数据的时候,就把这个数据同时写入到内存和硬盘,当查询某个数据的时候直接从内存里面读取,硬盘里面的数据只是在redis重启的时候,用来恢复内存里面的数据的。
具体来说,Redis有两种持久化的策略:RDB和AOF。
目录
RDB
RDB,Redis DataBase ,也就是定期备份。
RDB定期的把我们Redis内存中的所有数据,都写入到硬盘中,生成一个快照。后续Redis一旦重启,就可以根据快照来把数据重新恢复到内存中。
“定期”,具体来说,有两种方式:手动触发和自动触发。
手动触发
通过redis客户端,执行特定的命令,来触发快照的生成。
这个特定的命令就是 save 或者 bgsave(backgroud)。
执行save的时候,redis会全力以赴的进行“快照生成”操作,此时会阻塞redis的其他客户端的命令。
但是并不推荐这样的方式,因为会占用大量的资源,会导致类似于 keys * 的后果。
所以我们主要讲解的是bgsave。
bgsave处理的时候,是不会影响到Redis服务器处理其他客户端的请求和命令。是如何做到的呢?Redis不是单线程的吗?此处Redis使用的就是“多线程”的方式来完成的并发编程。
bgsave执行流程
按照 1) 2) 3) 4) 5)的顺序来看,bgsave在后台异步地保存当前数据库的数据到磁盘。这个过程是通过创建一个子进程(fork)来完成的,这样可以避免在数据保存过程中阻塞主进程,从而影响Redis的性能。等到子进程完成生成RDB文件以后,在通过一个信号通知父进程。
dump.rdb
redis生成的rdb文件,是存放在redis的工作目录中的,可以在redis配置文件中设置。
redis.conf文件中,有一个名为dir的配置项,它指定了RDB文件和AOF文件的存储目录。
dir /var/lib/redis
后续redis服务器重新启动,就会尝试加载这个rdb文件。如果这个文件的格式或者某方面有错误,也可能导致数据加载失败。这里具体redis会怎么样,取决于rdb文件坏的地方在哪里,如果坏的地方正好是在文件末尾,就有可能还能正常启动,但是如果中间位置坏了,可能直接启动失败了。
当生成rdb镜像的时候,此时就会要把生成的快照数据,保存到一个临时文件中,当这个快照生成完毕之后,会先删除之前的rdb文件,再把新生成的rdb文件的名字改成刚才的dump.rdb。
自动触发
在Redis配置文件中,可以自己设置Redis每隔多久/修改多少次 就可以触发RDB。
save 900 1
:如果在900秒(15分钟)内至少有1个键被修改,则保存数据库。save 300 10
:如果在300秒(5分钟)内至少有10个键被修改,则保存数据库。save 60 10000
:如果在60秒内至少有10000个键被修改,则保存数据库。
假设我们随便插入几个键值对,没有运行手动触发的命令,也达不到自动触发的条件。但是这一些数值都是可以修改的,但是需要注意的是:生成一次RDB快照,成本还是比较高的,不能让这个操作执行的太频繁。
正因为rdb生成的不能太频繁,这就导致快照里面的数据和当前实时的数据情况可能有一些偏差。
- 12:00:00 生成了rdb
- 12:00:01 redis收到了大量的key变化请求
- 12:01:00 生成下一个快照文件
如果在第二步的过程中,redis服务器挂了,或者被异常重启(如kill - 9或者服务器掉电),就会导致12:00:00之后的数据都丢了。解决这个的办法就是AOF,我们之后再说。
如果使用service redis-server restart (shutdown命令),redis服务器也会触发生成快照这一操作。
RDB最大的问题,就是不能实时的持久化保存数据,在两次生成快照之间,实时数据可能会随着服务器重启而丢失。
AOF
AOF,append only file,会把用户的每个操作都记录到文件中,当redis重新启动的时候,就会读取这个aof文件中的内容,用来恢复数据。
AOF一般是关闭状态,通过修改配置文件可以打开。
那redis是一个单线程的服务器,在操作内存的同时,又要实时操作aof,同时写硬盘和内存,速度怎么保证?
实际上,AOF机制并不是直接让工作线程把数据写入硬盘,而是先写数据到内存中的缓冲区,等到有一定量之后再统一写入硬盘。
但是这不就是和RDB一样的问题了吗,如果数据在内存缓冲区里面,还没有写入到硬盘里面就主机掉电等出现异常,那岂不是还是会有部分数据丢失?
同步频率
其实这就是程序员需要做的取舍,redis给出了一些选项,刷新频率越高,性能影响就越大,同时数据的可靠性就越高;刷新频率越低,性能影响就越小,数据的可靠性就越低~
在配置文件中,有一个appendfsync选项可以自行设置。
重写机制
AOF文件持续增长,体积会越来越大,而且会影响到下一次 redis的启动时间。有没有什么办法能够减少AOF的体积呢?在AOF文件中,有一些内容是冗余的,比如:
对于AOF文件来说,我们可以忽略一些操作的过程,只关注操作的结果,这样就能够节省很多的资源占用。这就是Redis的重写机制,能够针对AOF文件进行整理操作,这个整理就能够剔除其中的冗余操作,并且合并一些操作,达到给AOF文件瘦身的效果。
重写流程
对于AOF的重写来说,仍然会创建子进程fork。父进程仍然负责接收请求,子进程负责对AOF文件进行重写。重写的时候,不关心AOF文件中原来都有一些什么,只关心内存中最终的数据状态。子进程只需要把内存中当前的数据获取出来,以AOF的格式写入到一个新的AOF文件中。
因为内存中的数据的状态,就已经相当于把AOF文件整理后的模样了。
- 在fork后,子进程写新的AOF文件,父进程仍然在不停的接受客户端的新请求,父进程会把这些请求产生的AOF文件先写到缓冲区里面,再刷新到原来的AOF文件。
- 在创建子进程的一瞬间,子进程就继承了当前父进程的内存状态,因此子进程的内存数据是父进程fork之前的状态,fork之后,新来的请求子进程是不知道的,它在自己干自己的事。
- 父进程又准备了一个aof_rewrite_buf缓冲区,专门放fork之后收到的数据。等待子进程写完新的AOF文件后,再把缓冲区里面的文件写入到新AOF文件中,就可以用新的AOF文件代替旧的AOF文件了。
- 父进程fork完之后,仍然在写旧的AOF文件,并且随着时间的推移新的AOF文件也会完成,那父进程还有必要在fork之后写旧的AOF文件吗?考虑极端情况,fork后子进程重写一半了,但是服务器挂了,子进程的数据就会丢失,新的AOF文件内容还不完整。如果父进程不写旧的AOF文件,重启就无法保证数据的完整性了。
如果在执行bgrewriteaof的时候,此时redis已经在进行AOF重写了,就不会再次执行AOF重写,会直接返回。
如果在执行bgrewriteaof的时候,发现当前redis在生成rdb文件的快照,此时AOF就会等待,等待RDB快照生成完毕再进行AOF重写。
其实AOF这样做的好处就是:RDB对于fork之后的数据就置之不理,而AOF则采用了aof_rewrite_buf来处理。这也就是RDB和AOF的设计理念不同,前者强调定期备份,而后者则是强调实时备份。
混合持久化
AOF本来是按照文本的方式来写入文件的,但是文本的方式写文件,后续加载的成本是比较高的,redis就引入了“混合持久化”的方式,结合了RDB和AOF的特点。
通过这个配置可以打开混合持久化,默认是打开的。
按照AOF的方式,每一个请求、操作都记录到文件里面,在触发了AOF重写操作后,就会把当前的内存按照rdb的二进制格式写入到新的AOF文件中,后续再进行的操作仍然是按照AOF文本的方式追加到文件后面。
当Redis上同时存在AOF文件和RDB快照的时候,以AOF为主,RDB就被忽略了~
至此,Redis持久化就告一段落了~之后还会继续进行Redis的学习~