关于Redis我的一部分工作是阅读博客,论坛以及twitter时间线(time line)。对于开发者来说,能够了解用户社区,非用户社区如果理解他正在开发的产品是非常重要的。据我所知,持久化特性是最易产生误解的Redis特性。

这篇博客中,我会尽力客观公正,不为Redis做宣传,不忽略可能让Redis出丑的诸多细节。我所期望的仅是说明Redis持久化机制,可靠性,以及和其他数据库系统相比较的优势。

操作系统与磁盘

首先需要考虑的是数据库的耐用性(durability)。为此,我们看看一个简单的写操作实现过程:

1. 客户端发送一个写入命令到数据库(数据在客户端内存中)

2. 数据库收到这个写入命令(数据在数据库内存中)

3. 数据库调用系统调用将数据写到磁盘上(数据在内核的buffer中)

4. 操作系统将写Buffer转交到磁盘控制器(数据在磁盘cache中)

5. 磁盘控制器实际将数据写入物理媒质(磁盘或者闪存)

注:上面仅是一个简化过程,实际上存在更多级的缓存。

实际数据库实现中,步骤2常常是一个复杂的缓存系统,有时写入是由不同线程或者进程处理的。然后数据库最终会将数据写入磁盘,以我们的角度看,这一点是有意义的地方。那就是,数据将从内存传送给内核(步骤3) - 或许理解为从用户空间到内核空间更合适一点。

另一个容易忽略细节的步骤3。更复杂的是大多数内核实现了不同层次的缓存,即文件系统级缓存(如linux中的页缓存)和一个更小的包含等待提交给磁盘的数据的buffer缓存。使用特定API可以跳过这两个缓存(如Linux的open系统调用中的O_DIRECT和O_SYNC标志),但以我们的角度看,我们可以将之视为一个不透明的缓存层(我们不知道细节)。当数据库实现了缓存,那么去使能page缓存以避免数据库和内核同时进行缓存就足够了。buffer缓存通常要打开,否则每次文件写入都要提交给磁盘,对于大多数应用来说这太慢了。

数据库通常要做的是调用系统调用,系统调用提交buffer缓存到磁盘。

什么时候我们的写操作是安全的

如果我们仅仅考虑数据库软件错误(进程被杀死或者崩溃)而不触及内核,那么写操作在成功执行完步骤3就可以认为是安全的。即write系统调用(或者其他系统调用)成功返回后。执行完这一步,即使数据库软件崩溃,内核也会负责将数据写入到磁盘控制器。

如果我们继续考虑更严重的事件,如断电,那么只有在执行完步骤5才是安全的,即当数据已经实际写入到物理设备。

我们可以认为最重要的步骤就是步骤3,4,5. 即:

数据库软件从用户空间传输数据到内核空间的频率

内核将buffer中数据写到磁盘控制器的频率

最后是磁盘控制器将数据写入物理设备的频率

注:

当我们讨论磁盘控制器时,我们实际是指磁盘控制器或者磁盘自己的缓存。在耐用性比较重要的场景,系统管理员通常去使能这一层的缓存。

对大多数系统来说,磁盘控制器默认只执行write through(只缓存读操作)。只有在有电池或者超级电容器进行断电保护的时候,激活write back模式(缓存写操作)才是安全的。

POSIX API

从数据库开发者的角度来看,我们感兴趣的是数据实际写入物理设备的过程,但最关注的是API在写入过程中所能提供的控制。

我们从步骤3开始,我们能够使用write系统调用将数据传递到内核buffer,所以我们的角度看,我们使用POSIX API尽可能的控制了数据的写入。然而,我们不能控制这个系统调用在成功返回之前所耗费的时间。内核buffer大小有限如果。如果磁盘不能应对应用程序的写入带宽要求,内核写buffer将会被耗尽,内核将会阻塞写入。当磁盘可以接收更多数据时,write系统调用才会返回。最终要实现的目标是将数据写入物理设备。

步骤4:在这一步,内核将数据传递到磁盘控制器。默认情况下,内核会尽量减少传递数据的频率,因为传递大数据块会更高效。例如,Linux默认会在write调用后30秒钟将数据提交到磁盘控制器。这意味着,如果在此期间出现失败,所有最近30秒写入的数据可能丢失。

POSIX API提供了一组系统调用强制内核将buffer中数据写入到磁盘:最有名的可能就是fsync系统调用(也可参考msync和fdatasync)。fsync为数据库系统提供了一种强制内核将数据写入磁盘的方法,但是你能想象的到,这代价不菲。只要内核buffer中有数据,每次调用fsync发起一次些操作。fsync将会阻塞调用进程,直至所有写入操作完成,在Linux系统中,如果耗时过长,其他对同一文件进行写入的线程也会被阻塞。

我们不能控制的

到目前为止,我们可以控制步骤3和4,那么步骤5呢?正式的回应是,我们不能使用POSIX API控制这一步。或许,某些内核实现将尽力告知驱动器将数据提交到物理设备,但控制器也可能为了优化写入小了对写入操作重新排序,不会立即将数据写入磁盘而是再等待几毫秒。这时我们无能为力。

在文章后面部分,我们将我们的应用场景简化为两个数据安全级别

使用write系统调用的写入的数据对进程失败是安全的

使用fsync系统调用的是对系统失败(如断电)安全的。实际上,我们知道犹豫控制器缓存我们不能保证这一点,但由于所有数据库系统都有这种问题,所以我们不考虑。此外系统管理员也常常使用特定工具以控制物理设备的行为。

注:不是所有数据库都使用POSIX API。一些私有数据库使用内核模块对硬件进行更多直接的控制。但是问题的主要特征保持不变。你可以使用用户空间buffer,内核buffer,并最终会将数据写入到磁盘以保证数据安全(是一个慢操作)。一个典型的使用内核模块的数据库是Oracle。

数据损坏:

在前一节,我们分析了系统高层(应用程序和内核)将数据写入到磁盘保障数据安全的问题。然而这仅仅是数据耐用性的一面。另外一点是:数据库(包括系统)失败后,数据库是否可读,或者其内部结构是否损坏导致数据不能正确读取,或者需要恢复工具重建数据。

例如,许多SQL和NoSQL数据库实现了某种形式的树数据结构存储数据和索引。这一数据结构将会在写操作时被修改。如果系统在写操作过程中停止工作,这一树数据结构是否仍旧正确。

一般来说,对数据损坏有三种层次的安全性:

数据库写入数据时不关心失败,让用户使用数据备份进行数据恢复,或者提供工具尽力重建数据。

数据库系统使用log操作以便在失败场景时能够重新恢复数据

数据库不修改已经写入的数据,而是仅以追加模式工作,所以不会发生数据损坏。

现在,我们了解所有评估数据库系统持久化层可靠性的因素。我们可以看看Redis的表现如何。Redis提供了两种不同的持久化选择,下面依次说明。

快照

Redis快照是最简单的持久化模式。它在某些条件满足时在某个时间点生成快照,如前一次快照2分钟后且至少有100个新的写入操作。这些条件可以通过用户配置文件实现,可以在不重启服务器的条件下修改。快照是一个单个.rdb文件,包含整个数据集。

快照的耐用性只能限定在用户指定的存储点。如果数据集每15分钟后存一次,那么在Redis实例崩溃或者更严重事件发生时,那么15分钟的写入将会丢失。从Redis事务的角度看,快照能够保证MULTI/EXEC事务可以完全写入快照,或者不写入。

RDB文件不会损坏,因为它是由一个子进程以追加的方式生成。新的rdb快照是一个临时文件,生成成功后,使用原子系统调用rename将其修改为最终文件。

Redis快照不能提供好的耐用性保证,因为在两次快照中间的可能多达几分钟的数据丢失是不可接受的,它只对不关注丢失最新数据的应用和场景适用。

然而,即使是使用另外一种更高级持久化模式-AOF时,仍旧建议打开快照功能,因为它提供的一个包含完整数据集的文件在进行数据备份时,将数据发送给其他数据中心进行灾难恢复,或者在重大软件错误严重损坏数据时进行数据回滚益处多多。

需要注意的是,Redis快照Redis使用快照实现主从同步。

仅追加文件(AOF)

仅追加文件或者简称为AOF,是主要的Redis持久化选项。它的工作方式很简单:内存中每一次修改数据集的写操作都会被写入日志。日志的格式和客户端同Redis之间的通信格式相同。因此AOF能够使用netcat经管道传递给另外一个Redis实例,或者说需要时易于解析。Redis重启时,通过重放所有操作重建数据集。

为了说明AOF如何实际工作我们做一个简单的严重,启动一个新的Redis 2.6实例并激活AOF模式:

./redis-server --appendonly yes

现在发送一个写命令给这个Redis实例。

redis 127.0.0.1:6379> set key1 Hello

OK

redis 127.0.0.1:6379> append key1 " World!"

(integer) 12

redis 127.0.0.1:6379> del key1

(integer) 1

redis 127.0.0.1:6379> del non_existing_key

(integer) 0

前三个操作实际修改了数据集,第四个没有修改,它只是试图删除一个不存在的key。下面是AOF文件的内容

$ cat appendonly.aof 

*2

$6

SELECT

$1

0

*3

$3

set

$4

key1

$5

Hello

*3

$6

append

$4

key1

$7

 World!

*2

$3

del

$4

key1

可以看到,最后一个DEL不存在,因为它没有对数据集进行任何修改。

简而言之,只有实际修改数据集的命令才会记入AOF文件。

Redis AOF仅仅时做追加操作,不会有数据损坏。但是问题是,AOF文件会不断增加,及时数据集已经被删除为空,当文件很大时怎么办呢?

Redis persistence demystified - part 1的更多相关文章

  1. Redis persistence demystified

    https://redis.io/topics/persistence http://oldblog.antirez.com/post/redis-persistence-demystified.ht ...

  2. Redis persistence demystified - part 2

    重写AOF 当AOF文件太大时,Redis将在临时文件重新写入新的内容.重写不会读取旧的AOF文件,而是直接访问内存中数据,以便让新产生的AOF文件最小,重写过程不需要读取磁盘. 重写完成后,Redi ...

  3. redis 持久化与备份策略 【转载】

    本文转载自 http://blog.csdn.net/is_zhoufeng/article/details/10210353 持久化(persistence) 本文是 Redis 持久化文档 的中文 ...

  4. redis 持久化与备份策略

    持久化(persistence) 本文是 Redis 持久化文档 的中文翻译. 这篇文章提供了 Redis 持久化的技术性描述,推荐所有 Redis 用户阅读. 要更广泛地了解 Redis 持久化,以 ...

  5. Redis 持久化深入--机制、可靠性及比较

    本文是对 antirez 博客中 Redis persistence demystified 的翻译和总结.主要从Redis的持久化机制,提供何种程度的可靠性以及与其他数据库的比较三个方面进行讨论. ...

  6. 详解redis持久化

    我们的Redis必须使用数据持久化吗?如果我们的Redis服务器只作为缓存使用,Redis中存储的所有数据都是从其他地方同步过来的备份,那么就没必要开启数据持久化的选项.Redis提供了将数据定期自动 ...

  7. Redis(7)——持久化【一文了解】

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

  8. 转载:解密Redis持久化

    本文内容来源于Redis作者博文,Redis作者说,他看到的所有针对Redis的讨论中,对Redis持久化的误解是最大的,于是他写了一篇长文来对Redis的持久化进行了系统性的论述.文章非常长,也很值 ...

  9. 【Redis】Redis 持久化之 RDB 与 AOF 详解

    一.Redis 持久化 我们知道Redis的数据是全部存储在内存中的,如果机器突然GG,那么数据就会全部丢失,因此需要有持久化机制来保证数据不会一位宕机而丢失.Redis 为我们提供了两种持久化方案, ...

随机推荐

  1. iOS - OC 语言新特性

    前言 相对于 Java,OC 语言是一门古老的语言了,而它又是一门不断发展完善的语言.一些新的编译特性,为 OC 语言带来了许多新的活力.在 Xcode7 中,iOS9 的 SDK 已经全面兼容了 O ...

  2. 09 高效的PL/SQL程序设计

    程序包 Package 断开了依赖链 实验依赖关系: <1> 首先不使用包 -- 创建表 CREATE table t (x int); -- 创建视图 create view v as ...

  3. 转:程序员最值得关注的10个C开源项目

    程序员最值得关注的10个C开源项目 1. Webbench Webbench 是一个在 linux 下使用的非常简单的网站压测工具.它使用 fork ()模拟多个客户端同时访问我们设定的 URL,测试 ...

  4. 转:copy initialization

    转自: http://en.cppreference.com/w/cpp/language/copy_initialization copy initialization   C++   C++ la ...

  5. [转]Oracle中INITRANS和MAXTRANS参数

    每个块都有一个块首部.这个块首部中有一个事务表.事务表中会建立一些条目来描述哪些事务将块上的哪些行/元素锁定.这个事务表的初始大小由对象的INITRANS 设置指定.对于表,这个值默认为2(索引的IN ...

  6. C#_观察者模式

    假设有一个软件公司,每当有新产品推出,就把信息通知到一些客户. 把通知这个动作抽象成一个接口. public interface IService { void Notif(); } 客户如果想获得通 ...

  7. XML Attributes(XML属性)

    XML Attributes(XML属性) android:autoLink  是否自动链接网址或邮箱地址: android:autoText  自动检测错误: android:bufferType  ...

  8. aspcms标签使用经验

    1.调用导航栏标签写法,sort是网站后台栏目管理的编号{aspcms:type sort=19} <a href="[type:link]" title="[ty ...

  9. 例题:打印乘法口诀。可能大家一看有点难,但只要理解for 循环嵌套,两层循环,外层循环行数,里层循环列数,搞清楚行数和列数之间的关系,就可以轻松做出这道题

    namespace 打印乘法口诀{    class Program    {        static void Main(string[] args)        {            f ...

  10. selenium+python笔记11

    #!/usr/bin/env python # -*- coding: utf-8 -*- """ @desc: search in mail box "&qu ...