五、持久化

0.持久化介绍

redis是跑在内存里的,当程序重启或者服务崩溃,数据就会丢失,所以需要持久化,即把数据保存到可永久保存的存储设备中

持久化方法

redis提供两种方式来持久化:

1.RDB(Redis Database)

记录Redis某个时刻的全部数据,这个方法的本质就是数据快照,直接保存二进制数据到磁盘,后续通过加载RDB文件恢复数据

2.AOF(Append Only File)

记录执行的每条命令,重启之后通过重放命令来恢复数据,AOF是记录操作日志,后续通过日志重放恢复数据

两种持久化方法的对比(*)

上面两种持久化方法对比:RDB(快照恢复)和AOF(日志恢复)

  1. 体积方面:相同数据量下,RDB体积小,因为RDB记录的是二进制紧凑型数据

  2. 恢复速度方面:RDB是数据快照,可以直接加载,而AOF文件恢复,相当于重放情况,RDB显然更快

  3. 数据完整性:AOF记录了每条日志,RDB是间隔一段时间记录一次,用AOF恢复数据通常会更加完整

RDB好还是AOF好

  • 业务本身需要的是缓存数据并且不是一个海量访问,可以不用开持久化

  • 对数据本身十分重视,可以同时开启RDB和AOF,注意,在同时开启的情况下,只会用AOF来加载,如果只有RDB文件而没有AOF文件,不会用RDB文件去恢复数据,如果逻辑是你自主开启选择AOF,表明要强一点的一致性,但是AOF文件缺失,此时不会去使用RDB,业务RDB会少很多数据,此时启动是一个空库

1.RDB

RDB文件的内容是**二进制数据,**记录的是某一瞬间的内存数据,是实际的数据,也叫做快照

在Redis恢复数据时,RDB恢复数据的效率会比AOF高,因为会直接读取RDB文件到内存即可,不需要像AOF那样还需要额外执行操作命令的步骤才能恢复数据

1.1 RDB怎么使用

Redis提供两个命令来使用RDB文件,分别是savebgsave

  • save :执行了save命令,会在主线程生成RDB文件,由于和执行操作命令在同一个线程,所以写入RDB文件的时间太长,会阻塞主线程

  • bgsave :执行了bgsave命令,会创建一个子进程来生成RDB文件,这样可以避免主线程的阻塞

RDB文件的加载工作是在服务器启动时自动执行,Redis并没有提供专门加载RDB文件的命令

在Redis的配置文件中,有以下的选项来实现每隔一段时间自动执行一次bgsave命令

# 这里写的是save,实际上是bgsave
save 900 1         # 900s之内,对数据库进行至少1次修改
save 300 10        # 300s之内,对数据库进行至少10次修改
save 60 10000      # 60s之内,对数据库进行至少10000次修改

Redis的快照是全量快照,也就是说每次执行快照,都是把内存中的所有数据记录到磁盘中

RDB快照的缺点:当服务器发送故障时,丢失的数据会比AOF更多

1.2 执行快照时(bgsave),修改数据会发生什么?

在执行bgsave时,主线程是可以继续执行操作命令,由子线程来构建RDB文件,所以内存里的数据是可以修改的

实现这一功能靠的是写时复制****技术

流程如下:

  1. 执行bgsave命令时,会fork()创建子进程,此时子进程和父进程是共享同一片内存数据

主进程创建子进程时,子进程会复制父进程的页表,但是页表指向的物理内存跟主进程是同一个物理内存

  1. 此时如果主线程由写命令的执行,就会发生写时复制,物理内存才会被复制一次

发生写时复制后,RDB快照保存的是原本的内存数据

这是因为创建bgsave子进程后,由于父子进程共享所有内存数据,所以可以直接将数据写入到RDB文件

  • 当主进程对共享的内存数据是只读****操作,那么主进程和bgsave子进程是相互不影响

  • 当主进程对共享的内存数据进行写操作时,就会发生写时复制,这块数据的物理内存就会被复制一份,然后主线程在这个数据副本进行写操作,此时bgsave子进程继续把原来的数据(原物理内存)写入到RDB文件

执行bgsave时,在极端情况下,如果所有的共享内存都被修改,则此时的内存占用是原先的两倍

1.3 什么时候执行RDB持久化

  1. 主动执行命令save

  2. 主动执行命令bgsave

  3. 达到持久化配置阈值

  4. redis服务正常关闭

1.4 总结

从整体上看:RDB持久化的过程

  1. Fork出一个子进程来专门做RDB持久化

  2. 子进程写数据到临时的RDB文件

  3. 写完之后,用新RDB文件替换旧的RDB文件

RDB本质就是Redis的数据快照,这种方式是最常见、最稳定的数据持久化手段,Redis中RDB的触发方式有三种:

  1. 达到阈值周期函数触发

  2. 正常关闭Redis服务触发

  3. 主动执行BGSAVE命令触发

Redis通过fork一个子进程的方式来进行RDB,配合写时复制技术,相当于异步执行,与主进程互不干扰,将对执行流程的影响降到最低

2.AOF

AOF(Append Only File),保存写操作到日志的持久化方式,只记录写操作命令,读操作命令不会被记录,在redis中默认不开启

Redis是先执行命令,才将该命令记录到AOF日志,这样有两个好处:

  1. 避免额外的检查开销

  2. 不会阻塞当前写操作命令的执行

但是AOF持久化有潜在风险:

  1. 执行写操作命令和记录日志是两个过程,如果Redis还没来得及将命令写入到硬盘时,服务器发生宕机,这个数据就会有丢失的风险

  2. 写操作命令执行成功后才记录到AOF日志中,所以不会阻塞当前写操作命令的执行,但是可能会给下一个命令带来阻塞的风险

这个将写操作命令是在主线程完成的(执行命令也是在主进程)

2.1 AOF的三种写回策略(三种写入流程)

Redis写入AOF日志的过程

  1. Redis执行写操作命令后,会将命令追加到server.aof_buf 缓冲区

  2. 通过I/O系统调用write() ,将缓冲区的数据写到AOF文件,此时数据并没有写入硬盘,而是拷贝到内核缓冲区page cache,等待内核将数据写入硬盘

  3. 最后由内核决定page cache写入到硬盘

以上是Redsi将写操作命令写入到AOF文件的过程,在Redis中,由三种写回策略可以配置

  • Always 每次写操作命令执行完后,同步将AOF日志数据写回硬盘

  • Everysec 每次写操作命令执行完之后,先将命令写入到AOF文件的内核缓冲区,然后每隔一秒将缓冲区里的内容写回到硬盘

  • No 表示不由Redis控制写回硬盘的时机,每次写操作命令执行完成后,先将命令写入到AOF文件的内核缓冲区,在由操作系统决定何时将缓冲区内容写回硬盘

redis.conf 配置文件中 appendsync 配置项可以配置上面三种参数

这三种写回策略都无法能完美解决主进程阻塞和减少数据丢失的问题

写回策略写回时机优点缺点
Always同步写回可靠性高、最大程度保证数量不丢失每个写命令都要写会硬盘,性能开销大
Everysec每秒写回性能适中宕机时丢失1秒内的数据
No由操作系统控制写回性能好宕机时丢失的数据可能会很多

2.2 AOF的重写机制

AOF是一个文件,AOF文件会随着写操作命令越来越多,文件会越来越大

当AOF日志文件过大会带来性能问题,所以Redis为了避免AOF文件越写越大,提供了**AOF重写机制,**当AOF文件的大小超过所设定的阈值,Redis就会启用AOF重写机制,来压缩AOF文件

在使用重写机制后,就会读取name最新的value,然后用一条最新的命令记录到新的AOF文件

在AOF重写过程中,为了防止AOF文件被新的AOF文件污染,所以在AOF文件重写过程中,不会直接复用现有的AOF文件,而是先写到新的AOF文件在覆盖到旧的AOF文件

2.3 AOF后台重写

AOF重写的过程很耗时,所以重写的操作不放在主进程里。(AOF日志的写操作是在主进程里完成的)

Redis的重写AOF过程是由后台子进程bgrewriteaof来完成的

  • 子进程进行AOF重写期间,主进程可以继续处理命令请求,从而避免阻塞主进程;

  • 子进程带有主进程的数据副本

触发重写机制后,主进程就会创建重写AOF的子进程,此时父子进程共享物理内存,重写子进程只会对这个内存进行只读,重写AOF子进程会读取数据库(就是内存)里所有数据,并逐一把内存数据的键值对转化成一条命令,再将命令记录到重写日志

如果此时主进程修改了已经存在key-value,就会发生写时复制,这里只会复制主进程修改的物理内存数据,没修改物理内存还是与子进程共享的

对于AOF重写过程中,如果主进程修改了某个key-value,此时该key-value在子进程的内存和主进程的内存数据不一致,为了解决这种数据不一致问题,Redis设置一个**AOF重写缓冲区,**这个缓冲区在创建bgrewriteaof子进程后开始使用

在重写AOF期间,当Redis执行完一个命令之后,它会同时将这个命令写入到AOF缓冲区和AOF重写缓冲区

也就是,在bgwriteaof子进程执行AOF重写期间,主进程需要执行以下三个工作:

  1. 执行客户端发来的命令

  2. 将执行后的写命令追加到AOF缓冲区

  3. 将执行后的写命令追加到AOF重写缓冲区

当子进程完成AOF重写工作,会向主进程发送一条信号,信号是进程间通讯的一种方式,且是异步的

主进程收到该信号后,会调用一条信号处理函数,该函数主要做以下工作:

  • 将AOF重写缓冲区的所有内容追加到新的AOF的文件中,使得新旧两个AOF文件所保存的数据库状态一致

  • 新的AOF文件进行改名,覆盖现有的AOF文件

2.4 AOF的重写时机

在Redis的配置文件中,由两个参数决定了重写规则

# 相比上次重写是数据增长100%
auto-aof-rewrite-percentage 100
# 超过64mb
auto-aof-rewrite-min-size 64mb

通过周期函数来检查和触发

2.5 总结

AOF通过记录日志来进行持久化,Redis提供了三种刷盘策略以应对不同的性能要求,分别是每次刷,每秒刷,不主动刷三种,其中Redis推荐每秒刷。

针对AOF不断膨胀的问题,redis提供了重写机制,针对相同key的操作进行合并,来减少AOF日志文件的大小

3.AOF优化——混合持久化

混合持久化是对AOF重写的优化,这种方法可以大大降低AOF重写的性能损耗,以降低AOF文件的存储空间,付出的代价则是降低AOF文件的读写行。

Redis 5.0 开始之后默认打开AOF混合持久化模式

使用Redis配置文件开启混合持久化功能

aof-use-rdb-preamble yes

混合持久化工作在AOF日志重写过程

开启混合持久化时,在AOF重写日志时,fork出来的重写子进程会先将于主进程共享的内存数据以RDB的方式写入到AOF文件,然后主进程处理新的操作命令会被记录在重写缓冲区,重写缓冲区的增量命令会以AOF方式写入到AOF文件,写入完成后通知主进程将含有RDB格式和AOF格式的AOF文件替换旧的AOF文件

这里的AOF文件的前半部分是RDB格式的全量数据,后半部分是AOF格式的增量数据

3.1混合持久化的加载流程