本篇文章给大家带来了关于redis的相关知识,其中主要介绍了持久化的相关问题,可以从几个方面介绍一下redis持久化的机制原理,希望对大家有帮助。
|
本篇文章给大家带来了关于Redis的相关知识,其中主要介绍了持久化的相关问题,可以从几个方面介绍一下redis持久化的机制原理,希望对大家有帮助。
推荐学习:Redis教程 本文将从以下几个方面介绍Redis持久化机制:
Redis是一个内存数据库,所有的数据将保存在内存中,这与传统的MySQL、Oracle、SqlServer等关系型数据库直接把数据保存到硬盘相比,Redis的读写效率非常高。但是保存在内存中也有一个很大的缺陷,一旦断电或者宕机,内存数据库中的内容将会全部丢失。为了弥补这一缺陷,Redis提供了把内存数据持久化到硬盘文件,以及通过备份文件来恢复数据的功能,即Redis持久化机制。 Redis支持两种方式的持久化:RDB快照和AOF。 RDB持久化RDB快照用官方的话来说:RDB持久化方案是按照指定时间间隔对你的数据集生成的时间点快照(point-to-time snapshot)。它以紧缩的二进制文件保存Redis数据库某一时刻所有数据对象的内存快照,可用于Redis的数据备份、转移与恢复。到目前为止,仍是官方的默认支持方案。 RDB工作原理既然说RDB是Redis中数据集的时间点快照,那我们先简单了解一下Redis内的数据对象在内存中是如何存储与组织的。 默认情况下,Redis中有16个数据库,编号从0-15,每个Redis数据库使用一个 当然,这个前提时我们上面的假设成立,否则面对一个时刻变化的数据集,我们无从下手。我们知道Redis中客户端命令处理是单线程模型,如果把持久化作为一个命令处理,那数据集肯定时处于静止状态。另外,操作系统提供的fork()函数创建的子进程可获得与父进程一致的内存数据,相当于获取了内存数据副本;fork完成后,父进程该干嘛干嘛,持久化状态的工作交给子进程就行了。 很显然,第一种情况不可取,持久化备份会导致短时间内Redis服务不可用,这对于高HA的系统来讲是无法容忍的。所以,第二种方式是RDB持久化的主要实践方式。由于fork子进程后,父进程数据一直在变化,子进程并不与父进程同步,RDB持久化必然无法保证实时性;RDB持久化完成后发生断电或宕机,会导致部分数据丢失;备份频率决定了丢失数据量的大小,提高备份频率,意味着fork过程消耗较多的CPU资源,也会导致较大的磁盘I/O。 持久化流程在Redis内完成RDB持久化的方法有rdbSave和rdbSaveBackground两个函数方法(源码文件rdb.c中),先简单说下两者差别:
RDB持久化的触发必然离不开以上两个方法,触发的方式分为手动和自动。手动触发容易理解,是指我们通过Redis客户端人为的对Redis服务端发起持久化备份指令,然后Redis服务端开始执行持久化流程,这里的指令有save和bgsave。自动触发是Redis根据自身运行要求,在满足预设条件时自动触发的持久化流程,自动触发的场景有如下几个(摘自这篇文章):
结合源码及参考文章,我整理了RDB持久化流程来帮助大家有个整体的了解,然后再从一些细节进行说明。
自动触发流程是一个完整的链路,涵盖了rdbSaveBackground、rdbSave等,接下来我以serverCron为例分析一下整个流程。 save规则及检查 serverCron是Redis内的一个周期性函数,每隔100毫秒执行一次,它的其中一项工作就是:根据配置文件中save规则来判断当前需要进行自动持久化流程,如果满足条件则尝试开始持久化。了解一下这部分的实现。 在 struct redisServer {
/* 省略其他字段 */
/* RDB persistence */
long long dirty; /* Changes to DB from the last save
* 上次持久化后修改key的次数 */
struct saveparam *saveparams; /* Save points array for RDB,
* 对应配置文件多个save参数 */
int saveparamslen; /* Number of saving points,
* save参数的数量 */
time_t lastsave; /* Unix time of last successful save
* 上次持久化时间*/
/* 省略其他字段 */
}
/* 对应redis.conf中的save参数 */
struct saveparam {
time_t seconds; /* 统计时间范围 */
int changes; /* 数据修改次数 */
};
# 表示900秒(15分钟)内至少有1个key的值发生变化,则执行 save 900 1 # 表示300秒(5分钟)内至少有1个key的值发生变化,则执行 save 300 10 # 表示60秒(1分钟)内至少有10000个key的值发生变化,则执行 save 60 10000 # 该配置将会关闭RDB方式的持久化 save ""
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
/* 省略其他逻辑 */
/* 如果用户请求进行AOF文件重写时,Redis正在执行RDB持久化,Redis会安排在RDB持久化完成后执行AOF文件重写,
* 如果aof_rewrite_scheduled为true,说明需要执行用户的请求 */
/* Check if a background saving or AOF rewrite in progress terminated. */
if (hasActiveChildProcess() || ldbPendingChildren())
{
run_with_period(1000) receiveChildInfo();
checkChildrenDone();
} else {
/* 后台无 saving/rewrite 子进程才会进行,逐个检查每个save规则*/
for (j = 0; j < server.saveparamslen; j++) {
struct saveparam *sp = server.saveparams+j;
/* 检查规则有几个:满足修改次数,满足统计周期,达到重试时间间隔或者上次持久化完成*/
if (server.dirty >= sp->changes
&& server.unixtime-server.lastsave > sp->seconds
&&(server.unixtime-server.lastbgsave_try > CONFIG_BGSAVE_RETRY_DELAY || server.lastbgsave_status == C_OK))
{
serverLog(LL_NOTICE,"%d changes in %d seconds. Saving...", sp->changes, (int)sp->seconds);
rdbSaveInfo rsi, *rsiptr;
rsiptr = rdbPopulateSaveInfo(&rsi);
/* 执行bgsave过程 */
rdbSaveBackground(server.rdb_filename,rsiptr);
break;
}
}
/* 省略:Trigger an AOF rewrite if needed. */
}
/* 省略其他逻辑 */
}如果没有后台的RDB持久化或AOF重写进程,serverCron会根据以上配置及状态判断是否需要执行持久化操作,判断依据就是看lastsave、dirty是否满足saveparams数组中的其中一个条件。如果有一个条件匹配,则调用rdbSaveBackground方法,执行异步持久化流程。 rdbSaveBackground rdbSaveBackground是RDB持久化的辅助性方法,主要工作是fork子进程,然后根据调用方(父进程或者子进程)不同,有两种不同的执行逻辑。
int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {
pid_t childpid;
if (hasActiveChildProcess()) return C_ERR;
server.dirty_before_bgsave = server.dirty;
server.lastbgsave_try = time(NULL);
// fork子进程
if ((childpid = redisFork(CHILD_TYPE_RDB)) == 0) {
int retval;
/* Child 子进程:修改进程标题 */
redisSetProcTitle("redis-rdb-bgsave");
redisSetCpuAffinity(server.bgsave_cpulist);
// 执行rdb持久化
retval = rdbSave(filename,rsi);
if (retval == C_OK) {
sendChildCOWInfo(CHILD_TYPE_RDB, 1, "RDB");
}
// 持久化完成后,退出子进程
exitFromChild((retval == C_OK) ? 0 : 1);
} else {
/* Parent 父进程:记录fork子进程的时间等信息*/
if (childpid == -1) {
server.lastbgsave_status = C_ERR;
serverLog(LL_WARNING,"Can't save in background: fork: %s",
strerror(errno));
return C_ERR;
}
serverLog(LL_NOTICE,"Background saving started by pid %ld",(long) childpid);
// 记录子进程开始的时间、类型等。
server.rdb_save_time_start = time(NULL);
server.rdb_child_type = RDB_CHILD_TYPE_DISK;
return C_OK;
}
return C_OK; /* unreached */
}rdbSave是真正执行持久化的方法,它在执行时存在大量的I/O、计算操作,耗时、CPU占用较大,在Redis的单线程模型中持久化过程会持续占用线程资源,进而导致Redis无法提供其他服务。为了解决这一问题Redis在rdbSaveBackground中fork出子进程,由子进程完成持久化工作,避免了占用父进程过多的资源。 需要注意的是,如果父进程内存占用过大,fork过程会比较耗时,在这个过程中父进程无法对外提供服务;另外,需要综合考虑计算机内存使用量,fork子进程后会占用双倍的内存资源,需要确保内存够用。通过info stats命令查看latest_fork_usec选项,可以获取最近一个fork以操作的耗时。 rdbSave Redis的rdbSave函数是真正进行RDB持久化的函数,流程、细节贼多,整体流程可以总结为:创建并打开临时文件、Redis内存数据写入临时文件、临时文件写入磁盘、临时文件重命名为正式RDB文件、更新持久化状态信息(dirty、lastsave)。其中“Redis内存数据写入临时文件”最为核心和复杂,写入过程直接体现了RDB文件的文件格式,本着一图胜千言的理念,我按照源码流程绘制了下图。 AOF持久化上一节我们知道RDB是一种时间点(point-to-time)快照,适合数据备份及灾难恢复,由于工作原理的“先天性缺陷”无法保证实时性持久化,这对于缓存丢失零容忍的系统来说是个硬伤,于是就有了AOF。 AOF工作原理AOF是Append Only File的缩写,它是Redis的完全持久化策略,从1.1版本开始支持;这里的file存储的是引起Redis数据修改的命令集合(比如:set/hset/del等),这些集合按照Redis Server的处理顺序追加到文件中。当重启Redis时,Redis就可以从头读取AOF中的指令并重放,进而恢复关闭前的数据状态。 AOF持久化默认是关闭的,修改redis.conf以下信息并重启,即可开启AOF持久化功能。 # no-关闭,yes-开启,默认no appendonly yes appendfilename appendonly.aof AOF本质是为了持久化,持久化对象是Redis内每一个key的状态,持久化的目的是为了在Reids发生故障重启后能够恢复至重启前或故障前的状态。相比于RDB,AOF采取的策略是按照执行顺序持久化每一条能够引起Redis中对象状态变更的命令,命令是有序的、有选择的。把aof文件转移至任何一台Redis Server,从头到尾按序重放这些命令即可恢复如初。举个例子: 首先执行指令 因为在这个过程中,能够引起 set number 0 incr number incr number incr number incr number incr number 最本质的原理用“命令重放”四个字就可以概括。但是,考虑实际生产环境的复杂性及操作系统等方面的限制,Redis所要考虑的工作要比这个例子复杂的多:
持久化流程从流程上来看,AOF的工作原理可以概括为几个步骤:命令追加(append)、文件写入与同步(fsync)、文件重写(rewrite)、重启加载(load),接下来依次了解每个步骤的细节及背后的设计哲学。 命令追加 当 AOF 持久化功能处于打开状态时,Redis 在执行完一个写命令之后,会以协议格式(也就是RESP,即 Redis 客户端和服务器交互的通信协议 )把被执行的写命令追加到 Redis 服务端维护的 AOF 缓冲区末尾。对AOF文件只有单线程的追加操作,没有seek等复杂的操作,即使断电或宕机也不存在文件损坏风险。另外,使用文本协议好处多多:
AOF缓冲区类型为Redis自主设计的数据结构 需要注意的是:如果命令追加时正在进行AOF重写,这些命令还会追加到重写缓冲区( 文件写入与同步 AOF文件的写入与同步离不开操作系统的支持,开始介绍之前,我们需要补充一下Linux I/O缓冲区相关知识。硬盘I/O性能较差,文件读写速度远远比不上CPU的处理速度,如果每次文件写入都等待数据写入硬盘,会整体拉低操作系统的性能。为了解决这个问题,操作系统提供了延迟写(delayed write)机制来提高硬盘的I/O性能。
延迟写减少了磁盘读写次数,但是却降低了文件内容的更新速度,使得欲写到文件中的数据在一段时间内并没有写到磁盘上。当系统发生故障时,这种延迟可能造成文件更新内容的丢失。为了保证磁盘上实际文件系统与缓冲区高速缓存中内容的一致性,UNIX系统提供了sync、fsync和fdatasync三个函数为强制写入硬盘提供支持。 Redis每次事件轮训结束前(
注意:上面介绍的策略受配置项 如果 为了缓解此问题,可以使用该选项,以防止在进行
文件重写 如前面提到的,Redis长时间运行,命令不断写入AOF,文件会越来越大,不加控制可能影响宿主机的安全。 为了解决AOF文件体积问题,Redis引入了AOF文件重写功能,它会根据Redis内数据对象的最新状态生成新的AOF文件,新旧文件对应的数据状态一致,但是新文件会具有较小的体积。重写既减少了AOF文件对磁盘空间的占用,又可以提高Redis重启时数据恢复的速度。还是下面这个例子,旧文件中的6条命令等同于新文件中的1条命令,压缩效果显而易见。
Redis启动时把 增长比例:(aof_current_size - aof_base_size) / aof_base_size > auto-aof-rewrite-percentage 文件大小:aof_current_size > auto-aof-rewrite-min-size 手动触发与自动触发的代码如下,同样在周期性方法 int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
/* 省略其他逻辑 */
/* 如果用户请求进行AOF文件重写时,Redis正在执行RDB持久化,Redis会安排在RDB持久化完成后执行AOF文件重写,
* 如果aof_rewrite_scheduled为true,说明需要执行用户的请求 */
if (!hasActiveChildProcess() &&
server.aof_rewrite_scheduled)
{
rewriteAppendOnlyFileBackground();
}
/* Check if a background saving or AOF rewrite in progress terminated. */
if (hasActiveChildProcess() || ldbPendingChildren())
{
run_with_period(1000) receiveChildInfo();
checkChildrenDone();
} else {
/* 省略rdb持久化条件检查 */
/* AOF重写条件检查:aof开启、无子进程运行、增长百分比已设置、当前文件大小超过阈值 */
if (server.aof_state == AOF_ON &&
!hasActiveChildProcess() &&
server.aof_rewrite_perc &&
server.aof_current_size > server.aof_rewrite_min_size)
{
long long base = server.aof_rewrite_base_size ?
server.aof_rewrite_base_size : 1;
/* 计算增长百分比 */
long long growth = (server.aof_current_size*100/base) - 100;
if (growth >= server.aof_rewrite_perc) {
serverLog(LL_NOTICE,"Starting automatic rewriting of AOF on %lld%% growth",growth);
rewriteAppendOnlyFileBackground();
}
}
}
/**/
}AOF文件重写的流程是什么?听说Redis支持混合持久化,对AOF文件重写有什么影响? 从4.0版本开始,Redis在AOF模式中引入了混合持久化方案,即:纯AOF方式、RDB+AOF方式,这一策略由配置参数
结合源码(6.0版本,源码太多这里不贴出,可参考
数据加载Redis启动后通过 理论上,AOF持久化比RDB具有更好的实时性,当开启了AOF持久化方式,Redis在数据加载时优先考虑AOF方式。而且,Redis 4.0版本后AOF支持了混合持久化,加载AOF文件需要考虑版本兼容性。Redis数据加载流程如下图所示: 如果在AOF命令追加过程中发生宕机,由于延迟写的技术特点,AOF的RESP命令可能不完整(被截断)。遇到这种情况时,Redis会按照配置项
总结Redis提供了两种持久化的选择:RDB支持以特定的实践间隔为数据集生成时间点快照;AOF把Redis Server收到的每条写指令持久化到日志中,待Redis重启时通过重放命令恢复数据。日志格式为RESP协议,对日志文件只做append操作,无损坏风险。并且当AOF文件过大时可以自动重写压缩文件。 当然,如果你不需要对数据进行持久化,也可以禁用Redis的持久化功能,但是大多数情况并非如此。实际上,我们时有可能同时使用RDB和AOF两种方式的,最重要的就是我们要理解两者的区别,以便合理使用。 RDB vs AOFRDB优点
RDB缺点
AOF优点
AOF缺点
使用建议对RDB和AOF两种持久化方式的工作原理、执行流程及优缺点了解后,我们来思考下,实际场景中应该怎么权衡利弊,合理的使用两种持久化方式。如果仅仅是使用Redis作为缓存工具,所有数据可以根据持久化数据库进行重建,则可关闭持久化功能,做好预热、缓存穿透、击穿、雪崩之类的防护工作即可。 一般情况下,Redis会承担更多的工作,如分布式锁、排行榜、注册中心等,持久化功能在灾难恢复、数据迁移方面将发挥较大的作用。建议遵循几个原则:
关于fork()通过上面的分析,我们都知道RDB的快照、AOF的重写都需要fork,这是一个重量级操作,会对Redis造成阻塞。因此为了不影响Redis主进程响应,我们需要尽可能降低阻塞。
推荐学习:Redis学习教程 以上就是Redis经典技巧之详解持久化原理的详细内容,更多请关注模板之家(www.mb5.com.cn)其它相关文章! |
