关于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 RunTime 运行时

    1.运行时的使用 向分类中添加属性 // 包含运行时头文件 #import <objc/runtime.h> /* void objc_setAssociatedObject(id obj ...

  2. iOS - OC NSLocale 本地化信息

    前言 @interface NSLocale : NSObject <NSCopying, NSSecureCoding> NSLocale 类返回本地化信息,主要体现在"语言& ...

  3. js optiontransferselect

    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/ ...

  4. http协议简述

    HTTP协议 客户端连上web 服务器后,若想获得 web 服务器中的某个 web 资源,需遵守一定的通讯格式, HTTP 协议用于定义客户端与 web 服务器通迅的格式. WEB浏览器与 WEB 服 ...

  5. 联想VIBE UI 固件ROM刷机包集合

    固件下载_联想乐问吧http://ask.lenovomobile.com/?c-157.html 联想VIBE UI 固件ROM刷机包集合 悬赏分:0     解决时间:2014/09/12 15: ...

  6. 【转】 C++ vector用法

    在c++中,vector是一个十分有用的容器,下面对这个容器做一下总结. 1 基本操作 (1)头文件#include<vector>. (2)创建vector对象,vector<in ...

  7. UIStepper

    @在IOS5中增加了一个UIStepper的新控件,UIStepper可以连续增加或减少一个数值.控件的外观是两个水平并排的按钮构成,一个显示为“+”,一个显示为“-”. 1. 初始化控件 UISte ...

  8. mialx配置qq邮箱发送邮件

    #send mail use mailx(v12.0.4)#edit configure file set smtp-use-starttlsset from=xxxxxxxxx@qq.comset ...

  9. node服务器获取form表单

    搭建好服务器后 (前言,本文只是介绍form表单直接提供给 本页面数据,即在不刷新页面的前提下更改数据) 在public里面添加index.html <!DOCTYPE html> < ...

  10. 《javascript高级程序设计》第三章 Language Basics

    3.1 语法syntax 3.1.1 区分大小写case-sensitivity 3.1.2 标识符identifiers 3.1.3 注释comments 3.1.4 严格模式strict mode ...