Redis 持久化

引言

Redis的数据全部在内存中,如果突然宕机,数据就会全部丢失,因此必须有一种机制来保证Redis的数据不会因为故障而丢失,这种机制就是Redis的持久化机制。

Redis的持久化机制有两种,第一种是快照,第二种是AOF日志。 快照是一次全量备份,AOF日志是连续的增量备份快照是内存数据的二进制序列化形式,在存储上非常紧凑,而AOF日志记录的是内存数据修改的指令记录文本。AOF日志在长期运行过程中会变得无比庞大,数据库重启时需要加载AOF日志进行指令重放,这个时间就会无比漫长。所以需要定期重写AOF,给AOF日志进行瘦身。

rdb是内存镜像|aof是指令操作日志

在这里插入图片描述

快照原理

Redis是单线程程序,这个线程要同时负责处理多个客户端套接字的并发读写操作和内存数据结构的逻辑读写。

在服务线上请求的同时,Redis还需要进行内存快照,内存快照要求Redis必须进行文件IO操作,可文件IO操作是不能使用多路复用API。

这意味着单线程同时在1. 服务线上请求还要进行2. 文件IO操作,文件IO操作会严重拖垮服务器请求的性能。还有个重要的问题是为了不阻塞线上的业务,还需要边持久化边响应客户端请求。持久化的同时,内存数据结构还在改变,比如一个大型的hash字典正在持久化,结果一个请求过来把他删了,此时数据还没持久化完。

面对上述问题,Redis如何解决。

Redis使用操作系统的多进程COW(Copy On Write)机制来实现快照持久化。

fork(多进程)

Redis在持久化时会调用glibc的函数 fork产生一个子进程,快照持久化完全交给子进程来处理,父进程继续处理客户端请求。子进程刚刚产生时,他和父进程共享内存里面的代码段和数据段,这时可以将父子进程看成一个连体婴儿,共享身体。这是Linux操作系统的机制,为了节约内存资源,所以尽可能让他们共享起来。在进程分离的一瞬间,内存的增长几乎没有明显变化。

用代码描述进程分离的逻辑如下,fork函数会在父子进程中同时返回pid,在父进程里返回子进程的pid,在子进程里返回pid为零。如果操作系统内存资源不足,pid就会是负数,表示fork失败。

pid = os.fork()
if pid>0
  handle_client_request() // 父进程继续处理客户端请求
if pid == 0
  handle_snapshot_write() //子进程处理快照写磁盘
if pid<0:
 # fork error  

子进程做数据持久化,他不会修改现有的内存数据结构,他只是对数据结构进行遍历读取,然后序列化写到磁盘中。但是父进程不一样,他必须持续服务客户端请求,然后对内存数据结构进行不间断修改。

这个时候就会使用操作系统的COW机制来进行数据段页面的分离。数据段是由很多操作系统的页面组合而成,当 父进程对其中一个页面的数据进行修改时,会将被共享的页面复制一份分离出来。然后对这个复制的页面进行修改。这时子进程相应的页面是没有变化的,还是进程产生时那一瞬间的数据。

随着父进程修改操作的持续进行,越来越多的共享页面被分离出来,内存就会持续增长。但是也不会超过原有数据内存的2倍大小。另外一个Redis实例里冷数据占的比例往往是比较高的,所以很少会出现所有的页面都会被分离(COW)。被分离的往往只有其中一部分页面,每个页面的大小只有4K,一个Redis实例里面一般会有成千上万的页面。

子进程因为数据没有变化,他能看到的内存里的数据在进程产生的一瞬间就凝固了,再也不会改变,这也是为什么Redis的持久化叫【快照】的原因,接下来子进程就可以非常安心的遍历数据进行序列化写磁盘。

AOF原理

AOF日志存储的是Redis服务器的顺序指令序列,AOF日志只记录对内存进行修改的指令记录。

假设AOF日志记录了自Redis实例创建以来所有的修改性指令序列,那么就可以通过对一个空的Redis实例顺序执行所有的指令,也就是【重放】,来恢复Redis当前实例的内存数据结构的状态。

Redis会在收到客户端修改指令后,先进行参数校验,如果没问题,就立即将该指令文本存储到AOF日志中,也就是先写到磁盘,然后在执行指令。这样即使遇到突发宕机,已经存储到AOF日志的指令重放一下就可以恢复到宕机前的状态。

Redis在长期运行的过程中,AOF的日志会越来越长。如果实例宕机重启,重放整个AOF日志会非常耗时,导致长时间Redis无法对外提供服务。所以需要对AOF日志瘦身

AOF重写

Redis提供了bg-rewrite-aof 指令用于对AOF日志进行瘦身。其原理就是开辟一个子进程对内存进行遍历转换成一系列Redis的操作指令,序列化到一个新的AOF日志文件中。序列化完毕后再将操作期间发生的增量AOF日志追加到这个新的AOF日志文件中,追加完毕后就立即替代旧的AOF日志文件,瘦身工作就完成了。

fsync

AOF日志是以文件形式存在的,当程序对AOF日志文件进行写操作时,实际上是将内容写到了内核为文件描述符分配的一个内存缓存中,然后内核会异步将脏数据刷回到磁盘。具体可以分为如下几个步骤

  • 1. 写入操作: 当程序需要写入数据到AOF日志时,它实际上并不是直接写入磁盘,程序的写操作首先是向操作系统指示他想要将数据写入到一个特定的文件。

  • 2. 文件描述符和内存缓存: 操作系统为每个打开的文件分配一个文件描述,这是一个用于标识和管理文件的抽象概念。当数据被写入到一个文件时,实际上他首先被写入到与该文件描述符关联的内存缓存,这个内存缓存是操作系统内存中的一部分,用来临时存储写入的数据。

  • 3.异步数据刷新: 一旦数据被写入内存缓存,他就被标记为“脏数据”,即数据已经修改但还没有被保存到永久存储设备上,操作系统会在适当的时候,异步的将这些脏数据从内存缓存刷回到硬盘。这个过程是自动进行的,通常依赖于操作系统的IO调度策略,可能会考虑当前系统的负载和IO性能等因素。

  • 4. 性能与安全性: 这种写入机制提供了较高的性能,因为写入内存通常比写入硬盘快得多,同时,这种方法也可能带来数据丢失的风险,如果在数据被持久化到硬盘之前系统发生崩溃,那么内存中的脏数据可能会丢失。

安全性问题:上面提到如果数据被持久化到硬盘之前系统发生崩溃,内存中的脏数据可能会丢失,这个时候也就会出现AOF日志丢失。
Linux的glibc提供了fsync(int fd) 函数可以将指定文件的内容强制从内核缓存刷到磁盘。只要Redis进程实时调用fsync函数就可以保证aof日志不丢失,但是他很慢。如果Redis执行一条指令就要fsync一次,那么Redis的高性能地位就不保了。

在生产环境的服务器中,Redis通常是每隔1s左右执行一次fsync操作,周期1s是可以配置的。

Redis也同样提供了另外两种策略,一个是永不fsync——让操作系统决定何时同步磁盘,这很不安全。另一个就是每执行一条指令fsync一次——非常慢。

运维

快照是通过开启子进程的方式进行的,他是一个比较耗资源的操作。

  1. 遍历整个内存,大块写磁盘会加重系统负载。
  2. AOF的fsync是一个耗时的IO操作,他会降低Redis性能,同时也会增加系统IO负担。

通常来说,Redis的主节点不会进行持久化操作,持久化操作主要在从节点上进行。从节点是备份节点,没有来自客户端请求的压力,他的操作系统资源往往比较充沛。

但是如果出现网络分区,从节点长期连不上主节点,就会出现数据不一致问题,特别是在网络分区出现的情况下又发生主节点宕机,那么数据就会丢失。所以在生产环境要做好实时监控,保证网络畅通或者能快速修复。另外,还应该再增加一个从节点以降低网络分区的概率,只要有一个从节点数据同步正常,数据就不会轻易丢失。

Redis4.0 混合持久化

问题:重启Redis时,我们很少使用rdb来恢复内存状态,因为会丢失大量数据。我们通常使用AOF日志重放,但是重放AOF日志性能相比rdb来说要慢很多。这样在Redis实例很大的情况下,启动需要花费很长时间。

Redis4.0为了解决这个问题,带来了一个新的持久化选项——混合持久化,将rdb文件的内容和增量的AOF日志文件放在一起。这里的AOF日志不再是全量的AOF日志,而是自持久化开始到持久化结束的这段时间发生的增量AOF日志。通常这部分AOF日志很小。

在这里插入图片描述
于是,在Redis重启的时候,可以先加载rdb内容,然后在重放增量AOF日志就可以完全替代之前的AOF全量文件重放,重启效率因此大幅提升。

相关推荐

最近更新

  1. TCP协议是安全的吗?

    2024-06-16 00:20:01       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-16 00:20:01       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-16 00:20:01       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-16 00:20:01       18 阅读

热门阅读

  1. 判断子字符串是否存在

    2024-06-16 00:20:01       7 阅读
  2. leetcode 200 岛屿数量

    2024-06-16 00:20:01       5 阅读
  3. C语言----深入理解指针(5)

    2024-06-16 00:20:01       7 阅读
  4. Linux之tar打包解包命令

    2024-06-16 00:20:01       3 阅读
  5. Linux 常用命令 - cd 【切换目录】

    2024-06-16 00:20:01       7 阅读