参考:http://blog.nosqlfan.com/html/3813.htmlhtml
本文内容来源于Redis做者博文,Redis做者说,他看到的全部针对Redis的讨论中,对Redis持久化的误解是最大的,因而他写了一篇长文来对Redis的持久化进行了系统性的论述。文章很是长,也很值得一看,NoSQLFan将主要内容简述成本文。redis
什么是持久化,简单来说就是将数据放到断电后数据不会丢失的设备中。也就是咱们一般理解的硬盘上。sql
首先咱们来看一下数据库在进行写操做时到底作了哪些事,主要有下面五个过程。数据库
客户端向服务端发送写操做(数据在客户端的内存中)缓存
数据库服务端接收到写请求的数据(数据在服务端的内存中)安全
服务端调用write(2) 这个系统调用,将数据往磁盘上写(数据在系统内存的缓冲区中)app
操做系统将缓冲区中的数据转移到磁盘控制器上(数据在磁盘缓存中)nosql
磁盘控制器将数据写到磁盘的物理介质中(数据真正落到磁盘上)post
写操做大体有上面5个流程,下面咱们结合上面的5个流程看一下各类级别的故障。性能
当数据库系统故障时,这时候系统内核仍是OK的,那么此时只要咱们执行完了第3步,那么数据就是安全的,由于后续操做系统会来完成后面几步,保证数据最终会落到磁盘上。
当系统断电,这时候上面5项中提到的全部缓存都会失效,而且数据库和操做系统都会中止工做。因此只有当数据在完成第5步后,机器断电才能保证数据不丢失,在上述四步中的数据都会丢失。
经过上面5步的了解,可能咱们会但愿搞清下面一些问题:
数据库多长时间调用一次write(2),将数据写到内核缓冲区
内核多长时间会将系统缓冲区中的数据写到磁盘控制器
磁盘控制器又在何时把缓存中的数据写到物理介质上
对于第一个问题,一般数据库层面会进行全面控制。而对第二个问题,操做系统有其默认的策略,可是咱们也能够经过POSIX API提供的fsync系列命令强制操做系统将数据从内核区写到磁盘控制器上。对于第三个问题,好像数据库已经没法触及,但实际上,大多数状况下磁盘缓存是被设置关闭的。或者是只开启为读缓存,也就是写操做不会进行缓存,直接写到磁盘。建议的作法是仅仅当你的磁盘设备有备用电池时才开启写缓存。
所谓数据损坏,就是数据没法恢复,上面咱们讲的都是如何保证数据是确实写到磁盘上去,可是写到磁盘上可能并不意味着数据不会损坏。好比咱们可能一次写请求会进行两次不一样的写操做,当意外发生时,可能会致使一次写操做安全完成,可是另外一次尚未进行。若是数据库的数据文件结构组织不合理,可能就会致使数据彻底不能恢复的情况出现。
这里一般也有三种策略来组织数据,以防止数据文件损坏到没法恢复的状况:
第一种是最粗糙的处理,就是不经过数据的组织形式保证数据的可恢复性。而是经过配置数据同步备份的方式,在数据文件损坏后经过数据备份来进行恢复。实际上MongoDB在不开启journaling日志,经过配置Replica Sets时就是这种状况。
另外一种是在上面基础上添加一个操做日志,每次操做时记一下操做的行为,这样咱们能够经过操做日志来进行数据恢复。由于操做日志是顺序追加的方式写的,因此不会出现操做日志也没法恢复的状况。这也相似于MongoDB开启了journaling日志的状况。
更保险的作法是数据库不进行老数据的修改,只是以追加方式去完成写操做,这样数据自己就是一份日志,这样就永远不会出现数据没法恢复的状况了。实际上CouchDB就是此作法的优秀范例。
下面咱们说一下Redis的第一个持久化策略,RDB快照。Redis支持将当前数据的快照存成一个数据文件的持久化机制。而一个持续写入的数据库如何生成快照呢。Redis借助了fork命令的copy on write机制。在生成快照时,将当前进程fork出一个子进程,而后在子进程中循环全部的数据,将数据写成为RDB文件。
咱们能够经过Redis的save指令来配置RDB快照生成的时机,好比你能够配置当10分钟之内有100次写入就生成快照,也能够配置当1小时内有1000次写入就生成快照,也能够多个规则一块儿实施。这些规则的定义就在Redis的配置文件中,你也能够经过Redis的CONFIG SET命令在Redis运行时设置规则,不须要重启Redis。
Redis的RDB文件不会坏掉,由于其写操做是在一个新进程中进行的,当生成一个新的RDB文件时,Redis生成的子进程会先将数据写到一个临时文件中,而后经过原子性rename系统调用将临时文件重命名为RDB文件,这样在任什么时候候出现故障,Redis的RDB文件都老是可用的。
同时,Redis的RDB文件也是Redis主从同步内部实现中的一环。
可是,咱们能够很明显的看到,RDB有他的不足,就是一旦数据库出现问题,那么咱们的RDB文件中保存的数据并非全新的,从上次RDB文件生成到Redis停机这段时间的数据所有丢掉了。在某些业务下,这是能够忍受的,咱们也推荐这些业务使用RDB的方式进行持久化,由于开启RDB的代价并不高。可是对于另一些对数据安全性要求极高的应用,没法容忍数据丢失的应用,RDB就无能为力了,因此Redis引入了另外一个重要的持久化机制:AOF日志。
aof日志的全称是append only file,从名字上咱们就能看出来,它是一个追加写入的日志文件。与通常数据库的binlog不一样的是,AOF文件是可识别的纯文本,它的内容就是一个个的Redis标准命令。好比咱们进行以下实验,使用Redis2.6版本,在启动命令参数中设置开启aof功能:
./redis-server --appendonly yes
而后咱们执行以下的命令:
redis 127.0.0.1:6379> set key1 Hello OK redis 127.0.0.1:6379> append key1 " World!" (integer) 12 redis 127.0.0.1:6379> del key1 (integer) 1 redis 127.0.0.1:6379> del non_existing_key (integer) 0
这时咱们查看AOF日志文件,就会获得以下内容:
$ cat appendonly.aof *2 $6 SELECT $1 0 *3 $3 set $4 key1 $5 Hello *3 $6 append $4 key1 $7 World! *2 $3 del $4 key1
能够看到,写操做都生成了一条相应的命令做为日志。其中值得注意的是最后一个del命令,它并无被记录在AOF日志中,这是由于Redis判断出这个命令不会对当前数据集作出修改。因此不须要记录这个无用的写命令。另外AOF日志也不是彻底按客户端的请求来生成日志的,好比命令INCRBYFLOAT在记AOF日志时就被记成一条SET记录,由于浮点数操做可能在不一样的系统上会不一样,因此为了不同一份日志在不一样的系统上生成不一样的数据集,因此这里只将操做后的结果经过SET来记录。
你能够会想,每一条写命令都生成一条日志,那么AOF文件是否是会很大?答案是确定的,AOF文件会愈来愈大,因此Redis又提供了一个功能,叫作AOF rewrite。其功能就是从新生成一份AOF文件,新的AOF文件中一条记录的操做只会有一次,而不像一份老文件那样,可能记录了对同一个值的屡次操做。其生成过程和RDB相似,也是fork一个进程,直接遍历数据,写入新的AOF临时文件。在写入新文件的过程当中,全部的写操做日志仍是会写到原来老的AOF文件中,同时还会记录在内存缓冲区中。当重完操做完成后,会将全部缓冲区中的日志一次性写入到临时文件中。而后调用原子性的rename命令用新的AOF文件取代老的AOF文件。
从上面的流程咱们可以看到,RDB和AOF操做都是顺序IO操做,性能都很高。而同时在经过RDB文件或者AOF日志进行数据库恢复的时候,也是顺序的读取数据加载到内存中。因此也不会形成磁盘的随机读。
AOF是一个写文件操做,其目的是将操做日志写到磁盘上,因此它也一样会遇到咱们上面说的写操做的5个流程。那么写AOF的操做安全性又有多高呢。实际上这是能够设置的,在Redis中对AOF调用write(2)写入后,什么时候再调用fsync将其写到磁盘上,经过appendfsync选项来控制,下面appendfsync的三个设置项,安全强度逐渐变强。
当设置appendfsync为no的时候,Redis不会主动调用fsync去将AOF日志内容同步到磁盘,因此这一切就彻底依赖于操做系统的调试了。对大多数Linux操做系统,是每30秒进行一次fsync,将缓冲区中的数据写到磁盘上。
当设置appendfsync为everysec的时候,Redis会默认每隔一秒进行一次fsync调用,将缓冲区中的数据写到磁盘。可是当这一次的fsync调用时长超过1秒时。Redis会采起延迟fsync的策略,再等一秒钟。也就是在两秒后再进行fsync,这一次的fsync就无论会执行多长时间都会进行。这时候因为在fsync时文件描述符会被阻塞,因此当前的写操做就会阻塞。
因此,结论就是,在绝大多数状况下,Redis会每隔一秒进行一次fsync。在最坏的状况下,两秒钟会进行一次fsync操做。
这一操做在大多数数据库系统中被称为group commit,就是组合屡次写操做的数据,一次性将日志写到磁盘。
当设置appendfsync为always时,每一次写操做都会调用一次fsync,这时数据是最安全的,固然,因为每次都会执行fsync,因此其性能也会受到影响。
对于pipelining的操做,其具体过程是客户端一次性发送N个命令,而后等待这N个命令的返回结果被一块儿返回。经过采用pipilining就意味着放弃了对每个命令的返回值确认。因为在这种状况下,N个命令是在同一个执行过程当中执行的。因此当设置appendfsync为everysec时,可能会有一些误差,由于这N个命令可能执行时间超过1秒甚至2秒。可是能够保证的是,最长时间不会超过这N个命令的执行时间和。
这一块就很少说了,因为上面操做系统层面的数据安全已经讲了不少,因此其实不一样的数据库在实现上都大同小异。总之最后的结论就是,在Redis开启AOF的状况下,其单机数据安全性并不比这些成熟的SQL数据库弱。
这些持久化的数据有什么用,固然是用于重启后的数据恢复。Redis是一个内存数据库,不管是RDB仍是AOF,都只是其保证数据恢复的措施。因此Redis在利用RDB和AOF进行恢复的时候,都会读取RDB或AOF文件,从新加载到内存中。相对于MySQL等数据库的启动时间来讲,会长不少,由于MySQL原本是不须要将数据加载到内存中的。
可是相对来讲,MySQL启动后提供服务时,其被访问的热数据也会慢慢加载到内存中,一般咱们称之为预热,而在预热完成前,其性能都不会过高。而Redis的好处是一次性将数据加载到内存中,一次性预热。这样只要Redis启动完成,那么其提供服务的速度都是很是快的。
而在利用RDB和利用AOF启动上,其启动时间有一些差异。RDB的启动时间会更短,缘由有两个,一是RDB文件中每一条数据只有一条记录,不会像AOF日志那样可能有一条数据的屡次操做记录。因此每条数据只须要写一次就好了。另外一个缘由是RDB文件的存储格式和Redis数据在内存中的编码格式是一致的,不须要再进行数据编码工做。在CPU消耗上要远小于AOF日志的加载。
好了,大概内容就说到这里。更详细完整的版本请看Redis做者的博文:Redis persistence demystified。本文若有描述不周之处,就你们指正。