前言
Redis持久化,一个老掉牙的问题,但是面试官就是喜欢问。这也是我们学Redis必会的一个知识点。Redis作为内存数据库,它工作时,数据都保存在内存里,这也是它为什么很快的一个原因。但存到内存里肯定是有丢数据的风险,所以Redis是有设计持久化的。Redis持久化分为两种:RDB和AOF。
RDB持久化
RDB(Redis DataBase),是redis默认的存储方式,RDB持久化其实就是将内存的数据直接做了一份快照到磁盘上。触发RDB持久化的方式有:
-
符合配置的快照保存规则(配置文件里save开头的配置);
-
执行save或者bgsave命令;
-
执行flushall命令;
-
执行主从复制操作 (第一次)。
配置文件redis.conf中,save开头的配置为RDB持久化相关配置。具体解释如下:
- save “” 表示关闭rdb持久化;
- save 3600 1 表示每1小时至少有1个key改变,就触发一次持久化可以写多个条件 ;
- save 3600 1 300 100 60 10000 这里定义了三个策略,它们相互之间为或的关系。
RDB文件生成过程
我们以bgsave为例子来看下Redis生成RDB文件的大致过程是怎样的。
int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {
pid_t childpid;
long long start;
// 如果已经存在aof重写子进程以及rdb生成子进程则直接返回错误
if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) return C_ERR;
...
// fork子进程进行RDB文件生成
if ((childpid = fork()) == 0) {
...
// 生成RDB文件
retval = rdbSave(filename,rsi);
if (retval == C_OK) {
size_t private_dirty = zmalloc_get_private_dirty(-1);
if (private_dirty) {
serverLog(LL_NOTICE,
"RDB: %zu MB of memory used by copy-on-write",
private_dirty/(1024*1024));
}
server.child_info_data.cow_size = private_dirty;
// 通知父进程RDB文件生成完毕
sendChildInfo(CHILD_INFO_TYPE_RDB);
}
//子进程退出
exitFromChild((retval == C_OK) ? 0 : 1);
} else {
//父进程业务逻辑
...
}
return C_OK;
}
(1)Redis主进程首先判断当前是否存在已经在执行的aof重写子进程以及rdb文件生成子进程,如果存在的话则直接进行返回。为什么要进行这样的判断呢?主要还是从服务器性能方面进行考量,如果服务器有多个子线程在进行RDB持久化操作,那么必定会对磁盘造成比较大的IO压力,如果服务器中还部署了其他服务甚至会影响其他服务的正常运行。
(2)Redis主进程fork子进程进行RDB文件生成操作,在fork的过程中,此时的Redis主进程是阻塞的,不能响应客户端请求,子进程fork完成之后可以继续响应客户端请求。
(3)fork出来的子进程遍历内存数据进行RDB文件生成操作。
(4)如果此时客户端的请求需要修改缓存数据,那么如上面fork子进程的原理,通过COW机制,操作系统会开辟新的内存空间给Redis主进程进行新的缓存数据写入。
(5)子进程快照数据生成完成之后,替换原来老的RDB文件。
RDB触发时机
Redis主要支持两种持久化操作来生成RDB文件,分别是save、bsave命令方式手动生成以及在配置文件中配置时间间隔自动进行RDB文件生成。
手动命令触发
客户端连接到redis之后我们可以通过save以及bsave命令进行RDB文件的立即创建,两者的区别如下:
save:通过主线程触发,会阻塞Redis业务,如果内存数据比较多的话,会导致长时间不能响应外部请求;
客户端执行bsave命令进行RDB持久化,Redis主线程会fork子线程出来进行RDB文件持久化操作,这样避免了主线程的阻塞即便正在持久化操作依然可以响应外部数据缓存请求。
不过这里值得注意的是,虽然fork子进程之后不会阻塞主进程,但是在fork的过程中会阻塞主进程,尤其是在内存数据比较大的时候,阻塞主进程的时间会更长。
配置自动触发
另外在Redis的配置文件redis.conf中,我们可以配置按照一定的时间间隔来进行RDB持久化操作。如下配置:
save 900 1
save 300 10
save 60 10000
其他的触发RDB文件生成的操作这里不再赘述了,像从节点执行全量数据同步的时候,也会触发主节点生成RDB文件发送给从节点。
AOF持久化
AOF(Append Only File)持久化,是其将Reids执行过的所有写指令记录下来,保存到日志里,类似MySQL的bin-log。默认配置文件里该持久化方式是关闭的,需要将配置修改为:
appendonly yes
由于AOF是将Redis服务的写操作日志写到日志文件里,当写操作非常频繁时,那么它对磁盘也会造成很大的压力。所以,AOF的磁盘数据落地(fsync函数)也有三个策略:
Always:表示只要有写入就会调用fsync函数;
Everysec:表示每秒调用fsync函数一次;
No:表示不调用fscyn函数,完全跟着系统走;
建议选择everysec,比较保守一些。
AOF重写
AOF文件如果不做干预,它会一直增涨,直到将你的磁盘写满。好在Redis给AOF提供了重写机制。我们可以直接执行如下命令,进行AOF重写:
bgrewriteaof;
执行完该命令后,AOF文件会根据已经持久化的RDB文件和现有AOF文件重新整理,它会把无用的写日志清空,最终达到瘦身目的。
当然,AOF还有一个重写的配置,两个参数:
参数 | 说明 |
---|---|
auto-aof-rewrite-min-size | AOF文件必须要不低于这个尺寸时才会触发重写,后面的每次重写就不会根据这个变量了(根据上一次重写完成之后的大小)。此变量仅初始化启动redis有效 |
auto-aof-rewrite-percentage | 如果该数值定义为80,则表示当AOF文件增长的尺寸超过上次大小(AOF文件上次重写后的大小会被记录下来)百分80时就会触发重写操作 |
RDB和AOF如何选
在实际生产环境中,根据数据量、应用对数据的安全要求、预算限制等不同情况,会有各种各样的持久化策略。
如,完全不使用任何持久化、使用RDB持久化或AOF持久化的一种,或同时开启快照持久化和AOF持久化等。此外,持久化的选择必须与Redis的主从策略一起考虑,因为主从复制与持久化同样具有数据备份的功能,而且主机Master和从机Slave可以独立的选择持久化方案。
如果Redis中的数据完全丢弃也没有关系(如Redis完全用作DB层数据的cache),那么无论是单机,还是主从架构,都可以不进行任何持久化。
在单机环境下(对于个人开发者,这种情况可能比较常见),如果可以接受十几分钟或更多的数据丢失,选择RDB持久化对Redis的性能更加有利,如果只能接受秒级别的数据丢失,应该选择AOF。
但在多数情况下,我们都会配置主从环境,Slave的存在既可以实现数据的热备,也可以进行读写分离分担Redis读请求,以及在Master宕掉后继续提供服务。在这种情况下,一种可行的做法是:
- Master:完全关闭持久化,这样可以让Master的性能达到最好;
- Slave:关闭RDB持久化,开启AOF(如果对数据安全要求不高,开启RDB持久化关闭AOF也可以),并定时对持久化文件进行备份(如备份到其他文件夹,并标记好备份的时间)。然后关闭AOF的自动重写,然后添加定时任务,在每天Redis闲时(如凌晨12点)调用bgrewriteaof。