redis从零开始(5)----持久化

持久化

概念

为了保证redis的读写速度,redis的操作大多都是在内存中,但是设备重启之后,内存中的数据都会丢失,保证内存中的数据不丢失的操作就叫做数据持久化。这个机制会将redis的数据保存到磁盘,redis在重启之后去磁盘恢复数据。
redis持久化的方式:

  • AOF日志:每执行一条写操作命令,就把该命令以追加的方式写入到一个文件里;
  • RDB快照:将某一时刻的内存数据,以二进制的方式写入磁盘;
  • 混合持久化方式:Redis 4.0 新增的方式,集成了 AOF 和 RBD 的优点;

AOF日志

实现

redis在执行完一条写操作命令后,就会把该命令以追加的方式写入到一个文件里,然后redis重启时,会依次执行该文件中记录的命令。如:
客户端执行命令set name qjl,redis在执行完后,会将该命令记录在文件中:

*3
$3
set
$4
name
$3
qjl

「*3」的意思是该命令有三部分,「$+数字」的意思是这一部分的命令有几个字节,后面跟这部分命令

为什么先执行命令,再写入日志呢?

  • 优点:
    • 避免了额外的检查开销:因为如果先将写操作命令记录到 AOF 日志里,再执行该命令的话,如果当前的命令语法有问题,那么如果不进行命令语法检查,该错误的命令记录到 AOF 日志里后,Redis 在使用日志恢复数据时,就可能会出错。
    • 不会阻塞当前写操作命令的执行:因为当写操作命令执行成功后,才会将命令记录到 AOF 日志。
  • 缺点:
    • 数据可能会丢失: 执行写操作命令和记录日志是两个过程,那当 Redis 在还没来得及将命令写入到硬盘时,服务器发生宕机了,这个数据就会有丢失的风险。
    • 可能阻塞其他操作: 由于写操作命令执行成功后才记录到 AOF 日志,所以不会阻塞当前命令的执行,但因为 AOF 日志也是在主线程中执行,所以当 Redis 把日志文件写入磁盘的时候,还是会阻塞后续的操作无法执行。

AOF写回策略有几种?

redis写入AOF日志的过程,具体为:

  • redis执行完写操作后,将命令追加到server.aof_buf 缓冲区;
  • 然后通过 write() 系统调用,将 aof_buf 缓冲区的数据写入到 AOF 文件,此时数据并没有写入到硬盘,而是拷贝到了内核缓冲区 page cache,等待内核将数据写入硬盘;
  • 具体内核缓冲区的数据什么时候写入到硬盘,由内核决定。

Redis 提供了 3 种写回硬盘的策略,控制的就是上面说的第三步的过程。 在 Redis.conf 配置文件中的 appendfsync 配置项可以有以下 3 种参数可填:

  • Always,这个单词的意思是「总是」,所以它的意思是每次写操作命令执行完后,同步将 AOF 日志数据写回硬盘;
  • Everysec,这个单词的意思是「每秒」,所以它的意思是每次写操作命令执行完后,先将命令写入到 AOF 文件的内核缓冲区,然后每隔一秒将缓冲区里的内容写回到硬盘;
  • No,意味着不由 Redis 控制写回硬盘的时机,转交给操作系统控制写回的时机,也就是每次写操作命令执行完后,先将命令写入到 AOF 文件的内核缓冲区,再由操作系统决定何时将缓冲区内容写回硬盘。
    我也把这 3 个写回策略的优缺点总结成了一张表格:
写回策略 写回时机 优点 缺点
Always 同步写回 可靠性高、最大程度保证数据不丢失 每个写命令都要写回硬盘,性能开销大
Everysec 每秒写回 性能适中 宕机时会丢失一秒内的数据
No 由操作系统控制写回 性能好 宕机时丢失的数据可能会更多

AOF日志过大,会触发什么机制?

如果AOF日志过大,在redis重启后可能需要的恢复时间就会很长,带来一系列性能问题。所以,当AOF文件过大时,就会触发AOF重写机制来压缩AOF文件。如:
假设先后执行了「set name qjl」和「set name qjl2」,在执行重写机制之前,AOF文件中会有两条语句,执行重写机制后,redis会读取name最新的值,第一个命令就没有必要记录了,这样一来,新的AOF文件覆盖旧的文件后,就实现了文件的压缩。

重写AOF日志的过程?

Redis 的重写 AOF 过程是由后台子进程 bgrewriteaof 来完成的,这么做可以达到两个好处:

  • 子进程重写AOF日志期间不需要阻塞父进程的命令处理
  • 使用子进程而不是线程是因为线程会共享内存,那么在重写日志期间就需要加锁来保证线程安全,降低性能。而使用进程的话,父子进程以只读的方式共享内存,当父子进程任意一方修改了该共享内存,就会复制一份(写时复制),两方拥有独立的数据副本,保证数据安全

但是,如果在子进程重写日志的期间主进程又更改了数据,应该怎么办?
redis设置了一个AOF重写缓冲区,当主进程执行完写命令后,它会将这条命令同时写入到AOF缓冲区和AOF重写缓冲区。子进程完成日志重写工作后,向主进程发一条信号,主进程收到信号后,会调用一个信号处理函数,该函数主要做以下工作:

  • 将AOF重写缓冲区的所有内容追加到AOF日志文件中
  • 将新的AOF日志文件改名,覆盖旧文件

RDB快照

实现

AOF文件过大时势必会导致redis的恢复缓慢,未解决这个问题,可以使用RDB快照。
AOF保存的不是redis中的数据,而是一条条的命令;RDB快照保存到是redis中某一个时刻的实际数据。因此在恢复数据时,RDB的效率要比AOF高些

RDB做快照时会阻塞线程吗?

redis提供了两个命令来做快照,分别是:

  • save:主线程负责生成RDB文件,会阻塞主线程
  • bgsave:创建一个子线程来生成RDB文件

redis还可以通过修改配置文件来实现每过一段时间就自动执行bgsave命令,默认如下:

# 满足以下条件之一就执行
save 900 1			# 900秒内做了至少一次修改
save 300 10		# 300秒内做了至少10次修改
save 60 10000	# 60秒内做了至少10000次修改

redis做快照是全量快照,每次将内存中所有数据都记录到磁盘中。如果记录频率过高,会影响性能;记录频率过低,可能会丢失数据。

RDB做快照时,数据能修改吗?

执行bgsave的过程中,redis主进程依然可以处理命令。利用的就是写时复制技术(Copy-On-Write, COW)

  • 如果是读命令,主线程和bgsave子进程互不影响
  • 如果是写命令,被修改的数据会复制一份副本,然后子进程将该副本数据写入RDB文件,主线程仍然可以直接修改原来的数据

混合持久化

RDB 优点是数据恢复速度快,但是快照的频率不好把握。
AOF 优点是丢失数据少,但是数据恢复不快。
混合持久化集成了二者的优点,混合持久化工作在 AOF 日志重写过程,当开启了混合持久化时,在 AOF 重写日志时,fork 出来的重写子进程会先将与主线程共享的内存数据以 RDB 方式写入到 AOF 文件,然后主线程处理的操作命令会被记录在重写缓冲区里,重写缓冲区里的增量命令会以 AOF 方式写入到 AOF 文件,写入完成后通知主进程将新的含有 RDB 格式和 AOF 格式的 AOF 文件替换旧的的 AOF 文件。

也就是说,使用了混合持久化,AOF 文件的前半部分是 RDB 格式的全量数据,后半部分是 AOF 格式的增量数据。

这样的好处在于,重启 Redis 加载数据的时候,由于前半部分是 RDB 内容,这样加载的时候速度会很快。加载完 RDB 的内容后,才会加载后半部分的 AOF 内容,这里的内容是 Redis 后台子进程重写 AOF 期间,主线程处理的操作命令,可以使得数据更少的丢失。

混合持久化优点:

  • 混合持久化结合了 RDB 和 AOF 持久化的优点,开头为 RDB 的格式,使得 Redis 可以更快的启动,同时结合 AOF 的优点,有减低了大量数据丢失的风险。

混合持久化缺点:

  • AOF 文件中添加了 RDB 格式的内容,使得 AOF 文件的可读性变得很差;
    兼容性差,如果开启混合持久化,那么此混合持久化 AOF 文件,就不能用在 Redis 4.0 之前版本了。

redis大key对持久化的影响

当 AOF 写回策略配置了 Always 策略,如果写入是一个大 Key,主线程在执行 fsync() 函数的时候,阻塞的时间会比较久,因为当写入的数据量很大的时候,数据同步到硬盘这个过程是很耗时的。

AOF 重写机制和 RDB 快照(bgsave 命令)的过程,都会分别通过 fork() 函数创建一个子进程来处理任务。会有两个阶段会导致阻塞父进程(主线程):

  • 创建子进程的途中,由于要复制父进程的页表等数据结构,阻塞的时间跟页表的大小有关,页表越大,阻塞的时间也越长;
  • 创建完子进程后,如果父进程修改了共享数据中的大 Key,就会发生写时复制,这期间会拷贝物理内存,由于大 Key 占用的物理内存会很大,那么在复制物理内存这一过程,就会比较耗时,所以有可能会阻塞父进程。

大 key 除了会影响持久化之外,还会有以下的影响:

  • 客户端超时阻塞。由于 Redis 执行命令是单线程处理,然后在操作大 key 时会比较耗时,那么就会阻塞 Redis,从客户端这一视角看,就是很久很久都没有响应。
  • 引发网络阻塞。每次获取大 key 产生的网络流量较大,如果一个 key 的大小是 1 MB,每秒访问量为 1000,那么每秒会产生 1000MB 的流量,这对于普通千兆网卡的服务器来说是灾难性的。
  • 阻塞工作线程。如果使用 del 删除大 key 时,会阻塞工作线程,这样就没办法处理后续的命令。
  • 内存分布不均。集群模型在 slot 分片均匀情况下,会出现数据和查询倾斜情况,部分有大 key 的 Redis 节点占用内存多,QPS 也会比较大。

最好在设计阶段,就把大 key 拆分成一个一个小 key。或者,定时检查 Redis 是否存在大 key ,如果该大 key 是可以删除的,不要使用 DEL 命令删除,因为该命令删除过程会阻塞主线程,而是用 unlink 命令(Redis 4.0+)删除大 key,因为该命令的删除过程是异步的,不会阻塞主线程。

你可能感兴趣的:(数据库mysql/redis,redis,数据库,缓存)