Redis持久化方案

来源:互联网 发布:java身份证正则表达式 编辑:程序博客网 时间:2024/05/16 11:07

简介 redis 持久化 RDB、AOF

redis 提供两种持久化方式:RDB 和 AOF。redis 允许两者结合,也允许两者同时关闭。

  • RDB 可以定时备份内存中的数据集。服务器启动的时候,可以从 RDB 文件中回复数据集。

  • AOF 可以记录服务器的所有写操作。在服务器重新启动的时候,会把所有的写操作重新执行一遍,从而实现数据备份。当写操作集过大(比原有的数据集还大),redis 会重写写操作集。

本篇主要讲的是 RDB 持久化,了解 RDB 的数据保存结构和运作机制。redis 主要在 rdb.h 和 rdb.c 两个文件中实现 RDB 的操作。

数据结构 rio

持久化的 IO 操作在 rio.h 和 rio.c 中实现,核心数据结构是 struct rio。RDB 中的几乎每一个函数都带有 rio 参数。struct rio 既适用于文件,又适用于内存缓存,从 struct rio 的实现可见一斑。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
struct _rio {
    // 函数指针,包括读操作,写操作和文件指针移动操作
    /* Backend functions.
     * Since this functions do not tolerate short writes or reads the return
     * value is simplified to: zero on error, non zero on complete success. */
    size_t (*read)(struct _rio *, void *buf, size_t len);
    size_t (*write)(struct _rio *, const void *buf, size_t len);
    off_t (*tell)(struct _rio *);
      
    // 校验和计算函数
    /* The update_cksum method if not NULL is used to compute the checksum of
     * all the data that was read or written so far. The method should be
     * designed so that can be called with the current checksum, and the buf
     * and len fields pointing to the new block of data to add to the checksum
     * computation. */
    void (*update_cksum)(struct _rio *, const void *buf, size_tlen);
      
    // 校验和
    /* The current checksum */
    uint64_t cksum;
      
    // 已经读取或者写入的字符数
    /* number of bytes read or written */
    size_t processed_bytes;
      
    // 每次最多能处理的字符数
    /* maximum single read or write chunk size */
    size_t max_processing_chunk;
      
    // 可以是一个内存总的字符串,也可以是一个文件描述符
    /* Backend-specific vars. */
    union {
        struct {
            sds ptr;
            // 偏移量
            off_t pos;
        } buffer;
        struct {
            FILE *fp;
            // 偏移量
            off_t buffered; /* Bytes written since last fsync. */
            off_t autosync; /* fsync after 'autosync' bytes written. */
        } file;
    } io;
};
      
typedef struct _rio rio;

redis 定义两个 struct rio,分别是 rioFileIO 和 rioBufferIO,前者用于内存缓存,后者用于文件 IO:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 适用于内存缓存
static const rio rioBufferIO = {
    rioBufferRead,
    rioBufferWrite,
    rioBufferTell,
    NULL,           /* update_checksum */
    0,              /* current checksum */
    0,              /* bytes read or written */
    0,              /* read/write chunk size */
    { { NULL, 0 } } /* union for io-specific vars */
};
      
// 适用于文件 IO
static const rio rioFileIO = {
    rioFileRead,
    rioFileWrite,
    rioFileTell,
    NULL,           /* update_checksum */
    0,              /* current checksum */
    0,              /* bytes read or written */
    0,              /* read/write chunk size */
    { { NULL, 0 } } /* union for io-specific vars */
};

RDB 持久化的运作机制

rdb_persistence

redis 支持两种方式进行 RDB:当前进程执行和后台执行(BGSAVE)。RDB BGSAVE 策略是 fork 出一个子进程,把内存中的数据集整个 dump 到硬盘上。两个场景举例:

  1. redis 服务器初始化过程中,设定了定时事件,每隔一段时间就会触发持久化操作;进入定时事件处理程序中,就会 fork 产生子进程执行持久化操作。

  2. redis 服务器预设了 save 指令,客户端可要求服务器进程中断服务,执行持久化操作。

这里主要展开的内容是 RDB 持久化操作的写文件过程,读过程和写过程相反。子进程的产生发生在 rdbSaveBackground() 中,真正的 RDB 持久化操作是在 rdbSave(),想要直接进行 RDB 持久化,调用 rdbSave() 即可。

以下主要以代码的方式来展开 RDB 的运作机制:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
// 备份主程序
/* Save the DB on disk. Return REDIS_ERR on error, REDIS_OK on success */
int rdbSave(char *filename) {
    dictIterator *di = NULL;
    dictEntry *de;
    char tmpfile[256];
    char magic[10];
    int j;
    long long now = mstime();
    FILE *fp;
    rio rdb;
    uint64_t cksum;
      
    // 打开文件,准备写
    snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid());
    fp = fopen(tmpfile,"w");
    if (!fp) {
        redisLog(REDIS_WARNING, "Failed opening .rdb for saving: %s",
            strerror(errno));
        return REDIS_ERR;
    }
      
    // 初始化 rdb 结构体。rdb 结构体内指定了读写文件的函数,已写/读字符统计等数据
    rioInitWithFile(&rdb,fp);
      
    if (server.rdb_checksum) // 校验和
        rdb.update_cksum = rioGenericUpdateChecksum;
      
    // 先写入版本号
    snprintf(magic,sizeof(magic),"REDIS%04d",REDIS_RDB_VERSION);
    if (rdbWriteRaw(&rdb,magic,9) == -1) goto werr;
      
    for (j = 0; j < server.dbnum; j++) {
        // server 中保存的数据
        redisDb *db = server.db+j;
      
        // 字典
        dict *d = db->dict;
        if (dictSize(d) == 0) continue;
      
        // 字典迭代器
        di = dictGetSafeIterator(d);
        if (!di) {
            fclose(fp);
            return REDIS_ERR;
        }
      
        // 写入 RDB 操作码
        /* Write the SELECT DB opcode */
        if (rdbSaveType(&rdb,REDIS_RDB_OPCODE_SELECTDB) == -1) gotowerr;
      
        // 写入数据库序号
        if (rdbSaveLen(&rdb,j) == -1) goto werr;
      
        // 写入数据库中每一个数据项
        /* Iterate this DB writing every entry */
        while((de = dictNext(di)) != NULL) {
            sds keystr = dictGetKey(de);
            robj key,
                *o = dictGetVal(de);
            long long expire;
      
            // 将 keystr 封装在 robj 里
            initStaticStringObject(key,keystr);
      
            // 获取过期时间
            expire = getExpire(db,&key);
      
            // 开始写入磁盘
            if (rdbSaveKeyValuePair(&rdb,&key,o,expire,now) == -1)goto werr;
        }
        dictReleaseIterator(di);
    }
    di = NULL; /* So that we don't release it again on error. */
      
    // RDB 结束码
    /* EOF opcode */
    if (rdbSaveType(&rdb,REDIS_RDB_OPCODE_EOF) == -1) goto werr;
      
    // 校验和
    /* CRC64 checksum. It will be zero if checksum computation is disabled, the
     * loading code skips the check in this case. */
    cksum = rdb.cksum;
    memrev64ifbe(&cksum);
    rioWrite(&rdb,&cksum,8);
      
    // 同步到磁盘
    /* Make sure data will not remain on the OS's output buffers */
    fflush(fp);
    fsync(fileno(fp));
    fclose(fp);
      
    // 修改临时文件名为指定文件名
    /* Use RENAME to make sure the DB file is changed atomically only
     * if the generate DB file is ok. */
    if (rename(tmpfile,filename) == -1) {
        redisLog(REDIS_WARNING,"Error moving temp DB file on the final destination: %s"strerror(errno));
        unlink(tmpfile);
        return REDIS_ERR;
    }
    redisLog(REDIS_NOTICE,"DB saved on disk");
    server.dirty = 0;
      
    // 记录成功执行保存的时间
    server.lastsave = time(NULL);
      
    // 记录执行的结果状态为成功
    server.lastbgsave_status = REDIS_OK;
    return REDIS_OK;
      
werr:
    // 清理工作,关闭文件描述符等
    fclose(fp);
    unlink(tmpfile);
    redisLog(REDIS_WARNING,"Write error saving DB on disk: %s",strerror(errno));
    if (di) dictReleaseIterator(di);
    return REDIS_ERR;
}
      
// bgsaveCommand(),serverCron(),syncCommand(),updateSlavesWaitingBgsave() 会调用 rdbSaveBackground()
int rdbSaveBackground(char *filename) {
    pid_t childpid;
    long long start;
      
    // 已经有后台程序了,拒绝再次执行
    if (server.rdb_child_pid != -1) return REDIS_ERR;
      
    server.dirty_before_bgsave = server.dirty;
      
    // 记录这次尝试执行持久化操作的时间
    server.lastbgsave_try = time(NULL);
      
    start = ustime();
    if ((childpid = fork()) == 0) {
        int retval;
      
        // 取消监听
        /* Child */
        closeListeningSockets(0);
        redisSetProcTitle("redis-rdb-bgsave");
      
        // 执行备份主程序
        retval = rdbSave(filename);
      
        // 脏数据,其实就是子进程所消耗的内存大小
        if (retval == REDIS_OK) {
            // 获取脏数据大小
            size_t private_dirty = zmalloc_get_private_dirty();
      
            // 记录脏数据
            if (private_dirty) {
                redisLog(REDIS_NOTICE,
                    "RDB: %zu MB of memory used by copy-on-write",
                    private_dirty/(1024*1024));
            }
        }
      
        // 退出子进程
        exitFromChild((retval == REDIS_OK) ? 0 : 1);
    else {
        /* Parent */
        // 计算 fork 消耗的时间
        server.stat_fork_time = ustime()-start;
      
        // fork 出错
        if (childpid == -1) {
            // 记录执行的结果状态为失败
            server.lastbgsave_status = REDIS_ERR;
            redisLog(REDIS_WARNING,"Can't save in background: fork: %s",
                strerror(errno));
            return REDIS_ERR;
        }
        redisLog(REDIS_NOTICE,"Background saving started by pid %d",childpid);
      
        // 记录保存的起始时间
        server.rdb_save_time_start = time(NULL);
      
        // 子进程 ID
        server.rdb_child_pid = childpid;
        updateDictResizePolicy();
        return REDIS_OK;
    }
    return REDIS_OK; /* unreached */
}

如果采用 BGSAVE 策略,且内存中的数据集很大,fork() 会因为要为子进程产生一份虚拟空间表而花费较长的时间;如果此时客户端请求数量非常大的话,会导致较多的写时拷贝操作;在 RDB 持久化操作过程中,每一个数据都会导致 write() 系统调用,CPU 资源很紧张。因此,如果在一台物理机上部署多个 redis,应该避免同时持久化操作。

那如何知道 BGSAVE 占用了多少内存?子进程在结束之前,读取了自身私有脏数据 Private_Dirty 的大小,这样做是为了让用户看到 redis 的持久化进程所占用了有多少的空间。在父进程 fork 产生子进程过后,父子进程虽然有不同的虚拟空间,但物理空间上是共存的,直至父进程或者子进程修改内存数据为止,所以脏数据 Private_Dirty 可以近似的认为是子进程,即持久化进程占用的空间。

RDB 数据的组织方式

RDB 的文件组织方式为:数据集序号1:操作码:数据1:结束码:校验和—-数据集序号2:操作码:数据2:结束码:校验和……

其中,数据的组织方式为:过期时间:数据类型:键:值,即 TVL(type,length,value)。

举两个字符串存储的例子,其他的大概都以至于的形式来组织数据:

rdb_datastruct_sample

可见,RDB 持久化的结果是一个非常紧凑的文件,几乎每一位都是有用的信息。如果对 redis RDB 数据组织方式的细则感兴趣,可以参看 rdb.h 和 rdb.c 两个文件的实现。

对于每一个键值对都会调用 rdbSaveKeyValuePair(),如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val,
                        long long expiretime, long long now)
{
    // 过期时间
    /* Save the expire time */
    if (expiretime != -1) {
        /* If this key is already expired skip it */
        if (expiretime < now) return 0;
        if (rdbSaveType(rdb,REDIS_RDB_OPCODE_EXPIRETIME_MS) == -1) return -1;
        if (rdbSaveMillisecondTime(rdb,expiretime) == -1) return-1;
    }
      
    /* Save type, key, value */
    // 数据类型
    if (rdbSaveObjectType(rdb,val) == -1) return -1;
      
    // 键
    if (rdbSaveStringObject(rdb,key) == -1) return -1;
      
    // 值
    if (rdbSaveObject(rdb,val) == -1) return -1;
    return 1;
}



Redis Persistence

本文由 latteye 翻译自 Redis 文档 Redis Persistence。此文比较详细的介绍了 Redis 的两种持久化方式,是 Linux SA 了解 Redis 的必读资料.同时此文整理在 latteye doc 页面。本人能力有限,英文水平不佳,翻译有错误的地方欢迎指出。

Redis 为持久化(latteye注:可以简单的理解为存储)提供了不同的方案:
  • RDB 持久化提供了以某个周期时间为节点,对数据快照的方案。
  • AOF 持久化方案记录了所有服务器接收到的指令。并可以在服务器启动时将指令重新加载,从而重构原始的数据库。Redis 用和它协议相同的方式来记录这些指令,同时命令的记录是不断添加的(append-only)。当 log 过于庞大时 Redis 可以在后台对其进行重写。
  • 如果你只希望数据库运行的时候保存数据,你可以关闭持久化选项。
  • AOF 和 RDB 可以在一个实例中合起来用。注意,在这种情况下,当服务器重启时,是由 AOF 来重构数据库的。因为 AOF 对数据的保障性更加可靠。
最重要的是理解 RDB 和 AOF 之间的区别,从而能权衡两者。让我们先来看 RDB 吧:

RDB 的优势

  • RDB 是一种以时间点为基准(point-in-time)且使用单文件(single-file)的数据重现方式。RDB 文件非常适合用来备份。比如你希望在最近的24小时,每小时对数据做一次快照,且最近30天,每天保留一份快照。RDB 让你非常方便的从灾难中恢复数据到任意一个时间点。
  • RDB 非常适合灾难恢复。单文件存储的方式非常适合将文件传输到远程的数据中心,或者备份在 Amazon S3 上(最好加密哦)。
  • RDB 可以让性能最大化。Redis 父进程只需要派生出一个子进程来管理即可,其父进程永远都不需要执行磁盘 I/O 相关的操作。
  • 在你的数据库文件较大时,RDB 提供比 AOF 更快的重启速度。

RDB 的劣势

  • 如果你希望在 Redis 异常退出时(比如断电了)最小化数据损失,Redis 并不是你最好的选择。你可以在部署了RDB的机器上配置不同的保存点(对于实例来说至少等待5分钟且写入数据次数超过100次,不过你可以设置多个保存点)。无论如何,你通常会每五分钟创建一个或多个快照,所有当 Redis 非正常关闭的时候你应该会丢失几分钟的数据。
  • 由于需要经常操作磁盘,RDB 会经常 fork()出一个子进程。如果你的数据库文件很大的话,Fork()占用比较多的时间,并且可能会影响 Redis 暂停服务一段时间(millisecond 级别),如果你的数据库超级大并且你的CPU比较弱,有可能是会达到一秒。AOF 同样需要 fork(),但你可以调整重写的频率来协调性能和数据的保障性。

AOF 的优势

  • 在 Redis 使用 AOF 更加持久化:你可以使用不同的 fsync 策略:完全不做 fsync,每秒 fsync,每一个请求 fsync。使用默认的每秒 fsync 模式时,性能依旧是非常好的。(主进程在后台没有 fsync 在执行的时候会尽量的做写操作),而且你只会丢失最多一秒的数据。
  • AOF 日志是一个不断添加的日志(append only),所有它不可以搜索,同时即使在机器电源故障的情况下日志也不会损坏。即使有一条命令写到一半的时候失败了,redis-check-aof 工具也可以很方便的修复日志。
  • 当日志过大的时候,Redis 可以在后台重写 AOF 日志。整个重写过程是安全的,当 Redis 创建新的准备新的 log 文件时,它依旧往旧的日志里面写入数据。同时,它会根据当前的数据库创建一个新的、最小的日志文件。当这些都完成时,Redis 将两个日志文件交换,并将些的log记录在新的文件上。
  • AOF 用一种简单的格式逐一记录了所有的操作。你甚至可以方便的导出一个 AOF 文件。如果你在一台实例上误输入了 FLUSHALL 命令,在日志文件被重写之前,你可以简单的停止你的服务器,在日志文件中删除最后的 FLUSHALL 命令,然后重启 Redis,之前被删除的数据依旧在那里。

AOF 的劣势

  • 对于等量的数据库来说 AOF 产生的文件比 RDB 要大。
  • 根据 fsync 策略的不同,AOF 有可能比 RDB 要慢。通常使用每秒 fsync 一次的性能还是比较高的。如果你关闭 fsync, AOF 的性能即使在高负载下应该和 RDB 一样高。当然,在高磁盘负载时还是 RDB 相对能提供比较稳定的延时效果。
  • 曾经有一些命令(for instance there was one involving blocking commands like BRPOPLPUSH)会导致一些罕见的 bug — AOF 在启动的时候会不重新加载原有数据。这样的 bug 很少见,我们在一套测试环境中进行了测试。我们自动创建了一些大量的随机数据,然后重新加载数据库,看看一切是否正常。不过 RDB 环境下面机会不会出现这种 bug。让我们明确的看一下:Redis AOF 通过一种不断增长式的方式来更新数据。而 RDB 在一个基础上不断的创造快照,这种方式更加强壮。但是 1) 需要注意的是,Redis 每一次重写 AOF 时,都是从一个数据文件中真实的数据重写而来。相对不断的添加数据到 AOF 文件(or one rewritten reading the old AOF instead of reading the data in memory),这种方式产生 bug 的几率更小。2)在实际应用中从来没有收到过 AOF 损坏的报告。

OK ,到底选择什么呢?

通常,如果你要和 PostgreSQL 所提供的数据保障性相比较,那么建议你同时使用两种持久化方式。
如果你非常在意你的数据,但是也还可以接受灾难带来的几分钟的数据丢失,那么你可以仅使用 RDB。
很多用户仅使用了 AOF,但是我们建议,既然 RDB 可以时不时的给数据做个完整的快照,并且提供更快的重启,而且在某些情况下 bug 也要少,你最好还是使用 RDB。
注:综合以上原因,我们希望可以在未来(长远计划)统一 AOF 和 RDB 成一种持久化模式。
下面的章节将以实例来介绍两种持久化方式的更多细节。

快照

默认情况下,Redis 在磁盘上以二进制格式保存数据快照,名为 dump.rdb。你可以配置 Redis 每 N 秒且至少有 M 次 数据修改则保存一次数据,或者你可以手动通过 SAVE 或者 BGSAVE 命令来执行保存。
比如,这个配置会让 Redis 每 60 秒且至少有 1000 次 key 修改,则保存一次数据:
save 60 1000
这种策略被成为 快照(snapshotting)

它怎么工作?

当Redis需要将数据导出到磁盘时,发生了以下事情:
  • Redis 派生出一个子进程。我们现在有一个子进程和一个父进程。
  • 子进程开始将数据写入一个临时的 RDB 文件。
  • 当子进程写完新的 RDB 文件后,用新的 RDB 文件替换旧的。
这种工作方式允许 Redis 使用到 copy-on-wirite 带来的优势。

Append-only 文件

快照的持久化非常可靠。如果你的 Redis 突然停止、你的电源突然坏了、或者你意外的 kill -9 了你的进程,最后写入 Redis 的那些数据都会丢失。对某些应用来说,这可能不是什么大问题,还有很多的方式来保障数据可靠性,在那些情况下 Redis 并不是很好的选择。
append-only 文件是另一个可以提供完全数据保障的方案。这种技术自 1.1 版本启用。
你可以在配置中打开 AOF 功能:
appendonly yes
配置以后,只要是 Redis 接收到的会修改数据库的命令就会被记录在 AOF 文件中。当你重启 Redis 之后,它会重复 AOF 中的指令从而来重现数据最新的状态。

日志重写

和你想的一样,AOF 文件会在操作过程中变得越来越大。比如,如果你做一百次加法计算,最后你只会在数据库里面得到最终的数值,但是在你的 AOF 里面会存在 100 次记录。其中 99 条记录对最终的结果是无用的。
所以 Redis 支持一个很有趣的功能:他可以在不影响服务的前提下在后台重构 AOF 文件。当你输入 BGREWRITEAOF 命令时,Redis 会在内存中按最简短的排序整理所有的指令。如果你在使用 Redis 2.2 你需要是不是的跑一下 BGREWRITEAOF 命令。而 Redis 2.4 已经支持自动进行重新工作(更多信息请查看 2.4 文档。)

Append Only 文件到底有多可靠?

你可以配置 Redis 如何将数据 fsync 到磁盘上。有以下几种选项。
  • 每当有命令请求时,fsync。非常安全,非常非常慢。
  • 每秒做一次fsync。足够快了(在 2.4 版本中几乎和快照模式一样快。),而在遇到灾难时,你可能会丢失最近1秒的数据。
  • 永远不做 fsync,仅仅只是把数据放在系统中,非常快,但是却不安全的方式
建议的(且默认的)模式是每秒同步一次。这是坚固速度和安全的方式。一有命令就记录的方式在以前是很慢的(尽管在2.0版本中有所提升) - 没什么办法让 fsync 更快了。

当 AOF 文件损坏时,应该怎么做?

AOF 在服务器崩溃的时候是有可能损坏的(这永远没办法解决),Redis 无法加载已经损坏的文件。如果你遇到了这种情况,你可以通过以下的步骤来修复问题:
  • 给AOF文件做个备份。
  • 用 Redis 随身带的 redis-check-aof 命令来修复原始文件:
    $ redis-check-aof --fix "filename"
  • 用 diff -u 来比较两个文件之间的区别(非必须)
  • 用修复过的文件来重启服务器。

它如何工作的?

日志重写用了和快照一样的 copy-on-write 技术。以下是他工作的方式:
Redis 派生出一个子进程,于是我们一个父进程和一个子进程了。
  • 子进程开始在一个临时文件写入 AOF 信息。
  • 父进程将所有的新指令积蓄在内存中(在同一时刻,它也将指令记录在了老 AOF 文件中,这样即使重写过程失败了,我们也是安全的。)
  • 当子进程重写完文件之后,父进程会获取到一个信号,然后将内存中积累的数据由子进程写入到文件中去。
  • 搞定!现在Redis 自动将文件名修改,以后就将数据添加到新文件中了。

如果我正在使用 RDB 快照,怎么才能切入到AOF呢?

在2.2和2.0版本中,这个操作略有不同。当然,我相信你可以猜到,2.2中的操作要简单一些,且不要求重启。

Redis >= 2.2

  • 给你最新的 dump.rdb 文件做一个备份。
  • 将这个备份文件保存到一个安全的地方。
  • 输入下面两个命令:
  • redis-cli config set appendonly yes
  • redis-cli config set save ""
  • 确认一下你的数据库 key 的数量没有变化。
  • 确认一下新的记录已经记录到了正确的文件。
第一个 CONFIG 命令启用了 Append Only 文件。Redis 会按照顺序先先初始化一个文件,接着打开这个文件给 AOF 写入,然后会将所有的请求写入 AOF 文件。
第二个 CONFIG 命令 是用来关闭快照持久化模式。这不是必须的,如果你想要,你可以同时使用两种持久化模式。
重要:记得在你的 redis.conf 文件中开启 AOF,不然的话一旦你重启服务器,服务器会按照以前的配置工作了。

Redis 2.0

  • 备份你最新的 dump.rdb 文件。
  • 将备份文件放到安全的地方。
  • 停止一切数据库写入操作。
  • 执行 redis-cli bgrewriteaof 命令。这将创建一个 append only 文件.
  • 当 Redis 完成创建 AOF dump 文件之后停止服务器。
  • 编辑 redis.conf 文件以开启 AOF 持久化选项。
  • 重启服务器。
  • 确认你的数据没有少。
  • 确认所有请求已经记录到了正确的 AOF 文件。

整合 RDB 和 AOF 两种持久化模式

Redis >= 2.4 确保了在 RDB 做快照的过程中不会触发一个 AOF 重写动作,反之亦然。这避免了两种持久化同时触发高I/O的工作。
当快照正在处理时,如果用户手动执行了 BGREWRITEAOF 命令,服务器会立刻返回一个 OK。这表示重写动作已经在排队,当快照完成之后就会执行 AOF 重写。
如果你同时启用了 AOF 和 RDB,在 Redis 重启的时候,系统会使用 AOF 文件来重构数据库。这是因为 AOF 相对更加可靠。

备份Redis数据

在开始本章之前,请先读读下面这句话:一定要备份你的数据库。磁盘损坏,实例莫名其妙的消失,这相当于:将你大量的数据都写入到 /dev/null。
Redis 是一款非常易于备份的数据库 -- 你可以在 Redis 运行的时候拷贝 RDB 文件。RDB 文件绝对不会在拷贝的时候被修改,在拷贝的过程中,他会使用一个临时的文件名。只有在快照完全结束时,才会用 rename(2) 将其改为最终的名字。(and while it gets produced it uses a temporary name and is renamed into its final destination atomically using rename(2) only when the new snapshot is complete.)
这表示在服务器运行的时候拷贝RDB文件是一件绝对安全的事情。以下是我们的建议:
  • 用 crontab 每小时为数据库创建一次快照,另在别处保留每日快照。
  • 每次 crontab 执行的时候,用 find 命令寻找老的快照并删除。保留最近48小时的每小时快照,保留1-2个月的每日快照。用日期以及时间信息来命名这些文件。
  • 每天至少将这些文件转移一次到别的机房,至少转移出那台物理机器。

灾难恢复

在此文中灾难恢复可以理解为和备份一样的工作,外加将备份的文件转移至其他不同的机房。这样即使在 Redis 所在的机房遇到严重的灾难时,你依旧可以使用它的快照来恢复数据。
由于大多数 Redis 的用户都处于起步阶段,估计并没有太多的钱花在灾难恢复上,我们来看看比较有趣且便宜的方案吧:
Amazon S3 和其他类似的服务非常作为您的灾难恢复系统。你只要将你的每日备份简单的放在 S3 的加密区域。你可以使用 gpg -c 来加密你的数据。并且确保将密码保存在多个安全的地方(可以分发给你公司里比较重要的人)。我们建议同时使用多个外部存储服务来保障数据安全。
使用 scp (ssh 的一部分) 将数据传输到可信的服务器。下面是个简单而且安全的方式:在距离你很远的地方弄台VPS,装上 ssh,最好是一个仅用密钥登陆且无密码的配置。这样就可以很方便的自动传输备份文件了。最好在不同的商家那里多买点 VPS。
请注意!如果你没有按照正确的方式部署,这种方式还是很容易失败的。至少在完成传输以后请用 SHA1 sum 验证文件是否正确。


本文由 latteye 翻译,文章永久地址为 http://latteye.com/2011/11/redis-persistence.html


第一部分来自:http://blogread.cn/it/article/7093?f=catetitle

0 0
原创粉丝点击