面试官:Redis在RDB持久化时,还能对外读写数据吗

370 阅读8分钟

前言

Hi 你好,我是东东拿铁,一个正在探索个人IP&副业的帅比码农[手动狗头]。

Redis持久化方式有,AOF、RDB,本文将重点介绍RDB相关的一系列问题

RDB是持久化方法:内存快照。所谓内存快照,就是指内存中的数据在某一个时刻的状态记录。这就类似于照片,当你给朋友拍照时,一张照片就能把朋友一瞬间的形象完全记下来。

文章开始前,先问你几个问题

  1. 对哪些数据做快照?这关系到快照的执行效率问题
  2. 做快照时,数据还能被增删改吗?这关系到 Redis 是否被阻塞,能否同时正常处理请求?
  3. 既然是内存快照,如果机器内存是4GB,Redis使用3GB,那么快照时,是不是快照+实际使用=6GB?那到底是怎么处理的?
  4. RDB是快照,我们能不能频繁使用RDB方式,保证数据更加完整呢?

带着这四个问题,让我们一起走近这篇文章,当你看完后,相信你在也不怕RDB相关的问题了。

RDB概念

如何执行RDB,Redis 提供了两个命令来生成 RDB 文件,分别是save和bgsave。

save:在主线程中执行,会导致阻塞;

bgsave:创建一个子进程,专门用于写入 RDB 文件,避免了主线程的阻塞,这也是Redis RDB 文件生成的默认配置。

image.png

快照时,Redis还能进行读写操作吗?

做快照时,我们一定希望,在做完快照前,数据是不会修改的,快照就像是拍照片一样,我们想记录下,那一时刻的数据,如果数据有变动, 就会破坏快照的完整性。

假设我们的内存使用量为4GB,假设磁盘写入速度1GB/s,那也需要4s的时间,才能够完成快照操作。

如果在这4s中,为了保证快照完整性,如果我们不允许Redis对数据进行修改,无疑影响是巨大的,对业务造成很大的影响。

因此为了快照,禁止写操作,无疑是不能接受的,这时候,我们要引出一个非常重要的技术,写时复制(Copy-On-Write, COW)。

什么是写时复制?

写时复制(Copy-on-write,简称COW)是一种计算机程序设计领域的优化策略。其核心思想是,如果有多个调用者(callers)同时请求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的。 此作法主要的优点是如果调用者没有修改该资源,就不会有副本(private copy)被创建,因此多个调用者只是读取操作时可以共享同一份资源。

上面我们说到了,Redis在RDB时,默认使用bgsave,bgsave子进程是由主线程fork生成的,可以共享主线程的内存数据。在bgsave子进程运行时后,开始读取主线程的内存数据,并开始写入RDB文件。

如果主线程对这些数据都是读操作,那么主线程和bgsave子进程相互不影响。

如果主线程对这些数据有写操作,那么这块数据就会被复制,生成该数据的副本。然后bgsave子进程会把这个副本数据写入RDB文件中,主进程的写操作不受影响。

小结

到这里,我们已经回答了开头的前两个问题。

  1. RDB进行备份时,备份的数据为全量数据

  2. RDB时,bgsave子进程会在后台进行,利用了写时复制技术,允许主线程同时可以修改数据。

RDB操作,有没有可能导致内存不足?

回答这个问题前,我们先来了解两个操作系统的基础概念

OOM

OOM killer机制:Linux 内核有个机制叫OOM killer(Out Of Memory killer),该机制会监控那些占用内存过大,尤其是瞬间占用内存很快的进程,然后防止内存耗尽而自动把该进程杀掉。

内核检测到系统内存不足、挑选并杀掉某个进程的过程可以参考内核源代码linux/mm/oom_kill.c,当系统内存不足的时候, out_of_memory()被触发,然后调用select_bad_process()选择一个" bad"进程杀掉。如何判断和选择一个"bad进程呢? linux选择"bad"进程是通过调用oom_badness0,挑选的算法和想法都很简单很朴实:最bad的那个进程就是那个最占用内存的进程。

Swap机制

Swap机制:就是把一块磁盘或者一个本地文件,当作内存使用,它包括两个过程:换出,把进程暂时不用的内存数据保存到磁盘中,在释放内存给其他进程使用。换入,当进程再次访问内存时,从磁盘读取数据到内存中。

假设我们线上是以下场景

我们使用一个 2 核 CPU、4GB 内存、500GB 磁盘的云主机运行 Redis,Redis 数据库的数据量大小差不多是 2GB,我们使用了 RDB 做持久化保证。当时 Redis 的运行负载以修改操作为主,写读比例差不多在 8:2 左右,也就是说,如果有 100 个请求,80 个请求执行的是修改操作。你觉得,在这个场景下,会有什么现象产生呢?

在这种场景下,可能会带来性能下降,甚至OOM等一系列 内存资源风险

OOM?为什么

Redis fork子进程做RDB持久化,由于写的比例为80%,那么在持久化过程中,“写时复制”会重新分配整个实例80%的内存副本,大约需要重新分配1.6GB内存空间,这样整个系统的内存使用接近饱和。

如果此时父进程又有大量新key写入,很快机器内存就会被吃光,如果机器开启了Swap机制,那么Redis会有一部分数据被换到磁盘上,当Redis访问这部分在磁盘上的数据时,性能会急剧下降,已经达不到高性能的标准。如果机器没有开启Swap,会直接触发OOM,父子进程会面临被系统kill掉的风险。

小结

到这里,我们回答了第三个问题,在RDB时,写入操作的比例越高,对内存占用量就越大,极端情况下,可能会导致OOM,或者线上性能急剧下降。

为了保证数据更完整,RDB越频繁越好吗

RDB是内存快照,但在两次快照间隙中,如果Redis宕机,我们可能会丢掉一部分数据,既然如此,我们时刻都去RDB,不就可以几乎保证不丢失数据了吗?

为什么

先说答案,这是错误的。

虽然 bgsave 执行时不阻塞主线程,但是,如果频繁地执行全量

快照,也会带来两方面的开销。

一方面,频繁将全量数据写入磁盘,会给磁盘带来很大压力,多个快照竞争有限的磁盘带宽,前一个快照还没有做完,后一个又开始做了,容易造成恶性循环。

另一方面,bgsave 子进程需要通过 fork 操作从主线程创建出来。虽然,子进程在创建后不会再阻塞主线程,但是,fork 这个创建过程本身会阻塞主线程,而且主线程的内存越大,阻塞时间越长。如果频繁 fork 出 bgsave 子进程,这就会频繁阻塞主线程了。

怎么办

直接上答案:

Redis 4.0 中提出了一个混合使用 AOF 日志和内存快照的方法。简单来说,内存快照以一定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作。

说在最后

本文主要讲解了Redis采用RDB作为持久化方式的时,RDB也许你很了解,但底层实现可能遇到的一系列问题,你也需要掌握,做到知其然,知其所以然。

不知道你对Redis的了解怎么样,在面试过程中,有没有因为Redis而被面试官问住过呢?欢迎你在评论区与我交流

最后,我是东东拿铁,正在探索个人IP&副业,如果文章对你有帮助,希望得到你的三连~也欢迎加我的wx:Ldhrlhy10,一起交流~