一、为什么要持久化
众所周知,redis是内存数据库,即所有数据都存储在内存,当服务器关闭时所有数据都将丢失。而持久化则是为了将redis中的数据以某种格式异步保存到硬盘中,在redis重启时仍然可以获取到以前的数据。另外持久化也被称为最简单的高可用方式,可以实现数据备份。
Redis提供了两种持久化方式:RDB(快照)和AOF(日志)
二、RDB持久化
1、含义
RDB持久化指redis在运行期间按指定好的时间间隔生成当前redis数据的时间点快照,即保存当前时间点的数据集,并将该数据集保存在一个默认名为dump.rdb(可通过配置修改)的的二进制文件中,在下次redis启动时将该文件的数据载入到redis数据库(内存),从而实现数据的持久化(保存)。
2、RDB持久化触发方式
(1)手动触发rdb持久化的方式有两种,一种是在客户端执行 save(同步) 命令,一种是执行 bgsave(异步) 命令。
save:客户端输入save命令,服务端接收到命令后开始创建rdb文件。执行save命令会阻塞redis服务器主进程,即save命令执行过程中直到rdb文件彻底生成前服务器无法客户端的任何请求,这是一个很粗暴的方式所以不会使用,时间复杂度O(n)。
bgsave:执行bgsave命令时redis会调用fork()函数创建一个子进程,由这个子进程来生成rdb文件,而主进程则继续处理客户端的请求,当rdb文件创建成功后子进程会返回结果给主进程,时间复杂度O(n)。在该过程中,只有fork子进程时会阻塞服务器(一般很快),同时fork的时候会消耗内存,这是其缺点。
(2)自动触发
在 redis.conf 配置文件中有如下默认配置
save 900 1
save 300 10
save 60 10000
save 900 1:表示在900秒内redis中的数据有1次以上修改则执行一次bgsave命令将当前数据保存为rdb文件。其他两条save配置也是这个意思,只要这三个配置满足其中一个,则redis就执行bgsave命令(即save m n)。
save m n 原理:
这个配置其实是通过serverCron函数、dirty计数器、和lastsave时间戳来实现的。
redis服务器的周期性操作函数(serverCron)默认会每隔100ms执行一次,该函数主要用于包括检查 save m n 配置的条件是否满足等对服务器的状态进行维护的工作。dirty计数器主要是为了记录redis上一次执行bgsave/save后服务器数据做了多少次修改,每次执行完bgsave/save都将置0。注意dirty记录的是服务器进行了多少次修改,即lpush list a b 一条命令算执行两次修改。lastsave时间戳主要用于记录上一次成功执行save/bgsave的时间。
所以 save m n 命令执行需要满足两个条件: 当前时间-lastsave > m 、 dirty >= n
(3)其他场景下触发(不容忽略的触发机制)
- 在主从复制场景下,如果从节点执行全量复制,则主节点会执行bgsave命令,并将rdb文件发送给从节点;
- 执行shutdown命令时,自动执行rdb持久化(bgsave)。
- debug reload时
3、RDB持久化优点:
- 生成的rdb文件较紧凑,即体积较小,网络传输快,适合全量复制。
- 数据量大时较aof持久化方式的恢复速度快。
- 可以通过配置控制rdb文件生成的时间。
- 与AOF相比,RDB最重要的优点之一是对性能的影响相对较小。
- 在自动触发时,redis都会执行fork()函数生成子进程来生成rdb文件,而不占用主线程。
4、RDB持久化缺点:
- 数据量很大时耗性能,上面的fork()子进程比较耗时(消耗内存),导致服务器出现阻塞现象。
- 跟aof比,不是非常耐久。
- rdb持久化过程较繁琐,一般在较长一段时间才保存一次rdb文件。也就是说,在这个时间段内如果服务器宕机,则这个时间段的数据将丢失。
5、执行过程
在进行rdb持久化时,服务器执行以下操作:
- redis调用 fork()函数创建了子进程。
- 子进程创建后开始将redis中原有的数据集写入到一个临时 RDB 文件中。
- 当子进程完成对新 RDB 文件的写入时,会用新 rdb 文件替换旧的 rdb 文件并将其删除。
6、配置
redis.conf 配置文件中关于rdp持久化配置说明如下:
- save m n:bgsave自动触发的条件,当删除所有save m n配置则不会自动执行bgsave(最佳配置会删去)。
- dir ./data/rdb:rdb文件生成的目录位置
- dbfilename dump-6379.rdb:rdp文件名称
- rdbcompression yes:是否开启rdb文件压缩
- rdbchecksum yes:是否开启rdb文件的校验,即在写、读(启动载入)文件时检查数据是否损坏;关闭会带来性能优化但无法发现文件是否损坏。
- stop-writes-on-bgsave-error yes:在执行bgsave出错时,redis是否停止写命令;设置为yes,当硬盘出现问题时可以及时发现,避免数据的大量丢失;设置为no,则不管bgsave的错误继续执行写命令。
三、AOF持久化
1、含义
AOF持久化指redis在运行期间,根据配置所选策略,将所有执行的命令写到一个AOF文件中。当redis服务器宕机,下次启动是可载入该AOF文件执行之前的命令从而恢复数据,redis默认该持久化方式是关闭的。
2、三种策略
AOF开启的时候,redis在执行写操作的时候同时会把写命令刷新到缓冲区(aof_buf),再由不同的策略决定什么时候将缓冲区的数据写到硬盘的AOF文件中。AOF提供了三种持久化策略:always、everysec、no
always:每条写命令执行的时候都实时fsync到硬盘AOF文件
everysec:每秒把缓冲区fsync到硬盘AOF文件
no:由操作系统决定什么时候fsync到硬盘AOF文件,redis不会主动调用fsync去将AOF日志内容同步到磁盘,完全依赖于操作系统的调试,对大多数Linux操作系统,是每30秒进行一次fsync。
| always | everysec | no |
优点 | 不丢失数据 | 每秒一次fsync,只丢1秒数据 | 不用管 |
缺点 | IO开销较大,不断进行IO操作 | 会丢1秒数据(实际可能2秒) | 不可控 |
3、AOF重写
(1)定义:AOF持久化时,会将所有写命令写到AOF文件中,当操作比较频繁时文件会一下子变很大。同时用户如果不断执行多条自增或者对同一个key做多次set操作,将会导致AOF存在很多无意义的写命令,这对文件大小以及数据恢复都很不友好。因此AOF持久化时会有一个“AOF重写“的操作,当文件达到某个条件的时候会执行重写(bgrewriteaof),将文件中多条命令精简为最少的命令从而减少硬盘占用量、加快恢复速度。
(2)两种触发方式:手动执行bgrewriteaof命令、根据aof重写配置自动触发
bgrewriteaof:客户端发起bgrewriteaof命令,服务端收到命令后会fork一个子进程,再由子进程对AOF文件进行重写
配置方式:redis.conf配置文件中有 auto-aof-rewrite-min-size(aof文件重写需要达到的大小)、auto-aof-rewrite-percentage(文件增长率,即目前的AOF文件的大小超过上一次重写时的AOF文件的百分之多少时再次进行重写,如果之前没有重写过,则以启动时AOF文件大小为依据) 这两个配置,重写自动触发机制满足如下两个条件:aof_current_aof >auto-aof-rewrite-min-size ; (aof_current_aof -aof_base_size )/aof_base_size >auto-aof-rewrite-percentage。其中 aof_current_aof 表示AOF当前文件大小(字节) 、 aof_base_size 表示上次启动和重写后AOF文件的大小(字节)。
(3)重写执行步骤:
- redis主进程调用fork()函数,创建子进程。
- 子进程开始根据旧的AOF文件将新 AOF 文件的内容写入到临时文件。
- 对于所有新执行的写入命令,父进程一边将它们累积到一个内存缓存中,一边将这些改动追加到现有 AOF 文件的末尾。这样就算重写途中宕机,旧的AOF文件还是完整的。
- 当子进程完成重写工作时会给主进程发一个返回,主进程则开始将内存缓存的所有数据追加到新AOF文件末尾,之后就只对新AOF文件尽心追加操作。
(4)重写能够压缩AOF文件原因:
- 过期的数据不再写入文件
- 无效的命令不再写入文件:如有些数据被重复设值(set mykey v1, set mykey v2)、有些数据被删除了(sadd myset v1, del myset)等等
- 多条命令可以合并为一个
4、AOF的执行流程如下:
- 命令追加(append):将redis的写命令追加到缓冲区aof_buf,不是直接写入文件,主要是为了避免每次有写命令都直接写入硬盘,导致硬盘IO成为redis负载的瓶颈。
- 文件写入(write)和文件同步(sync):根据不同的同步策略将aof_buf中的内容同步到硬盘;
- 文件重写(rewrite):定期重写AOF文件,达到压缩的目的。
5、AOF持久化优点
- 可选不同的 fsync 策略,默认策略为每秒钟 fsync 一次,如果服务器挂了最多只丢失一秒钟的数据。
- AOF 文件是一个只进行追加操作的日志文件,操作较简单因此压力较小。
- 可用 redis-check-aof 工具修复有问题的aof文件。
- 可重写压缩文件。
- 相对于RDB持久化,其数据集更完整。
- 完全耐久。
6、AOF持久化缺点
- 相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积
- 根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB
- 恢复速度慢、对性能影响大。
7、配置
redis.conf 配置文件中关于aof持久化配置说明如下:
- appendonly yes :是否开启AOF,设置为yes开启aof持久化(默认关闭)
- appendfilename "appendonly-6379.aof":AOF文件名
- appendfsync everysec:fsync持久化策略
- dir ./ :AOF文件所在目录
- no-appendfsync-on-rewrite yes:AOF重写期间是否禁止fsync;如果开启该选项,可以减轻文件重写时CPU和硬盘的负载,但是可能会丢失AOF重写期间的数据;需要在负载和安全性之间进行平衡
- auto-aof-rewrite-percentage 100:文件重写需要满足的增长率
- auto-aof-rewrite-min-size 64mb:文件重写需要满足的大小
- aof-load-truncated yes:如果AOF文件结尾损坏,redis启动时是否仍载入AOF文件
四、两种持久化方式对比及总结
当两种持久化一起使用,在redis启动时会先使用aof还原数据。当AOF开启,但AOF文件不存在时,即使RDB文件存在也不会加载。选择哪一种持久化需要结合实际场景,如果能接受几分钟数据丢失就使用rdb。一般而言rdb最佳策略:单机时关、主从时,主关从开。而aof最佳策略:开启、选择everysec、同时集中管理AOF重写。
这两种持久化的对比如下表:
| RDB | AOF |
启动优先级 | 低 | 高 |
体积 | 小 | 大 |
恢复速度 | 快 | 慢 |
数据安全性 | 丢数据 | 根据策略决定 |
轻重 | 重 | 轻 |
五、其他
(1)当服务器运行时需要修改配置,如开启aof,可执行如下操作:
config get appendonly //得到关闭
config set appendonly yes
config rewrite
(2)fork
在两种持久化时都会涉及到fork操作,并且只有在fork时会阻塞主进程,所以这里也了解一下。
fork操作是同步操作 ,且其操作与内存量有很大关系,内存越大耗时越长。
改善(网上看到的):
- 优先使用物理机或高效支持fork操作的虚拟化技术
- 控制reids实例最大可用内存
- 合理配置linux内存分配策略
- 降低fork频率:例如放宽AOF重写自动触发时机,不必要的全量复制
(3)对于主从环境下的持久化设置
- master节点:完全关闭持久化(包括RDB和AOF),这样可以让master的性能达到最好
- slave节点:关闭RDB,开启AOF(如果对数据安全要求不高,开启RDB关闭AOF也可以),并定时对持久化文件进行备份(如备份到其他文件夹,并标记好备份的时间);然后关闭AOF的自动重写,然后添加定时任务,在每天Redis闲时(如凌晨12点)调用bgrewriteaof。
(4)aof持久化:当持久化策略选择everysec时,主进程在执行完写操作后调用系统write操作将命令写到aof_buf则返回,而命令从aof_buf到硬盘中的aof文件由专门的文件同步线程每秒调用一次,跟主进程无关。这样就会导致当硬盘负载过高,那么fsync操作可能会超过1s,此时redis主线程持续高速向aof_buf写入命令,但是数据一直卡着没fsync到硬盘,此时如果redis宕机则丢失的数据将远不止1秒。
redis的处理策略:主线程每次将数据扔给aof_buf返回后,会对比上次fsync成功的时间;如果距上次不到2s,主线程直接返回;如果超过2s,则主线程阻塞直到fsync同步完成(不再进行写操作)。因此,如果系统硬盘负载过大导致fsync速度太慢,会导致redis主线程的阻塞,此时使用everysec配置最多可能丢失2s的数据而非1s。