[翻译]redis持久化解密
本文来自Redis的作者,他在论坛看到大家对Redis持久化误解较大,所以写此文章论述持久化
首先要考虑的事情就是我们对数据库持久化抱着什么样的期待。为了做到这一点我们先看看写操作的过程具体发生了什么
- 客户端向数据库发送写命令(数据在客户端的内存中)
- 数据库接收到写命令(数据在服务端内存)
- 数据库调用write(2)系统调用,将数据往磁盘上写(数据在操作系统内核缓冲)
- 操作系统将数据传输到磁盘控制器(数据在磁盘缓存上)
- 磁盘控制器将数据写到物理介质中
如果我们考虑的故障只涉及到数据库层面而不涉及到操作系统内核(如数据库进程被杀掉或者崩溃),步骤 3 成功返回后就可以认为写操作是安全的。在write(2)系统调用返回后,数据被传输到内核,即使数据库进程崩溃,内核也会将数据写到磁盘。
如果考虑到突然停电或者更加灾难性的情况,所有缓存都已经失效,那么只有第5步骤完成后才可以认为写操作是安全的。
通过上述问题我们希望认识以下问题
- 数据库多长时间调用一次write(2),将数据写到内核中
- 内核多长时间会把缓冲的数据传给磁盘控制器
- 磁盘控制器多久会把数据写到物理介质上
让我们从步骤3开始看,可以使用write(2)系统调用将数据传输到内核中,也就是说对于第一个问题,数据库层面可以完全控制。但是不能控制系统调用持续的时间,内核写入缓冲区大小有限,如果磁盘的处理速度跟不上程序的写入速度,缓冲区就会达到它的最大值并且阻塞写操作。当磁盘将能够接受更多数据时,系统调用最终返回。
对于第四步,内核多久将数据写到磁盘控制器?操作系统由默认的策略,Linux默认30秒提交一次写入。这就意味着,如果期间出现故障,那么可能会丢失30秒内写入的数据。除此之外,操作系统提供fsync系统调用可以强制将内核缓冲的数据写到磁盘控制器上。这是一个代价较大的操作。
对于最开始的5个步骤,write系统调用可以控制第3步,fsync系统调用可以控制第4个,但是数据库层面已经无法控制到第5步了
数据损坏
以上分析了数据由应用层转移到磁盘的问题,但是持久化并不是只关注这些问题。另外一个问题就是数据损坏。如数据库在灾难性事件后是否仍然可读,或者数据库内部结构被破坏导致数据不能正确读取等。
通常有以下3个策略保证数据损坏后仍然可以恢复
- 数据写到磁盘代表数据库不关心故障发生的情况,而是要求用户使用副本进行数据恢复。
- 使用操作日志,每次操作时记录操作行为,以便在故障后通过日志恢复到一致性的状态
- 数据库系统不修改已经写入的数据,只以追加的方式完成写操作。这用数据本身就是一种日志
从持久化的可靠性来看,我们已经分析了数据库系统所需的所有元素。Redis提供了两种不同的持久化方式。
RDB快照
RDB快照是Redis最简单的持久化模式。在满足特定条件后,Redis将当前数据的快照存成一个数据文件。RDB文件不会被破坏,因为在生成快照时,当前进程会fork出子进程以只读的方式将数据写成RDB文件。RDB文件会是一个临时文件,当子进程完成后,会调用rename原子性的系统调用将文件改名为RDB文件。
如果丢失几分钟内的数据是不可接受的,那么RDB快照可能不是一个很好的持久化方案。那些对于最新数据要求较高的业务可以使用AOF的持久化方式。但是仍然需要注意的是RDB文件也用与主从同步
AOF
对于使用AOF持久化方式的业务,仍然推荐打开RDB快照功能,因为快照文件对于数据库备份、将数据发送到远程进程灾难恢复或者发生重大事故需要回滚数据等场景都有重要意义。
AOF是Redis主要的持久化方式。它的工作方式比较简单,每一次修改数据集的写操作执行时,这个操作都会被记录。
在启动命令中打开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
前3个命令更改了数据集。第4个命令没有,因为数据库中并不存在这样一个键。下面是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只记录那些更改数据集的命令。但是也并不是全部按照客户端命令来记录,比如INCRBYFLOAT 被记录成SET。
但这里可以看到AOF文件是一个只追加写入的文件,所以AOF文件不会故障。
AOF重写
如果每条命令都被记录,时间久了AOF文件就会异常庞大,所以Redis提供了AOF重写机制。重写过程不是读取老的AOF文件,而是直接读取内存数据,新的AOF文件中一个键值对记录的操作只会有一次,而不会重复操作某一个键,所以新的AOF文件会小很多。
重写数据会记录到临时文件中,重写完成后会命名为AOF文件。
你可能想知道在重写过程中,要有客户端向Redis服务端写入数据怎么办?在写入新文件的过程中,所有的写操作日志还是会写到原来老的AOF文件中,同时还会记录在内存缓冲区中。所以当重写过程完成后,会将缓冲区日志写到临时文件中,然后在重命名。
AOF的持久化
Redis配置文件中有appendfsync参数可以控制AOF文件的写入频率。这个参数有3个可选的值:no,everysec,always。
appendfsync no 表示Redis不会执行fsyns(2)系统调用,所以数据多久从内核写到磁盘就由操作系统控制了,一般Linux为30秒刷新一次。
appendfsync everysec 表示数据每隔一秒钟就会调用fsync(2)将数据写到磁盘。
然而如果磁盘的处理速度跟不上写入速度,fsync(2)系统调用的处理可能要超过一秒钟,Redis就延迟fsync(2)系统调用,等再过一秒后,也就是两秒后开始执行fsync(2),这时无论执行耗时多久都会坚持执行。所以即使在最坏情况下,Redis也能保存最近两秒写入的数据
appendfsync always 这种情况每次写操作都会调用fsync,这是数据时最安全的,但是系统调用的执行对程序性能也有一定影响。
Pipelining有什么不同
客户端使用pipeline机制可以一次向服务端发送N条命令。然后等待这N个结果一起返回。