Redis学习笔记之十一:Redis数据持久化

    Redis在用作缓存服务器、队列等之前,是先作为数据库来使用的,Redis开发的初衷也是为了替代Mysql。Redis作为数据库,当然也支持将内存中的数据写到磁盘上或者从磁盘上读取数据。Redis支持写入两种格式的文件,RDB和AOF。

    1、RDB写入方式

    Redis默认是使用RDB方式进行持久化的,打开redis.conf配置文件,可以看到以下配置:

save 900 1
save 300 10
save 60 10000
    第一行表示如果 15分钟(900秒)内1个以上键被修改了就写入磁盘中。

    第二行表示如果在5分钟内10个以上的键被修改了就写入磁盘中。

    第三行表示如果在1分钟内10000个以上的键被修改了就写入磁盘中。

    ps:如果想禁用RDB方式可以将以上配置注释掉,然后加入 save ""即可。

    除了在配置文件中修改外,也可以使用指令SAVE 或 BGSAVE(background save)显示告知Redis写入磁盘。

    如果执行SAVE指令,Redis会拒绝其他请求,直到写入到RDB文件完毕,而BGSAVE指令则会在后台fork出一个子线程,让子线程来进行写入操作,然后主进程继续处理请求,不能在一个BGSAVE指令未运行完之前重复调用。

    那RDB文件保存在哪儿呢? 根据配置文件中dir和dbfilename决定路径以及文件名。默认配置如下:

# The filename where to dump the DB
dbfilename dump.rdb

# The working directory.
#
# The DB will be written inside this directory, with the filename specified
# above using the 'dbfilename' configuration directive.
#
# The Append Only File will also be created inside this directory.
#
# Note that you must specify a directory here, not a file name.
dir ./

    由于Redis并不是直接写入到配置指定的RDB文件中,而是写到另一个RDB文件,写完成后再替换掉配置指定的RDB文件,这就是一次写入过程,所以每一个RDB文件都是完整的,而且Redis在启动时会读取指定RDB文件中的数据,所以完全可以复制RDB文件进行备份。但如果Redis因为异常退出,则会丢失最后一次快照以后更改的数据。如果无法忍受使用RDB方式带来的数据损失,可以使用AOF方式。


    2、AOF方式写入磁盘

    Redis的AOF方式是默认关闭的,你可以在配置文件中加入以下配置打开:appendonly no 改为appendonly yes。那AOF写入到哪儿呢? 这由配置中的appendfilename决定,默认是appendonly.aof文件。

    那Redis什么时候写入到AOF文件呢? 可以看到在配置文件中有下列配置:


    可以看到appendfsync有三个值:

    always表示每执行一个SET等修改key的指令都会写入带AOF文件中,并同步AOF文件的内容。这个模式下Redis异常退出最多使一个指令修改的数据丢失,速度较慢,但最安全。

    everysec表示每一秒进行同步操作,这个模式下Redis异常退出最多丢失一秒内被修改的数据,速度快且也安全,也是Redis的默认配置。

    no 由操作系统来决定什么时候进行同步,这个模式下速度最快,但也最不安全。

    所以将配置文件中appendonly yes打开看下效果吧!

    首先打开客户端输入如下指令:

Redis学习笔记之十一:Redis数据持久化_第1张图片

    然后查看appendonly.aof文件,这是一个文本文件可以直接查看。

Redis学习笔记之十一:Redis数据持久化_第2张图片

    过滤掉$以及* 开头的行,可以发现aof文件存储的是执行的指令(写指令),但不会存储获取键值的指令例如GET,HGET等等。所以AOF文件的还原就是读取文件中的指令,依次执行即可。

    AOF重写

   可以从上面两图中看到,执行了两次set username,而aof文件中也的确存储了两条set指令,这种情况下第一条set指令就无用了。这种情况在使用中会多次出现,大量修改key的值,但只有最近一条修改指令有效(前面的指令可能被覆盖了)。例如,使用lpush向列表中添加10万个元素,每次添加1个或者2个,那aof文件中就要存储接近10w条lpush语句,这相当耗费内存,同时使用该aof文件恢复数据时,也要再执行10w条指令。为了防止aof文件因无用的指令过大,Redis提供了aof重写。

    aof重写会干什么?

    比如上述的set username指令,重写后就只有一条set username指令,lpush重写后也只有一条。

    aof重写怎么做的?

   当进行aof重写时,比如上述的set username指令,Redis会先从数据库中读取到username对应的value,然后使用set指令写入到aof文件中。也就是aof重写时不会依赖于现有的aof文件。

    什么时候进行aof重写?

    由配置文件中以下配置决定:


    auto-aof-rewrite-percentage表示如果当前aof文件的大小超出上次重写后aof文件大小的百分之多少就进行重写。如果没有重写过,则以启动时文件大小为依据进行比较

    auto-aof-rewrite-min-size表示当前aof文件最小为多大时进行重写。

    当然也可以手动执行BGREWRITEAOF指令显示告知Redis需要重写。


    3、Redis对过期键的持久化处理

    不管是RDB方式还是AOF方式,在写入磁盘时,都不会写入已经过期的键,同时在与内存中的数据做同步时,也会删除文件中已经过期的键。

    

    4、其他

    执行BGSAVE时,执行SAVE指令会被Redis拒绝,也即是BGSAVE和SAVE指令不能同时执行,也不能其中一个指令未执行完前重复执行,因为两个指令会产生竞态条件。同时执行BGREWRITEAOF指令将会被延后到BGSAVE指令执行完毕后,这两个指令并不会产生竞态条件,Redis主要是为了性能考虑防止BGSAVE和BGREWRITEAOF指令同时执行。

    测试, 1)、先使用代码添加100w条String类型的数据,代码如下:

package org.yamikaze.redis.persistence;

import org.yamikaze.redis.messsage.queue.StringUtils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;

/**
 * 向Redis插入大量String类型的数据
 * @author yamikaze
 */
public class SetData {

    private Jedis jedis;

    public SetData() {
        jedis = new Jedis("192.168.1.103");
    }

    public void insertData(long number) {
        if(number <= 0) {
            return;
        }
        Pipeline pipeline = jedis.pipelined();
        for(int i = 0; i < number; i++) {
            pipeline.set(i + "", StringUtils.generate32Str());
        }
        pipeline.syncAndReturnAll();
    }

    public void deleteData(long number) {
        if(number <= 0) {
            return;
        }
        Pipeline pipeline = jedis.pipelined();
        for(int i = 0; i < number; i++) {
            pipeline.del(i + "");
        }
        pipeline.syncAndReturnAll();
    }

    public static void main(String[] args) {
        SetData sd = new SetData();
        sd.deleteData(1000000);
        sd.insertData(1000000);
    }
}
    2)、执行完毕后可以看到Redis目录下的appendonly.aof文件有60M左右了,打开客户端执行BGSAVE指令,然后在执行SAVE指令,结果如下:


    3)、Jedis客户端也提供了相应的接口来进行重写和bgsave,代码如下:

package org.yamikaze.redis.persistence;

import org.yamikaze.redis.test.MyJedisFactory;
import redis.clients.jedis.Jedis;

import java.util.concurrent.TimeUnit;

/**
 * redis的RDB持久化
 * @author yamikaze
 */
public class RedisRdbPersistence {

    private Jedis jedis;

    public RedisRdbPersistence() {
        jedis = MyJedisFactory.getLocalJedis();
    }

    /**
     * fork出子线程进行写入
     */
    public void rdb() {
        String message = jedis.bgsave();
        //返回信息: Background saving started
        System.out.println(message);
    }

    public void save() {
        String message = jedis.save();
        //返回信息: OK
        System.out.println(message);
    }

    public static void main(String[] args) throws InterruptedException{
        RedisRdbPersistence rrp = new RedisRdbPersistence();
        rrp.rdb();
        //不能同时执行, 先让bgsave执行完毕在执行save
        TimeUnit.SECONDS.sleep(100);
        rrp.save();
    }
}

    Redis是同时支持RDB方式和AOF方式的,可以在开启RDB的同时开启AOF。但Redis在启动时会优先使用AOF文件来还原数据,因为AOF方式丢失的数据更少。


参考资料

    《Redis入门指南》

    《Redis的设计与实现》 第10、11章



    





    

你可能感兴趣的:(redis)