看了一天的技术文章,趁还没下班前写下自己的总结
首先明确概念:redis是可以持久化数据的。大致可以分为两种方式。
第一种:snapshot
在redis.conf设置自动执行snapshot所需要满足的条件即可。默认的例子是:save 900 1,save 300 10,save 60 1000. 只要满足其中任何一个条件即可。
snapshot过程:
1:首先fork一个子进程。主进程继续接收连接请求,子进程开始偷偷的对内存数据做快照。互不影响。做快照是基于copy on write模式,即父子进程共享相同的物理页。当父进程需要对相应的物理页做修改时,会生成相应物理页的一个副本,针对这个副本进行修改,原物理页不发生改变。这是为了保证子进程在做快照时数据的一致性。
2:做快照会产生一个临时文件。快照完成后,会将临时文件rename成dbfilename定义的名称。也就是对之前dump的文件进行覆盖。子进程退出。
3:每次做快照是对当前内存中的数据做快照,可以看成是完全备分,并不是增量备份。
缺点:数据量非常大的时候,它会有非常严重的IO性能问题。 在做快照之前发生崩溃,会丢失从上次快照到现在这期间的数据。
第二种:AOF(Appendonly File)
为了应对数据丢失的问题,AOF应运而生。模式可以看成Mysql的二进制,记录DML操作命令。设置appendonly yes开启AOF功能。
大致原理:每一次更新请求被处理后,会相应的在appendonly.aof文件记录DML操作命令。所以,appendonly.aof文件会越来越大。在每次启动redis时,它会通过运行appendonly.aof中记录的DML操作命令在内存中重构数据。可想而知,如果文件非常大,启动过程将是非常慢的。
这里就会有两个问题:
1:将DML操作命令写到appendonly.aof文件,这个过程中它并不是串型的。打个比方,现在有DML操作命令要写到appendonly.aof文件,redis就会调用os函数告诉os,我有东西要你写到appendonly.aof文件,os回应说好的。这时redis操作完成,认为已经写入文件了。可是,os它有自己的缓存策略并不保证立即完成这个动作,所以如果在这个时候crash,这个DML操作命令是没有写到文件中去的。同样造成数据丢失。
2:appendonly.aof文件只会越来越大,如果超过几十G,在启动时是非常可怕的。
解决第一个问题:设置appendfsync 指定os的flush规则。
appendfsync:everysec,每隔一秒让OS刷新,将DML操作命令真正的写到文件中。虽然仍然存在丢失数据的可能,但在性能与数据安全两个方面做了个折中,效果最好。
解决第二个问题:使用bgrewriteaof。大致过程如下:
1. redis调用fork ,现在有父子两个进程
2. 子进程根据内存中的数据库快照,往临时文件中写入重建数据库状态的DML命令。
3.父进程继续处理client请求,除了把新接收到的DML命令写入到原来的aof文件中(并不是真正写入原来aof文件,而是生成一个副本往副本里写DML命令,跟做快照的方式是一样的)。同时把收到的DML命令缓存起来。将新接收到的DML命令写入到原来的aof文件,是为了保证如果子进程重写失败的话原aof文件并不会丢失任何DML命令。
4.当子进程把快照内容按照DML命令方式写到临时文件中后,子进程发信号通知父进程。然后父进程把缓存的DML命令也写入到临时文件。
5.现在父进程可以使用临时文件替换老的aof文件,并重命名,后面收到的写命令也开始往新的aof文件中追加。
需要注意到是重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用DML命令的方式重写了一个新的aof文件,这点和快照有点类似。这样处理的话可以将很大的appendonly.aof文件处理成比较小的一个文件。