前言
Redis提供了两种数据存储方式,分别是:cache-only && persistence;cache-only顾名知义,是用与缓存服务的,数据在服务器终止后将消失,在此模式下将不存在"数据恢复"的方式,是一种安全性低、效率高、易扩展的模式;persistence即内存中的数据持久备份至磁盘文件中,在服务重启之后能够恢复数据,这种模式下数据的安全性大大提高。cache-only没有什么讲的,这里主要说明Redis的持久化存储模式。对于persistence持久化存储,Redis提供了两种持久化方法:"Redis DataBase"简称"RDB",以及"Append-Only Flie"简称"AOF",当然了除了这两种方式之外,Redis在早期版本中还使用虚拟内存的方法,不过在Redis 3.0版本之后已然被废弃掉了。
RDB持久化方式
RDB是"Redis DataBase"的简称,RDB持久化是指按指定的策略将内存中的数据集快照并写入磁盘。是默认的持久化方式,这种方式就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。可以通过修改配置文件设置自动做快照的方式。我们可以设置redis在n秒内如果超过m次key被修改就自动做快照,下面是默认的快照保存配置。匹配优先级由下到上。
save 900 1 # 900秒内如果超过1次key被修改,则发起快照保存
save 300 10 # 300秒内超过10次key被修改,则发起快照保存
save 60 10000 # 60s内发生10000次可以值修改,则发起快照
1 RDB文件存储过程
1)当有相关操作时,redis父进程调用fork(),创建子进程。
2)父进程继续处理client请求,子进程负责将内存内容写入到临时文件。由于os的写时复制机制(copy on write)父子进程会共享相同的物理页面,当父进程处理写请求时os会为父进程要修改的页面创建副本,而不是写共享的页面。所以子进程的地址空间中的数 据是fork()时刻整个数据库的一个快照。
3)当子进程将快照写入临时文件完毕后,用临时文件替换原有的快照文件,然后子进程退出。
Redis的client也可以使用"save"或者"bgsave"命令通知redis做一次快照持久化。save操作是在主线程中保存快照的,由于redis是用一个主线程(即单进程)来处理所有client的请求,这种方式将会阻塞所有client请求,不推荐使用。
值得注意的是,每次RDB都是将内存数据完整写入到磁盘一次,并不是增量的只同步脏数据。如果数据量大的话,而且写操作比较多,必然会引起大量的磁盘IO操作,可能会严重影响性能。
2 RDB的优势
1)一旦采用该方式,那么你的整个Redis数据库将只包含一个文件,这样非常方便进行备份。比如你可以每1天备份一次数据,而不用担心磁盘空间不够。而且我们可以很容易的将一个RDB文件移动到其他的存储介质上。
2)RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。
3)RDB 可以最大化Redis的性能:父进程在保存RDB文件时唯一要做的就是调用fork()生成子进程,然后这个子进程就会处理接下来的所有保存工作,而父进程无需参与磁盘IO,大大提升性能。
3 RDB的劣势
1)如果服务器运行时发生故障,那么RDB方式将会丢失数据,虽然Redis允许你设置不同的保存点(save point)来控制保存RDB文件的频率, 但是, 因为RDB 文件需要保存整个数据集的状态,所以它并不是一个轻松的操作。 因此你可能会至少 5 分钟才保存一次RDB文件。 在这种情况下, 一旦发生故障停机, 你就可能会丢失好备份时间段中的数据。
2)每次保存 RDB 的时候,Redis都要fork()出一个子进程,并由子进程来进行实际的持久化工作。在数据集比较庞大时,fork() 可能会非常耗时,造成服务器在某毫秒内停止处理客户端; 如果数据集非常巨大,并且 CPU 时间非常紧张的话,那么这种停止时间甚至可能会长达整整一秒。 虽然AOF重写也需要进行fork() ,但无论AOF重写的执行间隔有多长,数据的耐久性都不会有任何损失。
4 RDB的相关配置参数
除了上面用于定义RDB快照条件的项之外,还有一些其他的配置参数,放置在配置文件"/etc/redis.conf"中的"SNAPSHOTTING"字段中;详细信息如下:
rdbcompression yes # 是否开启压缩功能
rdbchecksum yes # 检测数据校验和
dbfilename dump.rdb # 数据集文件名
dir /var/lib/redis # 数据存放目录
stop-writes-on-bgsave-error yes # 默认情况下,如果启用了RDB快照(至少有一个保存点),而最新的数据保存失败,将停止接受写入。这将使用户(以一种困难的方式)意识到数据并不是正确地在磁盘上进行持续的,否则很可能没有人会注意到,并且会发生一些错误,即使存在磁盘、权限等问题,仍然照常工作。如果后台保存进程再次开始工作,Redis将自动允许再次写入。 但是,如果您已经设置了对Redis服务器和持久性的正确监视,则应该禁用此功能,以便即使磁盘,权限等方面存在问题,Redis也将照常继续工作。
AOF持久化方式
AOF是"Append-Only File"的简称,是一种将内存中的数据以命令的方式追加保存至临时文件中,然后依赖次此文件进行数据重现的方式。
1 AOF文件保存过程
redis会将每一个收到的写命令都通过write函数追加到文件中(默认是appendonly.aof)。当redis重启时会通过重新执行文件中保存的写命令在内存中重建整个数据库的内容。当然由于os会在内核中缓存 write做的修改,所以可能不是立即写到磁盘上。这样AOF方式的持久化也还是有可能会丢失部分修改。不过我们可以通过选项告诉redis我们想要 通过fsync函数强制os写入到磁盘。有三种方式如下(默认是:每秒fsync一次)
appendonly yes # 启用aof持久化方式
appendfsync always # 每次收到写命令就立即强制写入磁盘,虽然保证完全的持久化,但是严重影响性能,不推荐使用。
appendfsync everysec # 每秒钟强制写入磁盘一次,在性能和持久化方面做了很好的折中,推荐。
appendfsync no # 完全依赖os,即Redis在不参与。这种性能最好,但持久化没保证。
AOF的方式也同时带来了另一个问题。持久化文件会变的越来越大。例如我们调用"incr count"命令100次,文件中必须保存全部的100条命令,其实前99条都是多余的。因为要恢复数据库的状态时只需要执行"set test 100"就够了。为了压缩aof的持久化文件。redis提供了"bgrewriteaof"命令。收到此命令redis将使用与快照类似的方式将内存中的数据 以命令的方式保存到临时文件中(类似于Mysql中的二进制日志),最后替换原来的文件。具体过程如下:
1)redis调用fork ,生成子进程
2)子进程根据内存中的数据库快照,向临时文件中写入重建数据库状态的命令
3)父进程继续处理client请求,除了把写命令写入到原来的aof文件中。同时把收到的写命令缓存起来。这样就能保证如果子进程重写失败的话并不会出问题。
4)当子进程把快照内容以命令的方式写到临时文件中后,子进程发信号通知父进程。然后父进程把缓存的写命令也追加写入到临时文件中。
5)现在父进程可以使用临时文件替换老的aof文件,并重命名,后面收到的写命令也开始往新的aof文件中追加。
值得注意的是:重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容以命令的方式重写至了一个新的aof文件,这点和快照有点类似。
2 AOF持久化优势
1)使用 AOF 持久化会让 Redis 变得非常耐久(much more durable):你可以设置不同的 fsync 策略,比如无 fsync ,每秒钟一次 fsync ,或者每次执行写入命令时 fsync 。 AOF 的默认策略为每秒钟 fsync 一次,在这种配置下,Redis 仍然可以保持良好的性能,并且就算发生故障停机,也最多只会丢失一秒钟的数据( fsync 会在后台线程执行,所以主线程可以继续努力地处理命令请求)。
2)AOF文件是一个只进行追加操作的日志文件(append only log), 因此对AOF文件的写入不需要进行seek , 即使日志因为某些原因而包含了未写入完整的命令(比如写入时磁盘已满,写入中途停机,等等), redis-check-aof 工具也可以轻易地修复这种问题。
Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写: 重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。 整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。
3)AOF 文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松。 导出(export) AOF 文件也非常简单: 举个例子, 如果你不小心执行了FLUSHALL命令, 但只要 AOF 文件未被重写,那么只要停止服务器,移除AOF文件末尾的FLUSHALL命令,并重启Redis ,就可以将数据集恢复到FLUSHALL执行之前的状态。
3 AOF持久化的劣势
1)对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。
2)根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。在一般情况下,每秒 fsync 的性能依然非常高,而关闭 fsync 可以让 AOF 的速度和 RDB 一样快, 即使在高负荷之下也是如此。不过在处理巨大的写入载入时,RDB 可以提供更有保证的最大延迟时间(latency)。
3)AOF在过去曾经发生过这样的 bug : 因为个别命令的原因,导致 AOF 文件在重新载入时,无法将数据集恢复成保存时的原样。(例如,阻塞命令 BRPOPLPUSH 就曾经引起过这样的bug) 测试套件里为这种情况添加了测试:它们会自动生成随机的、复杂的数据集, 并通过重新载入这些数据来确保一切正常。虽然这种 bug 在 AOF 文件中并不常见,但是对比来说,RDB几乎是不可能出现这种bug的。
4 AOF的相关配置参数
关于Redis的持久化方式之一"AOF",与其相关的配置项放置在Redis配置文件"/etc/redis.conf"中的"APPEND ONLY MODE"字段中;与其相关的参数如下:
# 开启AOF持久化模式
appendonly no
# The name of the append only file (default: "appendonly.aof")
appendfilename "appendonly.aof"
# AOF持久化方式,只要分为三种,有写操作就fsync(影响性能)、每秒fync(折中方式,只丢失1s数据)以及OS执行刷写(持久化无保证),
# appendfsync always
appendfsync everysec
# appendfsync no
# 是否在后台执行aof重写期间不调用fsync,默认为no,表示调用;当AOF的fsync策略设置为always或everysec时,并且后台保存进程(后台保存或AOF日志背景重写)对磁盘执行大量I/O时,Redis可能在fsync()调用上阻塞太久。目前并没有解决此问题的方法,因为即使在其他线程中执行fsync也会阻止我们的同步写入调用。为了减轻这个问题,可以使用下面的选项来防止在BGSAVE或BGREWRITEAOF进程中在主进程中调用fsync()。这意味着,当另一个子进程正在保存时,Redis的持久性与"appendfsync none"相同。这意味着在最坏的情况下(使用默认的设置),最多可能会丢失30秒的日志。 如果有延迟问题,请将其转为"yes"
no-appendfsync-on-rewrite no
# 定义文件重写频度,有两个参数,一是百分不,即系列第一条命令;二是按AOF日志大小,默认为64MB。如果日志当前的增涨量大于预定义的增长率(百分比)则触发重写。此外,您还需要指定要重写的AOF文件的最小大小,即使达到了百分比增加但仍然非常小,这对于避免重写AOF文件很有用。指定百分比为零,表示禁用自动AOF重写功能。
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# 在Redis启动过程中,当AOF数据被加载回内存时,可能会发现一个AOF文件被截断。如果将AOF-load-truncated设置为yes,则会加载一个截断的AOF文件,而Redis服务器开始发出日志,以通知该事件的用户。如果该选项被设置为no,服务器将以错误中止并拒绝启动。当选项设置为no时,用户需要使用"redis-checkaof"实用程序修复AOF文件,然后再重新启动服务器。
aof-load-truncated yes
总结:
一般来说, 如果想达到足以媲美 PostgreSQL 的数据安全性,你应该同时使用两种持久化功能。如果你非常关心你的数据,但仍然可以承受数分钟以内的数据丢失,那么你可以只使用RDB持久化。