对 Redis 来讲,它实现相似照片记录效果的方式,就是把某一时刻的状态以文件的形式写到磁盘上,也就是快照。这样一来,即便宕机,快照文件也不会丢失,数据的可靠性也就获得了保证。这个快照文件就称为 RDB 文件,其中,RDB 就是 Redis DataBase 的缩写。ide
和 AOF 相比,RDB 记录的是某一时刻的数据,并非操做,因此,在作数据恢复时,咱们能够直接把 RDB 文件读入内存,很快地完成恢复。听起来好像很不错,但内存快照也并非最优选项。为何这么说呢?性能
咱们还要考虑两个关键问题:spa
对哪些数据作快照?这关系到快照的执行效率问题;操作系统
作快照时,数据还能被增删改吗?这关系到 Redis 是否被阻塞,可否同时正常处理请求。线程
这么说可能你还不太好理解,我仍是拿拍照片来举例子。咱们在拍照时,一般要关注两个问题:日志
如何取景?也就是说,咱们打算把哪些人、哪些物拍到照片中;blog
在按快门前,要记着提醒朋友不要乱动,不然拍出来的照片就模糊了进程
给哪些内存数据作快照?图片
Redis 的数据都在内存中,为了提供全部数据的可靠性保证,它执行的是全量快照,也就是说,把内存中的全部数据都记录到磁盘中,这就相似于给 100 我的拍合影,把每个人都拍进照片里。这样作的好处是,一次性记录了全部数据,一个都很多。内存
当你给一我的拍照时,只用协调一我的就够了,可是,拍 100 人的大合影,却须要协调 100 我的的位置、状态,等等,这固然会更费时费力。一样,给内存的全量数据作快照,把它们所有写入磁盘也会花费不少时间。并且,全量数据越多,RDB 文件就越大,往磁盘上写数据的时间开销就越大。
对于 Redis 而言,它的单线程模型就决定了,咱们要尽可能避免全部会阻塞主线程的操做,因此,针对任何操做,咱们都会提一个灵魂之问:“它会阻塞主线程吗?”RDB 文件的生成是否会阻塞主线程,这就关系到是否会下降 Redis 的性能。
Redis 提供了两个命令来生成 RDB 文件,分别是 save 和 bgsave。
save:在主线程中执行,会致使阻塞;
bgsave:建立一个子进程,专门用于写入 RDB 文件,避免了主线程的阻塞,这也是 Redis RDB 文件生成的默认配置。
好了,这个时候,咱们就能够经过 bgsave 命令来执行全量快照,这既提供了数据的可靠性保证,也避免了对 Redis 的性能影响。
接下来,咱们要关注的问题就是,在对内存数据作快照时,这些数据还能“动”吗? 也就是说,这些数据还能被修改吗?这个问题很是重要,这是由于,若是数据能被修改,那就意味着 Redis 还能正常处理写操做。不然,全部写操做都得等到快照完了才能执行,性能一会儿就下降了。
快照时数据能修改吗?
在给别人拍照时,一旦对方动了,那么这张照片就拍糊了,咱们就须要重拍,因此咱们固然但愿对方保持不动。对于内存快照而言,咱们也不但愿数据“动”。
举个例子。咱们在时刻 t 给内存作快照,假设内存数据量是 4GB,磁盘的写入带宽是 0.2GB/s,简单来讲,至少须要 20s(4/0.2 = 20)才能作完。若是在时刻 t+5s 时,一个尚未被写入磁盘的内存数据 A,被修改为了 A’,那么就会破坏快照的完整性,由于 A’不是时刻 t 时的状态。所以,和拍照相似,咱们在作快照时也不但愿数据“动”,也就是不能被修改。
可是,若是快照执行期间数据不能被修改,是会有潜在问题的。对于刚刚的例子来讲,在作快照的 20s 时间里,若是这 4GB 的数据都不能被修改,Redis 就不能处理对这些数据的写操做,那无疑就会给业务服务形成巨大的影响。
你可能会想到,能够用 bgsave 避免阻塞啊。这里我就要说到一个常见的误区了,避免阻塞和正常处理写操做并非一回事。此时,主线程的确没有阻塞,能够正常接收请求,可是,为了保证快照完整性,它只能处理读操做,由于不能修改正在执行快照的数据。
为了快照而暂停写操做,确定是不能接受的。因此这个时候,Redis 就会借助操做系统提供的写时复制技术(Copy-On-Write, COW),在执行快照的同时,正常处理写操做
简单来讲,bgsave 子进程是由主线程 fork 生成的,能够共享主线程的全部内存数据。bgsave 子进程运行后,开始读取主线程的内存数据,并把它们写入 RDB 文件。
此时,若是主线程对这些数据也都是读操做(例如图中的键值对 A),那么,主线程和 bgsave 子进程相互不影响。可是,若是主线程要修改一块数据(例如图中的键值对 C),那么,这块数据就会被复制一份,生成该数据的副本。而后,bgsave 子进程会把这个副本数据写入 RDB 文件,而在这个过程当中,主线程仍然能够直接修改原来的数据。
这既保证了快照的完整性,也容许主线程同时对数据进行修改,避免了对正常业务的影响。
到这里,咱们就解决了对“哪些数据作快照”以及“作快照时数据可否修改”这两大问题:Redis 会使用 bgsave 对当前内存中的全部数据作快照,这个操做是子进程在后台完成的,这就容许主线程同时能够修改数据。
如今,咱们再来看另外一个问题:多久作一次快照?咱们在拍照的时候,还有项技术叫“连拍”,能够记录人或物连续多个瞬间的状态。那么,快照也适合“连拍”吗?
能够每秒作一次快照吗?
对于快照来讲,所谓“连拍”就是指连续地作快照。这样一来,快照的间隔时间变得很短,即便某一时刻发生宕机了,由于上一时刻快照刚执行,丢失的数据也不会太多。可是,这其中的快照间隔时间就很关键了。
以下图所示,咱们先在 T0 时刻作了一次快照,而后又在 T0+t 时刻作了一次快照,在这期间,数据块 5 和 9 被修改了。若是在 t 这段时间内,机器宕机了,那么,只能按照 T0 时刻的快照进行恢复。此时,数据块 5 和 9 的修改值由于没有快照记录,就没法恢复了。
因此,要想尽量恢复数据,t 值就要尽量小,t 越小,就越像“连拍”。那么,t 值能够小到什么程度呢,好比说是否是能够每秒作一次快照?毕竟,每次快照都是由 bgsave 子进程在后台执行,也不会阻塞主线程。
这种想法实际上是错误的。虽然 bgsave 执行时不阻塞主线程,可是,若是频繁地执行全量快照,也会带来两方面的开销。
一方面,频繁将全量数据写入磁盘,会给磁盘带来很大压力,多个快照竞争有限的磁盘带宽,前一个快照尚未作完,后一个又开始作了,容易形成恶性循环。
另外一方面,bgsave 子进程须要经过 fork 操做从主线程建立出来。虽然,子进程在建立后不会再阻塞主线程,可是,fork 这个建立过程自己会阻塞主线程,并且主线程的内存越大,阻塞时间越长。若是频繁 fork 出 bgsave 子进程,这就会频繁阻塞主线程了。那么,有什么其余好方法吗?
若是咱们对每个键值对的修改,都作个记录,那么,若是有 1 万个被修改的键值对,咱们就须要有 1 万条额外的记录。并且,有的时候,键值对很是小,好比只有 32 字节,而记录它被修改的元数据信息,可能就须要 8 字节,这样的画,为了“记住”修改,引入的额外空间开销比较大。这对于内存资源宝贵的 Redis 来讲,有些得不偿失。
到这里,你能够发现,虽然跟 AOF 相比,快照的恢复速度快,可是,快照的频率很差把握,若是频率过低,两次快照间一旦宕机,就可能有比较多的数据丢失。若是频率过高,又会产生额外开销,那么,还有什么方法既能利用 RDB 的快速恢复,又能以较小的开销作到尽可能少丢数据呢?Redis 4.0 中提出了一个混合使用 AOF 日志和内存快照的方法。
简单来讲,内存快照以必定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的全部命令操做。这样一来,快照不用很频繁地执行,这就避免了频繁 fork 对主线程的影响。并且,AOF 日志也只用记录两次快照间的操做,也就是说,不须要记录全部操做了,所以,就不会出现文件过大的状况了,也能够避免重写开销。以下图所示,T1 和 T2 时刻的修改,用 AOF 日志记录,等到第二次作全量快照时,就能够清空 AOF 日志,由于此时的修改都已经记录到快照中了,恢复时就再也不用日志了。
这个方法既能享受到 RDB 文件快速恢复的好处,又能享受到 AOF 只记录操做命令的简单优点,很有点“鱼和熊掌能够兼得”的感受,建议你在实践中用起来。