Redis的强劲性能很大程度上是由于其将所有数据都存储在了内存中,为了使Redis在重启之后仍能保证数据不丢失,例如:
(1) 将Redis作为数据库使用(存储历史数据)。
(2) 将Redis作为缓存服务器使用,如果缓存被穿透后会对性能造成很大影响,所有缓存同时失效会导致缓存雪崩,从而使服务器无法响应。
这时,我们希望Redis能将数据从内存中以某种方式同步到硬盘中,当服务器重启后可以根据硬盘中的记录恢复数据,这一过程就是持久化。
写操作的流程
首先我们来看一下数据库在进行写操作时到底做了哪些事,主要有下面五个过程。
1.客户端向服务端发送写操作(数据在客户端的内存中)
2.数据库服务端接收到写请求的数据(数据在服务端的内存中)
3.服务端调用write(2) 这个系统调用,将数据往磁盘上写(数据在系统内存的缓冲区中)
4.操作系统将缓冲区中的数据转移到磁盘控制器上(数据在磁盘缓存中)
5.磁盘控制器将数据写到磁盘的物理介质中(数据真正落到磁盘上)
写操作大致有上面5个流程,下面我们结合上面的5个流程看一下各种级别的故障。
•当数据库系统故障时,这时候系统内核还是OK的,那么此时只要我们执行完了第3步,那么数据就是安全的,因为后续操作系统会来完成后面几步,保证数据最终会落到磁盘上。
•当系统断电,这时候上面5项中提到的所有缓存都会失效,并且数据库和操作系统都会停止工作。所以只有当数据在完成第5步后,机器断电才能保证数据不丢失,在上述四步中的数据都会丢失。
通过上面5步的了解,可能我们会希望搞清下面一些问题:
•数据库多长时间调用一次write(2),将数据写到内核缓冲区
•内核多长时间会将系统缓冲区中的数据写到磁盘控制器
•磁盘控制器又在什么时候把缓存中的数据写到物理介质上
对于第一个问题,通常数据库层面会进行全面控制。而对第二个问题,操作系统有其默认的策略,但是我们也可以通过POSIX API提供的fsync系列命令强制操作系统将数据从内核区写到磁盘控制器上。对于第三个问题,好像数据库已经无法触及,但实际上,大多数情况下磁盘缓存是被设置关闭的。或者是只开启为读缓存,也就是写操作不会进行缓存,直接写到磁盘。建议的做法是仅仅当你的磁盘设备有备用电池时才开启写缓存。
所谓数据损坏,就是数据无法恢复,上面我们讲的都是如何保证数据是确实写到磁盘上去,但是写到磁盘上可能并不意味着数据不会损坏。比如我们可能一次写请求会进行两次不同的写操作,当意外发生时,可能会导致一次写操作安全完成,但是另一次还没有进行。如果数据库的数据文件结构组织不合理,可能就会导致数据完全不能恢复的状况出现。
这里通常也有三种策略来组织数据,以防止数据文件损坏到无法恢复的情况:
1.第一种是最粗糙的处理,就是不通过数据的组织形式保证数据的可恢复性。而是通过配置数据同步备份的方式,在数据文件损坏后通过数据备份来进行恢复。
2.另一种是在上面基础上添加一个操作日志,每次操作时记一下操作的行为,这样我们可以通过操作日志来进行数据恢复。因为操作日志是顺序追加的方式写的,所以不会出现操作日志也无法恢复的情况。
3.更保险的做法是数据库不进行老数据的修改,只是以追加方式去完成写操作,这样数据本身就是一份日志,这样就永远不会出现数据无法恢复的情况了。
针对上述数据库写操作流程及数据库文件组织策略,我们来认识Redis的持久化
Redis支持两种方式的持久化
一种是RDB(快照)方式,一种是AOF(追加文件)方式。可以单独使用其中一种或将二者结合使用。
RDB
RDB方式的持久化是通过快照(snapshotting)完成的,当符合一定条件时Redis会自动将内存中的所有数据生成一份副本并存储在硬盘上,这个过程即为“快照”。Redis会在一下几种情况下对数据进行快照:
- 根据配置规则进行自动快照。
- 用户执行save或者bgsave命令。
- 执行flushall命令。
- 执行复制时。
根据配置规则进行自动快照
你可以配置保存点,使Redis如果在每N秒后数据发生了M次改变就保存快照文件。例如下面这个保存点配置表示每60秒,如果数据发生了1000次以上的变动,Redis就会自动保存快照文件:
save 60 1000
保存点可以设置多个,Redis的配置文件就默认设置了3个保存点:
# 格式为:save <seconds> <changes>
# 可以设置多个。
save 900 1 #900秒后至少1个key有变动
save 300 10 #300秒后至少10个key有变动
save 60 10000 #60秒后至少10000个key有变动
如果想禁用快照保存的功能,可以通过注释掉所有"save"配置达到,或者在最后一条"save"配置后添加如下的配置:
save ""
文件路径和名称
默认Redis会把快照文件存储为当前目录下一个名为dump.rdb的文件。要修改文件的存储路径和名称,可以通过修改配置文件redis.conf实现:
# RDB文件名,默认为dump.rdb。
dbfilename dump.rdb
# 文件存放的目录,AOF文件同样存放在此目录下。默认为当前工作目录。
dir ./
用户执行save或者bgsave命令
除了让Redis进行自动快照外,当进行服务器重启,手动迁移以及备份的时候我们也需要手动执行快照操作,redis提供了两命令:
save 命令执行一个同步保存操作,将当前 Redis 实例的所有数据快照(snapshot)以 RDB 文件的形式保存到硬盘。
一般来说,在生产环境很少执行 SAVE 操作,因为它会阻塞所有客户端,保存数据库的任务通常由 BGSAVE 命令异步地执行。
bgsave 命令执行之后立即返回 OK ,然后 Redis fork 出一个新子进程,原来的 Redis 进程(父进程)继续处理客户端请求,而子进程则负责将数据保存到磁盘,然后退出。
客户端可以通过 LASTSAVE 命令查看最近一次成功执行快照的时间,返回一个unix时间戳。
Flushall命令
flushall清空整个 Redis 服务器的数据(删除所有数据库的所有 key )。注意:无论清空数据库的过程是否触发了自动快照条件,只要自动快照条件不为空,redis就会执行一次快照。例如:当定义快照条件为1秒内修改10000个键时进行快照,而是据库只有一个健,执行flushall也会触发快照,即使这一过程只有一个健被修改。当没有定义自动快照条件时,执行fluahall命令不进行快照。
执行复制
当设置了主从模式时,redis会在复制初始化时进行自动快照,即使没有自定义自动快照条件,并且没有手动执行过快照操作,也会生成RDB快照文件。
快照原理
1.redis 使用 fork 命令复制一份当前进程(父进程)的副本(子进程)
2.父进程继续接受并处理客户端发来的命令,而子进程开始将内存的数据存储写入硬盘的临时文件
3.当子进程写入完所有数据后调用将临时文件替换旧的 RDB 文件,到这里一次快照结束。
RBD 明显的不足就是一旦数据库出现崩溃之类的,RDB 里保存的数据可能不是最新的,从上次 RDB 到 redis 停机这段时间的数据可能会丢失。
AOF
Redis 将所有对数据库进行过写入的命令(及其参数)记录到 AOF 文件, 以此达到记录数据库状态的目的为了方便起见, 我们称呼这种记录过程为同步。
开启AOF方式持久化:
appendonly yes
AOF文件的保存位置和RDB文件位置相同,默认appendonly.aof,可通过appendfilename参数修改:
Appendfilename appendonly.aof
同步硬盘数据
根据“写的操作流程”可知,虽然每次执行更改数据库内存操作时,AOF都会将命令记录在AOF文件中,但是根据操作系统缓存机制,数据并没有真正地写入硬盘。一般来讲启用AOF的应用无法容忍这样的损失,这就需要Redis在写入AOF文件后主动将系统缓存同步到硬盘中。
Redis 可以通过appendfsync 参数设同步的时机:
#appendfsync always #每次执行都同步
appendfsync everysec #每秒执行一次同步,默认
#appendfsync no # 不主动同步操作,由操作系统决定(30秒一次)
Redis 允许同时开启AOF和RDB,重启时会使用AOF文件恢复数据,因为AOF方式可能丢失的数据更少。
从上面看出,RDB和AOF操作都是顺序IO操作,性能都很高。而同时在通过RDB文件或者AOF日志进行数据库恢复的时候,也是顺序的读取数据加载到内存中。所以也不会造成磁盘的随机读。
到底选择什么呢?下面是来自官方的建议:
通常,如果你要想提供很高的数据保障性,那么建议你同时使用两种持久化方式。如果你可以接受灾难带来的几分钟的数据丢失,那么你可以仅使用RDB。很多用户仅使用了AOF,但是我们建议,既然RDB可以时不时的给数据做个完整的快照,并且提供更快的重启,所以最好还是也使用RDB。在数据恢复方面:RDB的启动时间会更短,原因有两个:一是RDB文件中每一条数据只有一条记录,不会像AOF日志那样可能有一条数据的多次操作记录。所以每条数据只需要写一次就行了。另一个原因是RDB文件的存储格式和Redis数据在内存中的编码格式是一致的,不需要再进行数据编码工作,所以在CPU消耗上要远小于AOF日志的加载