redis 多进程_深入理解Redis的持久化机制

一、持久化概述

我们都知道,Redis是一个基于内存的数据库,针对所有的数据读写都在内存中操作,Redis也因为高效的内存操作而成为著名的高性能键值对存储数据库。

但是基于内存的数据有一个明显的问题,就是如果Redis进程崩溃、OS系统崩溃或主机掉电都可能导致Redis实例中的数据丢失。为了解决这种当系统发生crash的时候造成数据丢失的问题,Redis引入了持久化的机制,从而为Redis内存的数据提供了crash-safe的能力,如下图所示:

b2766ddd09cc1456d313278052098de4.png

二、RDB持久化

2.1 RDB核心原理

我们先看看RDB的持久化方式,RDB(Redis Database)持久化,其实就是采用一种内存快照的方式,来对内存中的数据实施持久化。

Redis为我们提供了两个命令来实现持久化:

save - 由主线程直接进行持久化,会阻塞其它客户端的请求。

bgsave - 由主线程fork一个子线程来进行持久化,不会阻塞其它客户端的请求。

在生产环境中,我们通常不建议使用save命令,除非你确定无视这种阻塞情况并且希望在持久化的时候,不需要fork子进程来处理。否则我们一般都可以采用bgsave来进行持久化,下面我们就介绍一下bgsave持久化,如下图所示:

42f6518bacb146da42793b135e9281dd.png

从上图不难看出,采用bgsave进行持久化的时候,共有如下几个步骤:

(1)、主进程同步fork子进程(包括子进程从父进程进行页表的拷贝),这一步完成后主进程与子进程页表所指向的内存是完全一样的;

(2)、子进程执行快照数据的持久化,主线程继续处理读写请求,同时主线程还会监视子进程的持久化过程及状态;

(3)、主进程如果发现有新数据写入,就会申请新的内存块,拷贝被修改的页面数据对新的内存块中,从新数据写入的时候开始,主进程与子进程的页表指向的内存块就开始逐步分离;

(4)、子进程RDB持久化完成,子进程结束。

2.2 RDB持久化的问题及应对

理论上,整个过程看起来都是异步的,但这个RDB的持久化过程有如下问题:

      问题1、如果Redis实例的内存数据很多,那么会导致主进程的页表很大,其实页表的拷贝是需要阻塞主进程的,这里会有阻塞主进程的风险点,有可能导致Redis实例的性能抖动

问题2、如果子进程的持久化的过程中,有大量的页面数据被修改,那么主进程将会重新申请大量的内存块(关于写时复制技术,如果有兴趣的话,可以查阅相关操作系统的书籍),来写入新内容,如果内存不够用,有可能导致Redis实例内存不足而出现OOM的情况(如果没有开启swap的话),当然如果开启了的swap,新的数据写入将写入磁盘的swap,这也会引发Redis极大的性能风险

问题3、默认情况下,如果异步的子进程在持久化的时候失败了,那么主进程默认情况下将会停止接受所有客户端的写入请求

问题4、如果运行Redis实例的绑定了CPU的逻辑核,那么子进程在持久化的时候,还会与主进程竞争CPU资源,同样会增加主进程的请求处理延迟。

问题5、如果Redis实例在某个时刻宕机了,自最后一次执行RDB持久化的时间点之后的所有写入的新数据都会丢失。

针对问题1:不建议redis单实例的内存太大,最好不要超过8G内存,如果单机不够存储,可以考虑集群化的解决方案。

针对问题2:通常我们需要运行Redis实例的机器内存留有一定的内存富余,从而避免因为RDB子进程持久化时,子进程的写时复制机制引发的内存资源风险。

针对问题3:redis也提供参数来进行配置,如果子进程持久化失败可以不阻止主进程接受写入请求,但这样一来就会导致数据没有办法持久化。

针对问题4:通常来看,有两种解决办法:如果为多超线程CPU,让主进程绑定到一个物理核上,如果是单线程CPU,那么主进程就不要绑核(虽然主进程不绑核,会影响一些性能),还有另外一种办法就是修改Redis的源码,这样的话可以让子进程绑定跟主进程不一样的逻辑核。(关于CPU架构相关的知识,如果你有兴趣,可以参阅CPU架构方面的书籍)。

针对问题5:Redis提供了AOF的持久化机制,可以有效解决大量数据丢失的问题,这个后面的小节中会详细介绍AOF持久化。

2.3 RDB持久化配置

关于RDB持久化,Redis为我们提供下面这些参数来进行配置:

save 60 1000

save参数,第一个参数是表示时间,单位是秒,第二个参数是表示在指定时间段内有多少个key被更新。比如上面这个,意思就是如果在60秒内,有超过1000个key被更新了,那么redis将自动进行rdb持久化。这个save参数可以同时配置多个。有任何一个save参数的条件达到了,都将触发Redis进行RDB持久化。

stop-writes-on-bgsave-error yes

默认情况下,如果 redis 的RDB子进程持久化失败,redis 主进程将停止接受写操作,这样以一种强硬的方式让用户知道数据不能正确的持久化到磁盘, 否则就会没人注意到灾难的发生。如果后台保存进程重新启动工作了,redis 也将自动的允许写操作。

rdbcompression yes

Redis在使用RDB持久化的时候,会生成一个dump.rdb内存快照文件,那么持久化的时候,要不要对文件进行压缩,如果你想节约CPU资源,可以设置为no,但那样的话,rdb文件将会很大。默认是yes。

rdbchecksum yes

从Redis5之后,一个CRC64校验和就被存在rdb文件的尾部,校验可以确认rdb文件的完整性,但是它会占用10%左右的保存或加载rdb文件的资源,如果你为了让性能最大化,你可以设置为no,默认是true。   

dbfilename dump.rdb

RDB文件的名称,默认为dump.rdb

rdb-del-sync-files no

在没有持久性的情况下删除复制中使用的RDB文件,通常情况下保持默认即可。

dir ./

rdb文件的存放路径,默认值为./

2.4 RDB持久化优缺点

接下来,我们看看RDB持久化的优缺点:

优点:

(1)、持久化的过程可以异步化,不阻塞主线程处理客户端读写请求;

(2)、RDB持久化可以将rdb数据文件压缩成二进制,这样文件尺寸可以比较小,这样故障恢复的速度会比较快,在做主从复制的时候,还可以节省网络传输开销。

缺点:

(1)、上面的持久化流程图,我们可以看到,RDB持久化可能会造成部分数据的丢失,任何时刻如果Redis实例crash掉了,那么自最后一次执行RDB持久化开始的时间点之后所有新写入的数据都会丢失。

(2)、因为压缩技术跟随redis版本的升级可能存在迭进的现象,并非所有版本的redis都能够兼容旧版本的rdb文件,从而实现正常的数据恢复。

(3)、持久化的过程,如果内存数据比较多,仍然会有阻塞风险并可能显著增加Redis实例对内存的消耗,极端情况下甚至可能出现Redis直接崩溃掉的情况。

三、AOF持久化

3.1 AOF核心原理

上一节中,我们讲到RDB持久化可能会丢失数据,AOF(AppendOnly File)持久化是如何解决这个问题的呢?AOF持久化又会产生哪些新的问题呢?带着这些问题,我们一起研究一下AOF持久化的原理。

我们都知道,数据库比如说mysql,通常都是采用WAL(Write Ahead Log),也就是预写日志的方式来记录日志,保证数据的crash-safe。不过,redis跟mysql不太一样,redis是采用的一种后记日志的方式,如下图所示:

5a1bd7b2dd5505a5eb542b0627c3edfc.png

从上图分析,对于主进程来说,会有这样一些步骤:

(1)、处理客户端的命令;

(2)、再将客户端的命令同步写入到aof缓冲中;

(3)、然后再进行刷盘操作。

大家可能已经看出来了,这里会有两个问题:

问题1:为什么redis是先执行命令再写aof缓存?

这是因为redis写aof的时候,是将命令以文本的形式直接写入aof缓冲的,这里不会有语法校验。所以只要命令可以得到正确的执行,那么后面的写入就不需要再校验命令的语法,因为执行前已经校验过了。

问题2:刷盘策略如何选择?

选择no:也就是redis只管写入aof缓冲,此时调用了内核的write函数,而不会调用fsync函数,因此不主动刷盘,由操作系统负责刷盘,这种方式性能最高,但如果操作系统崩溃或主机掉电,数据丢失不可控。

选择always:每次redis都会主动进行fsync操作,直接数据刷盘,这种方式性能最差,但是不会丢失数据。

选择everysec:redis会创建子线程每秒钟去执行异步的刷盘操作,这种情况下,其实就是在性能与数据安全性所做的trade-off,最多只会丢失1秒的数据。

说完了主进程,接下来聊聊负责AOF重写的子进程,在聊这个问题之前,我们先说说为什么需要进行AOF重写。

因为redis的主进程是以文本追加的方式不断的写文件,这种方式的好处就是可以使用磁盘的顺序写降低IO操作导致的性能损耗,但是问题在于这种方式也会产生新的问题,那就是因为很多时候,对某一个key来来回回的写入操作可能达到几百条,比如说像下面这样:

第1条命令:set foo 0

第2条命令:set foo 111

第3条命令:set foo 222

....

第200条命令:set foo 200

像这样的命令序列,如果都写在aof文件中,那么key一旦多了,aof文件就会变得非常的巨大,而我们最后在做数据恢复的时候,只需要执行最后一条命令:set foo 200就行了。

这就是AOF重写的动机所在。

这里还有另外一个跟RDB持久化类似的问题:子进程去做AOF重写去了,如果这个时候主进程有新数据写入了怎么办?因为基于主进程的COW(Copy Of Write)机制,也就是写时复制,如果主进程中有新数据写入,主进程会申请新的内存块来写入新数据,那么子进程对这些新内存块的数据并不会可见,因为子进程的页表中保存的还是原来fork子进程时刻的内存块。那么这种情况下,主进程会等子进程完成了AOF初始重写(也就是根据子进程自己的页表所见到的内存数据的一次性全量重写)后,再会刚才主进程新收到的写入命令,重新一次性发给子进程,子进程再重放刚刚重写过程中的新写入的命令。

这里可能你还会有一个问题:就是为什么重写的时候,不直接在原有的AOF文件上进行覆盖写呢?

那是如果两个进程同时对同一个文件写的时候,如果并发高一点可能会导致严重的IO资源竞争问题,这可能会导致主进程的阻塞,从而降低redis的性能;另外,如果直接在原AOF文件上进行覆盖写,万一重写失败了,可能会破坏掉AOF文件的内容,这将直接导致原有的AOF文件无法用于数据恢复。但是新建一个新的用于重写的AOF文件就不一样了,因为毕竟磁盘很大,而且一旦重写失败了还可以删除掉再写一次都可以。

3.2 AOF持久化的问题及应对

AOF持久化,其实也会存在着一些问题:

问题1:fork子进程的时候,这个过程是阻塞的,因为操作系统要拷贝一些主进程的数据结构给子进程,其中很重要的一项就是页表,对页表的拷贝将完全阻塞主进程,阻塞的长短取决于页表的大小,这个问题其实在RDB持久化中也是存在的。

问题2:大家都知道操作系统分配内存是按页为单位进行分配的,以centos 7为例,它默认4k,如果在子进程进行初始重写的过程当中,父进程此时写入了一个bigkey,那么父进程就需要重新申请大块内存(根据写时复制机制,主进程在有页写入的情况下需要与子进程进行内存分离),那么这个大块内存申请过程的耗时将会变长,可能会产阻塞风险。如果开启了内存大页机制,比如说最小分配单元是2M/页的话,那么这个阻塞风险会更大一些。

针对问题1:不建议redis单实例的内存太大,最好不要超过8G内存,如果单机不够存储,可以考虑集群化的解决方案。

针对问题2:建议是在Redis实例运行的机器上,我们关闭掉大页(Huge Page)分配机制,同时尽可能减少在Redis中存储过多的bigkey,因为bigkey的内存释放也是有风险的,由于篇幅的关系,这里先暂不展开,如果你有兴趣,可以自己了解一下。

3.3 AOF持久化配置

接下来,我们再来介绍一下AOF的配置参数:

appendonly no

是否启用aof持久化方式 。即是否在每次更新操作后进行日志记录,默认配置是no,即在采用异步方式把数据写入到磁盘,如果不开启,可能会在断电时导致部分数据丢失。

appendfilename "appendonly.aof"

配置日志文件名,默认为appendonly.aof

appendfsync everysec

aof文件刷新的频率。默认为everysec,共有三种:

no 依靠OS进行刷新,redis不主动刷新AOF,这样性能最好,但数据安全性就差。

always 每提交一个修改命令都调用fsync刷新到AOF文件,性能最差,但数据安全性最好。

everysec 每秒钟都调用fsync刷新到AOF文件,性能也比较好,但可能会丢失一秒以内的数据。

如何选取这项配置的参数值,这需要你根据业务进行trade-off。

no-appendfsync-on-rewrite no

指定是否在后台aof文件rewrite期间调用fsync,默认为no,表示要调用fsync(无论后台是否有子进程在刷盘)。Redis在后台写RDB文件或重写AOF文件期间会存在大量磁盘IO,此时,在某些linux系统中,调用fsync可能会阻塞。

auto-aof-rewrite-percentage 100

aof文件增长比例,指当前aof文件比上次重写的增长比例大小。aof重写即在aof文件在一定大小之后,重新将整个内存写到aof文件当中,以反映最新的状态(相当于bgsave)。这样就避免了,aof文件过大而实际内存数据小的问题(频繁修改数据问题)。

auto-aof-rewrite-min-size 64mb

aof文件重写最小的文件大小,即最开始aof文件必须要达到这个文件时才触发,后面的每次重写就不会根据这个变量了(根据上一次重写完成之后的大小).此变量仅初始化启动redis有效.如果是redis恢复时,则lastSize等于初始aof文件大小。

aof-load-truncated yes

指redis在恢复时,会忽略最后一条可能存在问题的指令。默认值yes。即在aof写入时,可能存在指令写错的问题(突然断电,写了一半),这种情况下,配置为yes,就会忽略最后一条指令,并正常进行数据恢复,而no会直接恢复失败。

3.4 AOF优缺点

接下来,我们再来分析一下AOF持久化的优缺点:

优点:

(1)、数据安全性更高;

(2)、刷盘策略比较灵活(类似于mysql中redo log的刷盘配置);

(3)、持久化的容错性较好,即使失败了,也可以通过redis-check-aof工具进行修复;

(4)、如果最后一条命令执行的是flushall,只要没有执行AOF重写,拿着原来的aof文件,删除掉最后一条flushall命令就可以恢复数据了。

缺点:

(1)、相同的内存数据,通常情况下,aof文件要比同等内存数据的的rdb文件要大很多;

(2)、aof重写的过程,跟RDB持久化类似的有阻塞的风险;

(3)、主从同步的时候,数据传输慢,数据恢复的速度会比RDB慢很多;

(4)、如果配置主进程同步写aof文件(即appendfsync always),性能表现往往会非常糟糕。

四、混合持久化

4.1 基本概念

什么是混合持久化呢?

redis自4.0版本之后,就开始支持混合持久化,所谓混合持久化其实就是综合了RDB和AOF的优缺点之后,结合使用RDB持久化和AOF持久化两种手段,来达到一种性能与数据安全性的最佳平衡。

4.2 混合持久化配置

Redis提供了下面这个参数来配置混合持久化:

aof-use-rdb-preamble yes

在开启了这个功能之后,AOF重写产生的文件将同时包含RDB格式的内容和AOF格式的内容,其中RDB格式的内容用于记录已有的数据,而AOF格式的内存则用于记录最近发生了变化的数据,这样Redis就可以同时兼有RDB持久化和AOF持久化的优点(既能够快速地生成重写文件,也能够在出现问题时,快速地载入数据)。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值