redis的俩种持久化存储
rdb存储就是在不同的时间点,将内存中的数据生成
快照
并存储到硬盘介质上,通常数据存储是到一个dump.rdb的文件中。本次时间点生成的数据将会完全覆盖上次时间点在dump.rdb文件中的所有数据。可以理解为就是将redis中的数据放到了一个dump.rdb的文件中。在redis重启之后会执行rdb文件-
aof存储是将redis执行过程中的所有写指令以追加的方式写到一个文件,这个文件通常是appendonly.aof。这个写指令包括string类型的set,delete,incr,incrby,decrby,append等,list类型的rpush,rpop,lpush,lpop,lset等,set类型的sadd,srem等,有序set类型的zadd,zrem,zincrby等,哈希类型的hset,hdel,hmset,hincrby等。
在redis重启之后就会将appendonly.aof文件的内容由上到下执行一遍。具体文件内容如下,文件内容是一些常用的redis命令,可读性很强,我们读的时候可跳过$和*,图中圈住的命令就是 set xlu2 111,set yyy 11, select 0:
rdb存储和aof存储可以同时开启,在redis重启后会优先采用aof存储进行恢复,因为aof存储的数据更加完整。
如果你的项目没有数据持久化的需求,你可以关闭以上俩种功能,因为rdb和rof也是耗内存和读写Io的。
rdb存储的具体过程
- rdb会单独fork出一个子进程,此子进程来执行数据复制,fork操作过程父进程会阻塞,一般时间较短。一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间以及
内存数据
。相当于把原来的进程的所有资源都复制到了新的新进程中。相当于克隆了一个自己。也就是说此时内存使用量是存储之前的2倍
- 子进程会创建一个临时文件,随后将子进程内存中的数据复制到子进程的临时文件中,此时复制的所有的io操作都是在子进程中进行,主进程是没有任何io操作的。正是因为这一特性主进程可以继续接收客户端命令,不用阻塞,保证了redis的高性能。
- 子进程数据复制完成后,将子进程中的临时文件替换原来的持久化文件(dump.rdb)。
- 持久化完成,销毁子进程,资源释放,内存也随之释放。
需要注意的是,每次快照持久化都会将主进程的内存数据复制到子进程内存中,这样将导致内存开销加倍,若此时内存不足,则会阻塞服务器运行,直到复制结束释放内存;子进程内存数据将完整写入磁盘一次,所以如果内存数据量大的话,将会写io操作频繁,必然会引起大量的磁盘I/O操作,严重影响性能,并且最后一次持久化后的数据可能会丢失;
aof存储的具体过程
AOF 持久化功能的实现可以分为 3 个步骤:命令追加
、文件同步
、文件重写
- 命令追加:
所有
的写入命令会追加到AOF 缓冲区
的中。
大家可能会有疑问了,直接将写命令写到aof文件不就行了嘛,为什么还要写入到一个aof缓冲区呢?缓冲区的作用是什么呢?
redis使用单线程响应命令,如果每次写入aof文件都直接加到硬盘,每次io都会阻塞主进程,那么性能完全取决于当前的硬盘负载了。写入aof缓冲区还有另一个好处,redis可以提供多种缓冲区同步硬盘策略也就是文件同步的同步策略,在性能和安全性方面作出权衡。
- 文件同步:aof缓冲区根据
对应的策略
向硬盘做同步操作。
redis.conf文件中对应的策略一共有三个选择:
appendfsync always:每执行一个写命令保存一次
appendfsync everysec(默认,推荐):每一秒钟保存一次
appendfsync no:不保存、
上述3个步骤在三个appendfsync项的区别:
appendfsync always:每次执行完一个写命令之后,直接进行文件同步,而不需要写入到aof缓冲区内
appendfsync everysec:每秒从aof的缓冲区内执行一次文件同步
appendfsync no:不做讨论。
- 文件重写:随着aof文件越来越大,需要定期清理aof文件进行重写,达到压缩的目的,此过程只有在aof文件达到重写的标准时才会执行,并不是每次aof持久化时都执行。
既然 AOF 持久化是通过保存写命令到文件的,那随着时间的推移,这个 AOF 文件记录的内容就越来越多,文件体积也就越来越大,对其进行数据还原的时间也就越来越久。
针对这个问题,Redis 提供了 AOF 文件重写功能。
aof的重写机制
在redis.conf中有一段配置,表示aof文件达到64M时触发重写机制。
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
通过该功能来创建一个新的 AOF 文件来代替旧文件。并且两个文件所保存的数据库状态一样(就是redis的数据是一样的),但新文件不会包含任何冗余命令,所以新文件要比旧文件小得多。
而为什么新文件不会包含任何冗余命令呢?
那是因为这个重写功能是通过读取服务器当前的数据库状态来实现的(类似rdb的快照)。虽然叫做「重写」,但实际上并没有对旧文件进行任何读取修改。
比如旧文件保存了对某个 key 有 4 个 set 命令,经过重写之后,新文件只会记录最后一次对该 key 的 set 命令,可以这样理解。因此说新文件不会包含任何冗余命令。其实重写之后并不是key最后一条命令,而是类似rdb存储的二进制文件。
这个重写功能和rdb持久化存储很像,也是fork一个子进程,在子进程内创建一个临时文件,然后读取当前数据库状态后以命令行的形式写入到新的aof文件中,没有冗余命令。
写入新aof文件的所有io操作都是在子进程内操作,不会阻塞主进程。注意rdb写入临时文件的内容和aof写入临时文件的内容都是一些二进制文件。
当新的aof文件全部写入成功后,替换旧的aof文件,销毁子进程。重新完成
所以这个重写过程非常类似rdb的存储过程。
那么这里又会存在一个问题,子进程在重写过程中,服务器还在继续处理命令请求,新命令可能会对数据库进行修改,这会导致当前数据库状态和重写后的 AOF 文件,所保存的数据库状态不一致。
为了解决这个问题,Redis 设置了一个 AOF 重写缓冲区
。在子进程执行 AOF 重写期间,主进程需要执行以下三个步骤:
执行客户端的请求命令
将执行后的写命令追加到 AOF 缓冲区
将执行后的写命令追加到 AOF 重写缓冲区
当子进程结束重写后,会向主进程发送一个信号,主进程接收到之后会调用信号处理函数执行以下步骤:
将 AOF 重写缓冲区内容写入新的 AOF 文件中。此时新文件所保存的数据库状态就和当前数据库状态一致了。
对新文件进行改名,原子地覆盖现有 AOF 文件,完成新旧文件的替换。
当函数执行完成后,主进程就继续处理客户端命令。
因此,在整个 AOF 重写过程中,只有在执行信号处理函数时才会阻塞主进程,其他时候都不会阻塞。
有时aof文件的大小还没有触发重写机制,但是文件已经很大了,我们想手动触发重写,可以使用下面的命令
bgrewriteaof
flushall命令后的恢复
-
持久化方式为aof
如果你使用的持久化方式为aof。当你输入flushdb或者flushall命令后,所有缓存将会清空。此时如果你想恢复之前的内容怎么办?很简单。因为flushdb或者flushall命令也是会写入aof文件,所以只要将aof文件中的flushdb或flushall命令删除即可。
并不是aof存储的话,输入命令flushdb或flushall后,都可以恢复。那什么情况下就恢复不了呢?
当输入flushdb或者flushall后,该命令写入aof文件,此时文件正好触发了aof重写机制,此时redis数据也全部清空,所以重写之后,aof文件是一个空文件,此时数据全部丢失,不能在恢复。
总结:当输入flushdb或flushall后,我们应尽快停止redis,删除aof文件中的flushdb或者flushall命令
输入flushall命令,在aof文件中如下,我们应删除圈住的内容。
删除后,应该留一个空行,空行太多的话,启动redis时报错,如下,空俩行。
总结:aof文件总是空一行,如果空太多行,启动错误。
持久化方式为 rdb
如果使用rdb持久化存储了,存储频率如下,根据配置文件而定
save 900 1
save 300 10
save 60 10000
取个中间值,300s存储一次。也就是说我们在输入flushdb或者flushall后,此时redis内数据已经被清空,但是rdb文件还没有被清空,在300s之后就会清空,所以如果想要恢复数据的话,请立即关闭redis,重启redis即可。重启后将会加载之前的rdb文件,数据会被重新加载。
rdb和aof的优缺点
rdb优点
- 数据恢复速度快,因为存储文件小。比如恢复redis10M的数据,rdb文件可能就10多M,而aof可能就的30M,此时如果io读写能力相同的情况下,肯定是文件小的恢复速度快了。
- 也正因为存储文件小,所以适合恢复大规模数据
rdb缺点
- 数据完整性不高,数据容易丢失。即使你5分钟存储一次,如果redis宕机,那么在前5分钟内的数据就会丢失
- 如果redis数据量非常大,那么进行一次rdb(快照)复制,将会非常消耗系统性能,例如cpu,内存,io;如果更新数据频繁,将会频繁触发rdb复制,性能将会消耗非常大。
redis.conf相关介绍
rdb存储相关配置
################################ SNAPSHOTTING #################################
# 快照配置
# 注释掉“save”这一行配置项就可以让保存数据库功能失效
# 设置sedis进行数据库镜像的频率。
# 900秒(15分钟)内至少1个key值改变(则进行数据库保存--持久化)
# 300秒(5分钟)内至少10个key值改变(则进行数据库保存--持久化)
# 60秒(1分钟)内至少10000个key值改变(则进行数据库保存--持久化)
save 900 1
save 300 10
save 60 10000
# 快照功能开启,后台持久化操作失败,Redis则会停止接受更新操作,否则没人注意到数据不会写磁盘持久化。
# 如果后台持久化操作进程再次工作,Redis会自动允许更新操作(yes);后台持久化操作出错,redis也仍然
# 可以继续像平常一样工作(no)
stop-writes-on-bgsave-error yes
# 在进行镜像备份时,是否进行压缩。yes:压缩,但是需要一些cpu的消耗。no:不压缩,需要更多的磁盘空间
rdbcompression yes
#从版本RDB版本5开始,一个CRC64的校验就被放在了文件末尾。
#这会让格式更加耐攻击,但是当存储或者加载rbd文件的时候会有一个10%左右的性能下降,
#所以,为了达到性能的最大化,你可以关掉这个配置项。
#没有校验的RDB文件会有一个0校验位,来告诉加载代码跳过校验检查。
rdbchecksum yes
# 数据库文件的文件名
dbfilename dump.rdb
# 数据目录
dir /var/lib/redis
我们重点关注下save配置段,这段配置是说明rdb存储的频率。
save 900 1
900秒(15分钟)内至少1个key值改变,则执行一次rdb持久化存储(fork一个子进程,临时文件替换原来的dump.rdb)
save 300 10
300秒(5分钟)内至少10个key值改变,则执行一次rdb持久化存储
save 60 10000
60秒(1分钟)内至少10000个key值改变,则执行一次rdb持久化存储
简单的说,如果redis一个key没有更新的话就不会执行rdb持久化存储。只要满足上面3条条件中的一条,则会触发一次rdb的持久化存储。上面条件的时间都是相对时间。rdb的持久化存储备份的时间是不固定的
aof相关配置
############################## APPEND ONLY MODE ###############################
# 是否开启aof持久化存储,yes为开启
appendonly no
# 定义AOF文件的名称
# appendfilename appendonly.aof
# Redis支持三种不同的刷写模式:
# no:不要立刻刷,只有在操作系统需要刷的时候再刷。比较快。IO消耗最小,最不安全
# always:每次写操作都立刻写入到aof文件。慢,但是最安全。
# everysec:每秒写一次。折衷方案。
# 默认的 "everysec" 通常来说能在速度和数据安全性之间取得比较好的平衡。
# appendfsync always
appendfsync everysec
# appendfsync no
# 如果你有延迟的问题那就把这个设为 "yes",否则就保持 "no",这是保存持久数据的最安全的方式。
no-appendfsync-on-rewrite no
# 自动重写AOF文件,指定百分比为0会禁用AOF自动重写特性。
# 如果AOF日志文件大到指定百分比,Redis能够通过 BGREWRITEAOF 自动重写AOF日志文件。如果当前大小超过指定比例,就会触发重写操作。
# 你还需要指定被重写日志的最小尺寸,这样避免了达到约定百分比但尺寸仍然很小的情况还要重写。
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
主要介绍一段配置。aof持久化存储时,aof文件通常会很大,当aof文件达到一定大小时就会触发重写机制(rewrite)。以下配置说明当文件大小达到64M的100%也就是达到64M时就会触发重写机制。
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
rdb存储和aof存储的使用场景
通常,如果你要想提供很高的数据保障性,那么建议你同时使用两种持久化方式。如果你可以接受灾难带来的几分钟的数据丢失,那么你可以仅使用 RDB。
目前,通常的设计思路是利用复制(Replication)机制来弥补 aof、rdb性能上的不足,达到了数据可持久化。即 Master 上 rdb 和 aof 都不做,来保证 Master 的读写性能,而 Slave 上则同时开启 rdb 和 AOF 来进行持久化,保证数据的安全性。
fork操作
在redis的rdb持久化和aof重写时都会fork一个子进程,对于大多数操作系统来说,fork操作属于重量级操作。
子进程fork时,占用内存大小等于父进程,理论上需要消耗2倍的内存的来完成持久化操作,但是linux有写时复制机制
,并不需要拷贝父进程的内存空间,父子进程会共享相同的物理内存页,当父进程处理写请求时会吧要修改的页创建副本,而子进程在fork操作过程中共享父进程的内存快照。
子进程在aof重写和rdb持久化时,主要设计cpu,内存,磁盘的消耗
- cpu:子进程父子负责把内存中的数据写到文件中,这个写io过程属于cpu密集操作,通常子进程对单核的cpu利用率接近90%。
redis属于cpu密集服务
,不要和其他cpu密集服务绑定在一起,会造成cpu的竞争。 - 内存:会fork一个子进程,子进程将会复制父进程的物理内存页
- 硬盘:子进程主要职责是把aof重写和rdb的文件持久化到磁盘,势必造成磁盘写入压力。
思考
- 为什么aof存储的文件通常会比较大?而rdb恢复速度会更快?
首先说rdb文件小的原因:RDB 文件中每一条数据只有一条记录,不会像 AOF 日志那样可能有一条数据的多次操作记录。因此rdb文件更小。所以每条数据只需要写一次就行了。
其次说为什么rdb恢复速度快:1.正是因为rdb文件小 所以速度快 2.RDB 文件的存储格式和 Redis 数据在内存中的编码格式是一致的,不需要再进行数据编码工作,所以在 CPU 消耗上要远小于 AOF 日志的加载。而aof文件存储的是一条条命令,redis数据恢复时还需要解析。
- 如果rdb和aof俩种方式同时开启,redis重启后会优先执行aof存储,那么执行完aof存储后还会执行rdb存储吗?
预测:只执行aof存储文件,不执行rdb存储文件了。