文章目录
Redis持久化简介
什么是持久化?
Redis所有数据保存在内存中,对数据的更新将异步的保存到磁盘上。
为什么需要持久化?
Redis是基于内存的数据库,服务一旦宕机,内存中的数据将全部丢失。通常的解决方案是从后端数据库恢复这些数据,但是后端数据库有性能瓶颈,如果是大数据量的恢复,1. 会对数据库带来巨大的压力 2.数据库的性能不如Redis,导致程序响应慢。所以对Redis来说,实现数据的持久化,避免从后端数据库中恢复数据,是至关重要的。
Redis持久化有哪些方式?为什么要重点学习RDB和AOF?
Redis服务提供四种持久化存储的方案:
RDB(快照)、AOF(写日志)、虚拟内存(VM)、DISKSTORE
。VM不再建议使用,DISKSTORE没有明确的支持。
目前官方文档上能看到的Redis对持久化存储的支持只有两种方案。
RDB持久化
RDB是Redis DataBase的缩写,中文名是快照/内存快照,RDB持久化是把当前进程数据生成快照保存到磁盘上的过程,由于是某一时刻的快照,那么快照中的值要早于或者等于内存中的值。
触发方式
手动触发和自动触发。
手动触发
手动触发分别对应save
和bgsave
命令。
- save(同步)
(1)在数据量很大的时候,会存在阻塞,直到RDB过程完成为止,因为Redis是单线程的。
(2)文件策略:如果存在老的RDB文件,新的文件会替换掉老的文件。
(3)复杂度:O(N),因为需要将redis中的所有数据都写到RDB文件中。
- bgsave命令:Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短。
这个createRDB
是由子进程来完成的。
bgsave流程图:
具体流程: - redis客户端执行bgsave命令;
- 主进程判断当前是否已经存在正在执行的子进程,如果存在,那么主进程直接返回。
- 如果不存在正在执行的子进程,那么就fork一个新的子进程进行持久化数据,fork过程是阻塞的,fork操作完成后主进程即可执行其他操作。
- 子进程先将数据写入到临时的rdb文件中,待快照数据写入完成后再原子替换旧的rdb文件。
- 同时发送信号给主进程,通知主进程rdb持久化完成,主进程更新相关的统计信息。
自动触发
在某些条件达到的时候,自动的生成RDB文件(这个条件可以在配置文件中配置)。
上述表格中满足任意条件就会自动创建RDB,第一行的意思是:每900秒,有一个数据改变,就会生成RDB文件。
以下四种情况会自动触发:
- redis.conf中配置
save m n
,即在m秒内有n次修改时,自动触发bgsave生成rdb文件; - 主从复制,从节点要从主节点进行全量复制时也会触发bgsave操作,主节点会自动生成RDB文件,发送到从节点。
- 执行debug reload命令重新加载redis时也会触发bgsave操作。
- 默认情况下执行shutdown命令时,如果没有开启AOF持久化,那么也会触发bgsave操作。
深入理解RDB
问题一:由于生产环境中我们为Redis开辟的内存区域都比较大,那么将内存中的数据同步到硬盘的过程可能就会持续比较长的时间,而实际情况是这段时间Redis服务一般都会受到数据写操作请求,那么如何保证数据的一致性呢?
RDB的核心思路是Copy-on-Write
来保证在进行快照操作的这段时间内,需要压缩写入磁盘上的数据在内存中不会发生变化。在正常的快照操作中,一方面Redis主进程会fork一个新的快照进程专门来做这个事,这样保证Redis服务不会停止对客户端包括写请求在内的任何响应。另一方面这段时间发生的数据变化会以副本的方式存放在一个新的内存区域,待快照结束后才同步到原来的内存区域。
补充:
什么是
Copy-on-Write
?
写入时复制,是一种计算机程序设计领域的优化策略,其核心思想是:如果有多个调用者同时请求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本给该调用者,而其他调用者所见到的最初的资源仍然保持不变。
举例:
如果主线程对这些数据都是读操作(例如键值对A),那么,主线程和bgsave子进程相互不影响。但是,如果主线程要修改一块数据(例如键值对C),那么这块数据就会被复制一份,生成该数据的副本,然后bgsave子进程会把这个副本数据写入RDB文件,而在这个过程中,主线程仍然可以直接读取原来的数据。
问题二:在进行快照操作的这段时间内,如果发生服务器崩溃怎么办?
在没有将数据全部写入到磁盘前,这次快照操作都不算成功。如果出现了服务器崩溃的情况,将以上一次完整的RDB快照文件作为恢复内存数据的参考。也就是说,在快照过程中不能影响上一次的备份数据。Redis服务会在磁盘上创建一个临时文件进行数据操作,待成功后才会用这个临时文件替代上一次的备份。
问题三:可以每秒做一次快照吗?
所谓“连拍”就是连续的做快照。这样一来,快照的间隔时间变得很短,即使某一刻发生宕机了,因为上一时刻刚执行,丢失数据也不会太多,但是,这其中的快照间隔时间就很关键了。
虽然bgsave执行时不阻塞主线程,但是,如果频繁的执行全量快照,也会带来两方面的开销:
- 一方面,频繁将全量数据写入磁盘,会给磁盘带来很大的压力,多个快照竞争有限的磁盘带宽,前一个快照还没有做完,后一个又开始做了,容易造成恶性循环。
- 另一方面,bgsave子进程需要通过fork操作从主线程创建出来,虽然,子进程在创建后不会阻塞主线程,但是,fork这个创建过程本身会阻塞主线程,而且主线程的内存越大,阻塞时间越长。如果频繁fork出bgsave子进程,这就会频繁阻塞主进程了。
我们可以做增量快照,就是指做了一次全量快照之后,后续的快照只对修改的数据进行快照记录,这样可以避免每次全量快照的开销。
但是这需要我们使用额外的元数据信息去记录哪些数据被修改了,这会带来额外的空间开销问题。
RDB优缺点
优点:
- RDB文件是某个时间节点的快照,默认使用LZF算法进行压缩,压缩后的文件体积远远小于内存大小,适用于备份、全量复制等场景。
- Redis加载RDB文件恢复数据要远远快于AOF方式。
缺点:
RDB现存的问题:
-
耗时、耗性能
-
不可控、丢失数据
-
RDB方式的实时性不够,无法做到秒级的持久化
-
每次调用bgsave都需要fork子进程,fork子进程属于重量级操作,频繁执行成本较高。
-
RDB文件是二进制的,没有可读性,AOF文件在了解其结构的情况下可以手动修改或者补全。
-
版本兼容RDB文件问题。
AOF持久化
Redis是“写后”日志,Redis先执行命令,把数据写入内存,然后才记录日志。日志里记录的是Redis收到的每一条命令,这些命令是以文本的形式保存。大多数的数据库采用的是写前日志,例如MySQL,通过写前日志和两阶段提交,实现数据和逻辑的一致性。
AOF日志采用写后日志,先写内存,后写日志。
运行原理:
写一个数据,就向AOF文件中添加一条数据。
为什么采用写后日志?
Redis要求高性能,采用写后日志有两方面的好处:
- 避免额外的检查开销:Redis在向AOF里面记录日志的时候,并不会先去对这些命令进行语法检查。所以,如果先记日志再执行命令的话,日志中就有可能记录了错误的命令,Redis在使用日志恢复数据时,就可能会出错。
- 不会阻塞当前的写操作。
但是这种方式存在潜在的风险:
- 如果命令执行完成,写日志之前宕机了,会丢失数据。
- 主线程写磁盘压力大,导致写盘慢,阻塞后续操作。
如何实现AOF
AOF日志记录Redis的每个写命令:步骤分为:命令追加(append)、文件写入(write)和文件同步(sync)。
-
命令追加:当AOF持久化功能打开了,服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器的
aof_buf
缓冲区。 -
文件写入和同步:关于如何将
aof_buf
缓冲区的内容写入AOF文件中,Redis提供了三种写回策略。
-
Always
:同步写回:每个写命令执行完,立马同步的将日志写回磁盘。
-
Everysec
:每秒写回:每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘。
-
No
:操作系统控制写回:每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘。
AOF的同步策略是涉及到操作系统的write函数和fsync函数的:
为了提高文件写入效率,在现代操作系统中,当用户调用write函数,将一些数据写入文件时,操作系统通常会将数据暂存到一个内存缓冲区里,当缓冲区的空间被填满或超过了指定时限后,才真正将缓冲区的数据写入到磁盘中。
这样的操作虽然提高了效率,但也为数据写入带来了安全问题,如果计算机停机,内存缓冲区中的数据会丢失。为此,系统提供了fsync、fdatasync同步函数,可以强制操作系统立刻将缓冲区中的数据写入到磁盘中,从而确保了写入数据的安全性。
深入理解AOF重写
AOF会记录每个写命令到AOF文件中,随着时间越来越长,AOF文件会变得越来越大,如果不加以控制,会对Redis服务器,甚至对操作系统造成影响,而且AOF文件越大,数据恢复就越慢,为了解决AOF文件体积膨胀的问题,Redis提供AOF文件重写机制来对AOF文件进行“瘦身”。
-
图例解释AOF重写
Redis通过创建一个新的AOF文件来替换现有的AOF,新旧两个AOF文件保存的数据相同,但新AOF文件没有了冗余命令。
-
AOF重写的两种方式
(1)bgrewriteaof
(2)AOF重写配置
-
AOF重写会阻塞吗?
AOF重写过程是由后台进程bgrewriteaof来完成的,主线程fork出后台的bgrewriteaof子进程,fork会把主线程的内存拷贝一份给bgrewriteaof子线程,这里面就包含了数据库的最新数据。然后,bgrewriteaof子进程就可以在不影响主线程的情况下,逐一把拷贝的数据写成操作,计入重写日志。
所以AOF在重写时,在fork进程时会阻塞住主进程的。 -
AOF日志何时会重写?
有两个配置项控制AOF重写的触发: -
auto-aof-rewrite-min-size
:表示运行AOF重写时文件的最小大小,默认为64MB。 -
auto-aof-rewrite-percentage
:这个值的计算方式是,当前aof文件大小和上一次重写后aof文件大小的差值,再除以上一次重写后aof文件大小。也就是当前aof文件比上一次重写后aof文件的增量大小,和上一次重写后aof文件大小的比值。 -
重写日志时,有新数据写入怎么办?
重写过程总结为:一个拷贝,两处日志。在fork出子进程时的拷贝,以及在重写时,如果有新数据写入,主线程就会将命令记录到两个aof日志内存缓冲区中。如果AOF写回策略是always,则直接将命令写回旧的日志文件,并且保存一份命令至AOF重写缓冲区,这些操作对新的日志文件是不存在影响的(旧的日志文件:主线程使用的日志文件;新的日志文件:bgrewriteaof进程使用的日志文件) 。
而在bgrewriteaof子进程完成日志文件的重写操作后,会提示主线程已经完成重写操作,主线程会将AOF重写缓冲中的命令追加到新的日志文件后面。这时候在高并发的情况下,AOF重写缓冲区积累可能会很大,这样就会造成阻塞,Redis后来通过Linux管道技术让aof重写期间就能同时进行回放,这样aof重写结束后只需回放少量剩余的数据即可。
最后通过修改文件名的方式,保证文件切换的原子性。
在AOF重写日志期间发生宕机的话,因为日志文件还没切换,所以恢复数据时,用的还是旧的日志文件。
总结操作:
- 主线程fork出子进程重写aof日志。
- 子进程重写日志完成后,主线程追加aof日志缓冲
- 替换日志文件
这里的进程和线程的概念有些混乱。因为后台的bgrewriteaof进程就只有一个线程在操作,而主线程是Redis的操作进程,也是一个单独的线程。这里想表达的是Redis主进程在fork出一个后台进程之后,后台进程的操作和主进程是没有任何关联的,也不会阻塞主线程。
主线程fork出子进程是如何复制内存数据的?
fork采用操作系统提供的写时复制机制(copy-on-write),就是为了避免一次性拷贝大量内存数据给子进程造成阻塞。fork子进程时,子进程会拷贝父进程的页表,即虚实映射关系(虚拟内存和物理内存的映射索引表),而不会拷贝物理内存。这个拷贝会消耗大量cpu资源,并且拷贝完成前会阻塞主线程,阻塞时间取决于内存中的数据量,数据量越大,则内存页表越大。拷贝完成后父子进程使用相同的内存地址空间。
但主进程是可以有数据写入的,这时候就会拷贝物理内存中的数据,下图中进程1看做是主进程,进程2看做是子进程。
在主进程有数据写入时,而这个数据刚好在页C中,操作系统会创建这个页面的副本,即拷贝当前页的物理数据,将其映射到主进程中,而子进程还是使用原来的页c。
在重写日志整个过程中,主线程有哪些地方会被阻塞?
- fork子进程时,需要拷贝虚拟页表,会对主线程阻塞。
- 主进程有bigkey写入时,操作系统会创建页面的副本,并拷贝原有的数据,会对主线程阻塞。
- 子进程重写日志完成后,主进程追加aop重写缓冲区时可能会对主线程阻塞。
为什么AOF重写不复用原AOF日志?
两方面的原因:
- 父子进程写同一个文件会产生竞争问题,影响父进程的性能。
- 如果AOF重写过程失败了,相当于污染了原本的AOF文件,无法做恢复数据使用。
RDB和AOF的抉择
RDB和AOF混合方式
Redis 4.0中提出了一个混合使用RDB和AOF的方法,简单来说,内存快照以一定的频率执行,在两次快照之间,使用AOF日志记录这段时间的所有命令操作。
这样一来,快照就不用很频繁的执行,这就避免了频繁fork对主线程的影响,而且,AOF日志叶志勇记录两次快照间的操作,也就是说,不需要记录所有的操作了,因此,就不会出现文件过大的情况了,也可以避免重写开销。
这个方法既能享受到RDB文件快速恢复的好处,又能享受到AOF只记录操作命令的简单优势,实际环境中用的很多。
从持久化中恢复数据
想要从文件中恢复数据,只需要重新启动Redis即可。
因为AOF保存的数据更完整,所以会优先加载AOF,AOF基本上最多损失1s的数据。
开发运维常见问题
-
fork操作
(1)同步操作,做了一个内存页的拷贝
(2)与内存量息息相关:内存越大,耗时越长(与机器类型相关)
(3)info:latest_fork_usec
-
进程外开销
-
AOF追加阻塞
-
单机多实例部署
性能与实践
RDB的快照、AOF的重写都需要fork,这是一个重量级的操作,会对Redis造成阻塞。因此为了不影响Redis主进程响应,我们需要尽可能降低阻塞。
- 降低fork的频率,比如可以手动来触发RDB生成快照以及AOF重写。
- 控制Redis最大使用内存,防止fork耗时过长。
- 使用更牛逼的硬件
- 合理配置Linux的内存分配策略,避免因为物理内存不足导致fork失败。
实践经验:
- 如果Redis中的数据并不是特别敏感或者可以通过其他方式重写生成数据,可以关闭持久化,如果丢失数据可以通过其他途径补回。
- 自己制定策略定期检查Redis的情况,然后可以手动触发备份、重写数据
- 单机如果部署多个实例,要防止多个机器同时运行持久化、重写操作,防止出现内存、CPU、IO资源竞争,让持久化变为串行。
- 可以加入主从机器,利用一台从机器进行备份处理,其他机器正常响应客户端的命令。
- RDB持久化和AOF持久化可以同时存在,配合使用。
感谢并参考:
https://www.cnblogs.com/ybyn/p/14157568.html
https://segmentfault.com/a/1190000015983518
https://blog.csdn.net/qq_41434612/article/details/108791437
https://www.cnblogs.com/liang24/p/14180036.html
https://www.jianshu.com/p/d3ba7b8ad964
https://blog.csdn.net/liupeifeng3514/article/details/79048767
https://blog.csdn.net/xiaojie_570/article/details/86368420?spm=1001.2014.3001.5501