首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

您应该了解的 redis 数据持久化的过程-PART1持久化相关因素

原文 http://oldblog.antirez.com/post/redis-persistence-demystified.html

在这篇文章中,我公平公正的描述Redis和其他数据库在数据持久化方面的内容,不会偏袒Redis。尽量以简单,容易理解的文字描述Redis的数据持久化是如何工作的,还有他的可靠性如何实现的,和其他数据库产品的区别。

首先说说数据库在持久性方面如果操作的,可以将写操作抽象为如下几个步骤:

1:客户端发送命令给数据库(数据存在于客户端的内存)

2:数据库服务端接收到写操作(数据存在于数据库服务端程序的内存)

3:数据库调用系统API提供的write函数(数据提交到系统内核buffer缓冲区)

4:操作系统将系统内核的数据提交到磁盘控制器(数据进入磁盘cache)

5:磁盘控制器将cache中的数据真正写到物理介质

备注:这是已经非常抽象的步骤了,因为还有许多层cache和buffer。

关于步骤2省略好多内容:这个步骤通常数据库都会实现一个复杂的缓存系统,有时写操作是被其他进程或者线程处理的,但是对我们最重要的是,最终数据库会调用系统API将数据写入到磁盘,也就是执行步骤3将数据被提交到操作系统内核的buffer。

关于步骤3也省略好多信息:操作系统的实现非常复杂,内核会有多层cache系统,常见的由文件系统的cache(Linux中称之为page cache)和 buffer,这里边存放着修改过的数据即将提交磁盘控制器,这层控制器可以通过系统API提供的打开文件的open函数中传入参数O_DIRECT和 O_SYNC 绕过文件系统的缓存。我们可以把文件系统层抽象为一个黑盒子不去想里边如何实现的,因为我也确实不知道里边如何实现的。如果数据库系统已经实现了这个步骤中说的文件系统层面的缓存,就可以禁用文件系统的缓存,防止内核和操作系统同时做同样的事情耗时。尽量不要禁用文件系统的缓存,因为每次操作都直接写入写磁盘会导致应用程序执行很慢。

数据库通常时调用系统提供的API提交数据更改到磁盘控制器,除非我们在后文提到的特一些殊情况。

假如只是数据库程序层面的失败,(比如:crash或者被kill) 并没有内核层面的问题,就可以认为步骤3及后边的步骤不会出问题,数据可以认为是安全的。也就是说只要write(2)函数(或者相同功能的函数)返回成功,就算数据库程序cresh,也可以认为数据是安全的,因为内核会谨慎的提交数据到磁盘控制器。

假如是更坏的情况比如掉电,我们只有到步骤5才能算安全,就是说当数据真正被传输到物理磁盘介质才算安全。

我们可以总结出总重要的步骤是步骤3,步骤4,步骤5.

引出如下几个问题:

数据库软件以什么频率将用户态的数据通过write或者类似的函数提交到内核。

内核以什么频率将内核的buffer刷新到磁盘控制器

磁盘控制器以什么频率刷新数据到磁盘介质

备注:我们这里其实是把disk conttroller的工作简化为磁盘缓存,他还有很多功能,编码,压缩等, 在对数据持久化要求非常高的场景可以禁用磁盘的cache,参考http://ubuntuhandbook.org/index.php/2014/01/disable-disk-caching-prevent-data-loss/

磁盘控制器默认值缓存reads,大部分的write都会直接穿透cache.如果有更好的断电时磁盘数据保护机制,就可以打开磁盘的caheing of write

POSIX API

对于数据库开发者来看,在数据真正写入到磁盘介质之前的操作步骤都是有很重要的,而且POSIX API提供了各个操作的API控制这些操作。

步骤3.我们可以调用系统函数,’write‘,提交数据到内核buffer,这个地方我们是可以通过POSIXA PI控制的,但是我们不能控制系统调用到放回成功之间花费的时间。内核的Buffer是否大小限制的,如果磁盘控制器(内核buffer的消费者)的处理速度不能匹配应用程序的数据带宽,内核的buffer就有可能达到它的大小限制,阻塞我们的write操作,直到磁盘处理完这些数据,write才会返回。最终目的是保证数据写入磁盘介质。

步骤4:这个步骤内核提交数据到磁盘控制器。默认要避免频繁做这个操作,因为把碎片累积成大块更有利于效率。比如Linux是每30秒提交一次。这就是说这不出现问题,有可能最近30秒的修改会丢失。

POSIX API提供了一组系统调用强制内核将buffer中数据写入到磁盘:最棒的可能就是fsync系统调用(也可参见 msync和fdatasync)。使用fsync,数据库系统强制将内核将数据提交到到磁盘控制器(步骤4),但是结果你也能想到,这成本很高。只要内核buffer中有数据,就要fsync发起一次写操作。fsync将会阻塞调用进程,直至所有写入操作完成,不止这些,在Linux系统中,其他对同一文件进行写入的线程也会被阻塞。

What we can't control

目前为止我们能控制步骤3、4,但是不能控制步骤5.或者换个说法我们不能通过POSIX API控制步骤5.有可能有些内核可以实现通知磁盘控制器将数据写入磁盘介质,但是磁盘控制器为了性能考虑通常并不会如此做,而是等上2毫秒或更长,这些已经远超出我们的控制。

如此我们就可以抽象出2个层面的的数据安全场景:

通过write(2)系统调用数据提交到内核buffer,在我们的应用进程失败时,数据是安全的的。

通过fsync(2)系统调用数据提交到磁盘控制器,也可以近似认为就算系统掉电导致操作系统崩溃数据也是安全。严格讲因为磁盘控制的缓存行为的存在我们不能完全保证数据安全,但是我们可以忽略,因为这个问题几乎是所有的数据库系统都存在的问题。而且SA也会尝试使用一些特殊工具控制磁盘的行为。

备注:并不是所有的数据库软件都是用POSIX API。一些商业软件可能会使用一些内核模块增加对硬件的控制。但是核心问题模型没变,都是通过控制用户态数据,内核Buffer,提交数据到磁盘控制器来达到安全的目的。典型的例子就是Oracle

前边的章节我们介绍了如何保证写入的数据真正的持久化到磁盘:应用程序层面和内核层面。但数据安全不仅包含持久化,还有另一个问题,灾难恢复时,数据库是否可读,数据内部结构有可能被破坏导致数据不可读,或者需要一个数据重建步骤重建正确的数据实例,就是如何避免数据损坏。

对于大部分SQL和NoSQL数据库实现,都会使用tree结构用户组织数据存储和索引。这个结构在写操作时需要分裂和合并,如果写的过程中出现问题,是否会导致这种结构出问题?

数据库软件写数据时不关心失败的问题,需要用户使用副本或者其他工具重建实例,当然有可能重建失败。

数据库系统通过日志(比如journal),所以失败后就可以基于日志重建数据实例。

数据库系统从来修改已有的生成数据,只通过追加方式,所以就不会有既有数据的损坏。

明天继续分析Redis的持久化是否可靠。

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180213A0GFOU00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券