Redis持久化

1.概述

redis支持将内存中的数据周期性的写入磁盘或者把操作追加到记录文件中,这个过程称为redis的持久化。redis支持两种方式的持久化,一种是快照方式(snapshotting),也称RDB方式;两一种是追加文件方式(append-only file),也称AOF方式。RDB方式是redis默认的持久化方式。持久化可以避免因进程退出而造成数据丢失。

Redis持久化文件加载流程

 

2.RDB持久化

(1)RDB文件的创建

RDB持久化是把当前进程数据以二进制的方式生成快照(.rdb)文件保存到硬盘的过程,有手动触发和自动触发,手动触发有两个命令来生成RDB文件,一个是SAVE,另一个是BGSAVE。

a.手动触发

SAVE命令:阻塞Redis服务器进程,在阻塞期间,服务器不能处理任何命令请求,直到RDB文件创建完毕为止,。

BGSAVE命令:Redis进程执行fork操作创建子线程,由子线程负责创建RDB文件完成持久化,服务器进程(父进程)继续处理命令请求。当BGSAVE命令在执行期间,客户端再发送BGSAVE命令会被服务器拒绝,因为同时执行两个GBSAVE命令也会产生竞争条件。最后BGREWRITEAOF和GBSAVE两个命令也不能同时执行。如果没有开启AOF持久化,自动执行BGSAVE;显然BGSAVE是对SAVE的优化。下图是SAVE命令和BGSAVE命令的对比:

命令 SAVE BGSAVE
IO类型 同步 异步
是否阻塞 阻塞 非阻塞(在Fork是阻塞)
复杂度 O(n) O(n)
优点 不会消耗额外内存 不阻塞客户端命令
缺点 阻塞客户端命令 需要Fork子进程,内存开销大

 

BGSAVE运行流程

 

b.自动触发

 

在redis.config配置文件里可以配置自动触发,配置方式如下:

 

            save <seconds> <changes>             

这个配置的规则指的是在seconds秒内发生changes次写操作,就会自动进行一次bgsave,例如:

 

            save 900 1                               

指的是如果900秒内有1条Key信息发生变化就会触发一次bgsave。

 

还有在执行shutdown命令的时候,如果没有开启AOF持久化功能,那么会自动执行一次bgsave。

 

(2)RDB文件载入

和创建文件不同,RDB文件的载入是在服务器启动时自动执行的,并没有用于载入RDB文件的命令,只要Redis服务器在启动时检测到RDB文件的存在,它就会自动载入RDB文件。能过启动时日志记录可以查看。需要注意的是,如果打开了AOF持久化,那么服务器会优先使用AOF文件来还原数据库状态。

(3)数据集保存

a.自动间隔性保存

 

文件的创建除了SAVE和GBSAVE保存RDB 文件,还可以通过配置SAVE选项,让服务器每隔一段时间自动执行一次BGSAVE命令。可以配置SAVE选项设置多个保存条件,只要任意一个条件被满足,服务器就会执行BGSAVE命令。

 

    --默认配置的SAVE选项,保存方式有三种条件,满足任意一种就可以,如下:
    127.0.0.1:6379> config get save
    1) "save"
    2) "900 1 300 10 60 10000"

 

    -- 服务器在900秒之内,对数据库进行了至少1次修改,则发起保存快照。

 

    -- 服务器在300秒之内,对数据库进行了至少10次修改,则发起保存快照。

 

    -- 服务器在60秒之内,对数据库进行了至少10000次修改,则发起保存快照。

 

b.检查保存条件是否满足

Redis的服务器周期性操作默认每隔100毫秒就会检查执行一次,用于对正在运行的服务器进行维护,其中一项工作是检查save 选项所设置的保存条件是否已经满足,如果满足就调用BGSAVE命令。

(4)Redis实现快照的过程

-  Redis使用fork函数复制一份当前进程(父进程)的副本(子进程);
-  父进程继续接收并处理客户端发来的命令,而子进程开始将内存中的数据写入硬盘中的临时文件;
-  当子进程写入完所有数据后会用该临时文件替换旧的RDB文件,至此一次快照操作完成。
-  在执行fork的时候操作系统(类Unix操作系统)会使用写时复制(copy-on-write)策略,即fork函数发生的一刻父子进程共享同一内存数据,当父进程要更改其中某片数据时(如执行一个写命令 ),操作系统会将该片数据复制一份以保证子进程的数据不受影响,所以新的RDB文件存储的是执行fork一刻的内存数据。

(5)RDB持久化的优缺点

优点:

  1.只有一个文件dump.rdb,方便持久化。

  2.容灾性好,一个文件可以保存到安全的磁盘。

  3.性能最大化,fork子进程来完成写操作,让主进程继续处理命令,所以是IO最大化。

  4.相对于数据集大时,比AOF的启动效率更高。

缺点:

  1.数据安全性低,通过配置save参数来达到定时的写快照,比如 每900 秒有1个键被修改就进行一次快照,每600秒至少有10个键被修改进行快照,每30秒有至少10000个键被修改进行记录。所以如果当服务器还在等待写快照时出现了宕机,那么将会丢失数据。

  2.fork子进程时可能导致服务器停机1秒,数据集太大。

3.AOF持久化

AOF(append only file),以日志的方式记录每次写命令,服务重启的时候重新执行AOF文件中的命令来恢复内存数据。因为解决了数据持久化实时性的问题,所以目前AOF是Redis持久化的主流方式。

(1)AOF持久化流程

 

  • 所有的写命令都会追加到aof_buf(缓冲区)中。
  • 可以使用不同的策略将AOF缓冲区中的命令写到AOF文件中。
  • 随着AOF文件的越来越大,会对AOF文件进行重写。
  • 当服务器重启的时候,会加载AOF文件并执行AOF文件中的命令用于恢复数据。

(2)AOF持久化的实现

a.命令追加

 

当AOF执行处于打开状态时,服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器状态的AOF_buf缓冲区的末尾。

 

复制代码
    struct redisServer {

        sds aof_buf; /* AOF buffer, written before entering the event loop */
        }
复制代码

 

b.AOF文件写入与同步

edis的服务器进程就是一个事件循环(loop),这个循环中的文件事件负责接收客户端的命令请求,以及向客户端发送命令回复。在服务器每次结束一个事件循环之前,都会调用内部flushAppendOnlyFile函数,考虑是否需要将aof_buf缓冲区中的内容写入和保存到AOF文件里面。flushAppendOnlyFile函数的行为由服务器配置appendfsync选项的值来决定。appendfsync是指数据同步到磁盘文件(AOF)的方式,默认配置是everysec选项。当同步频率是everysec值时,并且距离上次同步AOF文件已经超过一秒时,那么服务器会先将aof_buf中的内容写入到AOF文件中。

    127.0.0.1:6379>config get Appendfsync
    1) "appendfsync"
    2) "everysec"

 

RDB方式是周期性的持久化数据, 如果未到持久化时间点,Redis 因为某些原因而造成故障停机, 那么服务器将丢失最近写入、且仍未保存到快照中的那些数据。所以从redis 1.1开始引入了AOF方式,AOF 持久化记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集。 AOF 文件中的命令全部以 Redis 协议的格式来保存,新命令会被追加到文件的末尾。

 

AOF方式仍然有丢失数据的可能,因为收到写命令后可能并不会马上将写命令写入磁盘,因此我们可以修改redis.conf,配置redis调用write函数写入命令到文件中的时机。如下

#启用AOF方式

appendonly yes

#每次有新命令追加到 AOF 文件时就执行一次 fsync :非常慢,也非常安全

appendfsync always

#每秒 fsync 一次:足够快(和使用 RDB 持久化差不多),并且在故障时只会丢失 1 秒钟的数据

appendfsync everysec

#从不 fsync :将数据交给操作系统来处理。更快,也更不安全的选择

appendfsync no

 

Appendfsync模式

对应flushAppendOnlyFile函数行为

no

当设置appendfsync为no的时候,Redis不会主动调用fsync去将AOF日志内容同步到磁盘,所以这一切就完全依赖于操作系统的调试了。对大多数Linux操作系统,是每30秒进行一次fsync,将缓冲区中的数据写到磁盘上。从效率上讲该模式最快,

但同步到磁盘不及时,是最不安全的选择。

Everysec (推荐)

当设置appendfsync为everysec的时候,Redis会默认每隔一秒进行一次fsync调用,将缓冲区中的数据写到磁盘,从效率上讲该模式足够快(和使用 RDB 持久化差不多),并且当出现故障停机时,数据库也只丢失一秒钟的命令。

always

当设置appendfsync为always时,每一次写操作都会调用一次fsync,这时数据是最安全的,当然,由于每次都会执行fsync,所以其性能也会受到影响,效率上讲该模式最慢的。

 

 

 

(3)AOF文件载入与数据还原

服务器读入并重新执行一遍aof文件里面保存的写命令,就可以还原服务器关闭之前的数据库状态。

服务器读取aof文件并还原数据库状态的流程:

a.创建一个伪客户端(因为Redis的命令只能在客户端上下文中执行),用于载入AOF文件时所使用的命令直接来源于AOF文件,而不是来自网络连接的命令。

b. 从AOF文件中分析并读出一条写命令。

c. 使用伪客户端执行被读出的写命令。

d. 重复执行步骤2和3,直到AOF文件中的所有命令都被处理完为止。

比如:服务器首先读入并执行select 0 命令,之后是set msg hello命令,再之后是sadd fruits apple banana cherry命令等等,这些命令都执行完之后,服务器的数据库就被还原到之前的状态了。

(4)AOF文件重写

AOF持久化是通过保存被执行的写命令来记录数据库状态的,随着时间流逝,Redis服务器执行的写命令越来越多,AOF文件也会越来越大;过大的AOF文件不仅会影响服务器的正常运行,也会导致数据恢复需要的时间过长。为了解决AOF文件体积膨胀的问题,Redis提供了AOF文件重写功能。通过该功能,Redis服务器可以创建一个新的AOF文件来替代现有的AOF文件,新旧两个AOF文件所保存的数据库状态相同,但新的AOF文件不会包含任何浪费空间的冗余命令,所以新AOF文件体积比旧的AOF文件体积要小得多。

文件重写是指定期重写AOF文件,减小AOF文件的体积。需要注意的是,AOF重写是把Redis进程内的数据转化为写命令,同步到新的AOF文件;不会对旧的AOF文件进行任何读取、写入操作!这个重写功能是通过读取服务器当前的数据库状态来实现的。比如:使用写命令 rpush list "A", rpush list "B", rpush list "C", rpush list "D" 此时必须在AOF文件中写入四条命令。重写可以是直接从数据库中读取键list的值,然后用一条rpush list "A","B","C","D"命令来代替。

文件重写之所以能够压缩AOF文件,原因在于: 
a.过期的数据不再写入文件 
b.无效的命令不再写入文件:如有些数据被重复设值(set mykey v1, set mykey v2)、有些数据被删除了(sadd myset v1, del myset)等等 
c.多条命令可以合并为一个:如sadd myset v1, sadd myset v2, sadd myset v3可以合并为sadd myset v1 v2 v3。不过为了防止单条命令过大造成客户端缓冲区溢出,对于list、set、hash、zset类型的key,并不一定只使用一条命令;而是以某个常量为界将命令拆分为多条。这个常量在redis.h/REDIS_AOF_REWRITE_ITEMS_PER_CMD中定义,不可更改,3.0版本中值是64。

(5)文件重写的触发

 

文件重写的触发,分为手动触发和自动触发: 
手动触发:直接调用bgrewriteaof命令,该命令的执行与bgsave有些类似:都是fork子进程进行具体的工作,且都只有在fork时阻塞。

 

自动触发:根据auto-aof-rewrite-min-size和auto-aof-rewrite-percentage参数,以及aof_current_size和aof_base_size状态确定触发时机。 
auto-aof-rewrite-min-size:执行AOF重写时,文件的最小体积,默认值为64MB。 
auto-aof-rewrite-percentage:执行AOF重写时,当前AOF大小(即aof_current_size)和上一次重写时AOF大小(aof_base_size)的比值。 
其中,参数可以通过config get命令查看: 

状态可以通过info persistence查看: 

只有当auto-aof-rewrite-min-size和auto-aof-rewrite-percentage两个参数同时满足时,才会自动触发AOF重写,即bgrewriteaof操作。

对照上图,文件重写的流程如下:

1) Redis父进程首先判断当前是否存在正在执行 bgsave/bgrewriteaof的子进程,如果存在则bgrewriteaof命令直接返回,如果存在bgsave命令则等bgsave执行完成后再执行。前面曾介绍过,这个主要是基于性能方面的考虑。 
2) 父进程执行fork操作创建子进程,这个过程中父进程是阻塞的。 
3.1) 父进程fork后,bgrewriteaof命令返回”Background append only file rewrite started”信息并不再阻塞父进程,并可以响应其他命令。Redis的所有写命令依然写入AOF缓冲区,并根据appendfsync策略同步到硬盘,保证原有AOF机制的正确。 
3.2) 由于fork操作使用写时复制技术,子进程只能共享fork操作时的内存数据。由于父进程依然在响应命令,因此Redis使用AOF重写缓冲区(图中的aof_rewrite_buf)保存这部分数据,防止新AOF文件生成期间丢失这部分数据。也就是说,bgrewriteaof执行期间,Redis的写命令同时追加到aof_buf和aof_rewirte_buf两个缓冲区。 
4) 子进程根据内存快照,按照命令合并规则写入到新的AOF文件。 
5.1) 子进程写完新的AOF文件后,向父进程发信号,父进程更新统计信息,具体可以通过info persistence查看。 
5.2) 父进程把AOF重写缓冲区的数据写入到新的AOF文件,这样就保证了新AOF文件所保存的数据库状态和服务器当前状态一致。 
5.3) 使用新的AOF文件替换老文件,完成AOF重写。

 

(6)AOF后台重写

 

重写作为一种辅助维护,Redis不希望AOF重写造成服务器无法处理请求,所以Redis决定将AOF重写程序放到子进程里执行。对AOF 文件进行重写,执行bgrewriteaof命令, Redis将生成一个新的 AOF 文件,这个文件包含重写当前数据集所需的最少命令。bgrewriteaof后台重写实现步骤如下:

 

a.Redis执行 fork() ,现在同时拥有父进程和子进程。

 

b.子进程开始将新 AOF 文件的内容写入到临时文件。

 

c.对于所有新执行的写入命令,父进程一边将它们累积到一个内存缓存中,一边将这些改动追加到现有 AOF 文件的末尾,这样样即使在重写的中途发生停机,现有的 AOF 文件也还是安全的。

 

d.当子进程完成重写工作时,它给父进程发送一个信号,父进程在接收到信号之后,将内存缓存中的所有数据追加到新 AOF 文件的末尾。

 

e.现在Redis原子地用新文件替换旧文件,之后所有命令都会直接追加到新 AOF 文件的末尾。

 

(7)AOF配置详解

 

appendonly yes     //启用aof持久化方式

 

# appendfsync always //每收到写命令就立即强制写入磁盘,最慢的,但是保证完全的持久化,不推荐使用

 

appendfsync everysec //每秒强制写入磁盘一次,性能和持久化方面做了折中,推荐

 

# appendfsync no    //完全依赖os,性能最好,持久化没保证(操作系统自身的同步)

 

no-appendfsync-on-rewrite  yes  //正在导出rdb快照的过程中,要不要停止同步aof

 

auto-aof-rewrite-percentage 100  //aof文件大小比起上次重写时的大小,增长率100%时,重写

 

auto-aof-rewrite-min-size 64mb   //aof文件,至少超过64M时,重写

具体配置相关项参照以下表格

选项

取值

说明

Appendonly

no|yes

是否开启AOF机制

Appendfilename

"appendonly.aof"

Aof文件名

Appendfsync

no|appendfsync|always

AOF持久化同步频率

no-appendfsync-on-rewrite

No | yes

在日志进行BGREWRITEAOF时,如果设置为yes表示新写操作不进行同步fsync,只是暂存在缓冲区里,避免造成磁盘IO操作冲突,等重写完成后在写入。redis中默认为no 

 

auto-aof-rewrite-percentage

100

当前AOF文件大小是上次日志重写时的AOF文件大小两倍时,发生BGREWRITEAOF操作。

auto-aof-rewrite-min-size

64mb

当前AOF文件执行BGREWRITEAOF命令的最小值,避免刚开始启动Reids时由于文件尺寸较小导致频繁的BGREWRITEAOF。

aof-load-truncated

yes

Redis再恢复时,忽略最后一条可能存在问题的指令

aof-use-rdb-preamble

no

新增RDB-AOF混合持久化格式,在开启了这个功能之后,AOF重写产生的文件将同时包含RDB格式的内容和AOF格式的内容

 

(8)AOF持久化的优缺点

优点

1). 该机制可以带来更高的数据安全性,即数据持久性。Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失。而每修改同步,我们可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁盘中。可以预见,这种方式在效率上是最低的。至于无同步,无需多言,我想大家都能正确的理解它。

2). 由于该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容。然而如果我们本次操作只是写入了一半数据就出现了系统崩溃问题,不用担心,在Redis下一次启动之前,我们可以通过redis-check-aof工具来帮助我们解决数据一致性的问题。

3). 如果日志过大,Redis可以自动启用rewrite机制。即Redis以append模式不断的将修改数据写入到老的磁盘文件中,同时Redis还会创建一个新的文件用于记录此期间有哪些修改命令被执行。因此在进行rewrite切换时可以更好的保证数据安全性。

4). AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,我们也可以通过该文件完成数据的重建。

缺点

1). 对于相同数量的数据集而言,AOF文件通常要大于RDB文件。RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。

2). 根据同步策略的不同,AOF在运行效率上往往会慢于RDB。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB一样高效。

4.RDB与AOF两者的区别

RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。

 

AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。

 

 

              RDB AOF
存储数据   

保存键空间的所有键值对(包括过期字典中的数据),并以二进制形式保存,符合rdb文件规范,根据不同数据类型会有不同处理

 保存redis服务器所执行的所有写命令来记录数据库状态,在写入之前命令存储在aof_buf缓冲区。

 持久化时间选择

 通过conf的save选项设置持久化行为(单位时间内的修改次数)。

 通过conf的appendfsync选项设置持久化行为
数据还原

服务器载入rdb文件,阻塞线程,在载入完成之前不接受任何命令。

服务器创建不带网络连接的伪客户端,读取aof文件中的所有命令并执行(redis服务开启aof持久化在服务器启动时会选择aof文件恢复数据库状态)
 过期键  在写入或读取时会忽略过期键  不会忽略过期键,在键被惰性删除或定期删除时向aof文件追加一条删除命令
 文件大小  随着存储数据量的变化而变化(根据不同数据类型有不同的数据压缩优化)  随着执行命令的增加而增加(通过aof重写进行优化)
执行命令 SAVE和BGSAVE --

5.持久化相关阻塞

(1)fork阻塞:CPU的阻塞

在Redis中,无论是RDB持久化的bgsave,还是AOF重写的bgrewriteaof,都需要fork出子进程来进行操作。如果Redis内存过大,会导致fork操作时复制内存页表耗时过多;而Redis主进程在进行fork时,是完全阻塞的,也就意味着无法响应客户端的请求,会造成请求延迟过大。 
对于不同的硬件、不同的操作系统,fork操作的耗时会有所差别,一般来说,如果Redis单机内存达到了10GB,fork时耗时可能会达到百毫秒级别(如果使用Xen虚拟机,这个耗时可能达到秒级别)。因此,一般来说Redis单机内存一般要限制在10GB以内;不过这个数据并不是绝对的,可以通过观察线上环境fork的耗时来进行调整。观察的方法如下:执行命令info stats,查看latest_fork_usec的值,单位为微秒。 

为了减轻fork操作带来的阻塞问题,除了控制Redis单机内存的大小以外,还可以适度放宽AOF重写的触发条件、选用物理机或高效支持fork操作的虚拟化技术等,例如使用Vmware或KVM虚拟机,不要使用Xen虚拟机。

(2)AOF追加阻塞:硬盘的阻塞

 

当开启aof持久化功能时,文件刷盘的方式一般采用每秒一次,后台线程每秒对aof文件做fsync操作,硬盘压力过大时,fsync操作需要等待,直到写入完成如果主线程发现距离上一次的fsync成功超过2秒,为了数据安全性它会阻塞直到后台线程执行fsync操作完成,这种阻塞行为主要是硬盘压力引起,可查看info persistence统计中的aof_delayed_fsync指标,每次发生aof刷盘阻塞主线程时会累加。

AOF追加阻塞问题定位的方法: 
(1)监控info Persistence中的aof_delayed_fsync:当AOF追加阻塞发生时(即主线程等待fsync而阻塞),该指标累加。 
(2)AOF阻塞时的Redis日志: 
Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis. 
(3)如果AOF追加阻塞频繁发生,说明系统的硬盘负载太大;可以考虑更换IO速度更快的硬盘,或者通过IO监控分析工具对系统的IO负载进行分析,如iostat(系统级io)、iotop(io版的top)、pidstat等。

 

 

6.总结

本文主要内容可以总结如下: 
1、持久化在Redis高可用中的作用:数据备份,与主从复制相比强调的是由内存到硬盘的备份。 
2、RDB持久化:将数据快照备份到硬盘;介绍了其触发条件(包括手动出发和自动触发)、执行流程、RDB文件等,特别需要注意的是文件保存操作由fork出的子进程来进行。 
3、AOF持久化:将执行的写命令备份到硬盘(类似于MySQL的binlog),介绍了其开启方法、执行流程等,特别需要注意的是文件同步策略的选择(everysec)、文件重写的流程。 
4、一些现实的问题:包括如何选择持久化策略,以及需要注意的fork阻塞、AOF追加阻塞等。

7.参考资料

RDB参考

https://www.cnblogs.com/bamaofan/p/5284014.html

https://www.cnblogs.com/MrHSR/p/9999957.html

https://www.cnblogs.com/hjwublog/p/5660578.html

https://www.cnblogs.com/leeSmall/p/8379768.html

https://www.cnblogs.com/kevingrace/p/5685332.html

AOF参考

https://blog.csdn.net/Leon_cx/article/details/81545178

https://blog.csdn.net/qq_35433716/article/details/82195106

https://blog.csdn.net/hangbo216/article/details/68925644

https://www.cnblogs.com/MrHSR/P/10045533.html

https://www.cnblogs.com/chenliangcl/p/7240350.html

https://www.cnblogs.com/xingzc/p/5988080.html

https://www.cnblogs.com/jieliu726/p/9045942.html

https://blog.csdn.net/fjj15732621696/article/details/81842075

https://blog.csdn.net/linbiaorui/article/details/79822318

posted on 2019-04-21 15:50  Eugene_Jin  阅读(439)  评论(0编辑  收藏  举报