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打开看下效果吧!
首先打开客户端输入如下指令:
然后查看appendonly.aof文件,这是一个文本文件可以直接查看。
过滤掉$以及* 开头的行,可以发现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章