Redis
持久化
MySQL
的事务,有四个比较核心的特性:
- 原子性
- 一致性
- 持久性(和持久化一样),将数据存储在硬盘上,重启主机之后数据仍然存在
- 隔离性
redis
是一个内存数据库,把数据存储在内存中,是不持久的,就需要将redis
中的数据存储在硬盘上。
redis
最明显的优势就是效率高,快。
为了保证速度快,数据肯定还是得在内存中,但是为了持久,数据得在硬盘中,所以redis
就是内存和硬盘中都存,这样存储在两个地方的数据都是一样的。
代价是消耗了更多的空间,同样的数据存储了两遍,但是硬盘便宜没有太多的成本。
两边都存储但是实际上具体怎么写入硬盘还有不同的策略可以保证整体的效率足够高。
- 插入数据的时候,内存硬盘都写
- 查询数据的时候,直接从内存中读取
- 硬盘中的数据只在
redis
重启的时候用来恢复内存中的数据
Redis
持久化实现策略
Redis
提供了两种持久化⽅式:RDB
和 AOF
,即可以⽤两种策略将内存的数据保存到硬盘中 .
RDB
=> redis DataBase
定期把Redis
内存中的所有数据都存入到硬盘中,生成一个快照。
定期的实现方式有两种:
-
手动触发:
程序员通过
Redis
客户端执行特定的命令来触发快照生成。-
save:执行save的时候
Redis
就会全力以赴的执行快照生成操作,此时就会阻塞Redis
的其他客户端的命令,类似于keys*
的后果,Redis
系统服务可能就挂了。 -
bgsave
:background
不会影响Redis
服务器处理其他客户端的请求和命令。-
Redis
怎么做到的这种?并发编程的场景,多线程是一种方式,还可以使用其他方式,多进程的方式来完成的。 -
bgsave
执行流程Redis
服务器就是父进程,-
判定当前是否已经存在其他正在进行的子进程,比如现在已经有一个子进程 执行
bgsave
,此时就把当前的bgsave
返回。 -
如果没有其他的工作子进程,就通过
fork
这样的系统调用创建出一个子进程,子进程会复制一份父进程的数据包括进程控制块,程序地址空间,文件描述符表等。本来
Redis-Server
中存储了若干变量,保存了键值对数据。因为fork的进行,子进程的虚拟内存中也会得到和刚才父进程一样的变量数据。安排子进程去执行持久化操作,也就相当于把父进程本体给持久化了。
fork在进行内存拷贝的时候,不是简单的复制一份,而是执行写时拷贝的机制。如果父子进行的内存数据一样,此时就不会触发真正的拷贝动作,两个角色共用一份数据。但是当某一方要进行修改时就会触发真正的物理内存上的数据拷贝。
在这个场景中,绝大部分内存数据是不需要改变的,整体来说这个过程执行的还挺快的,短时间内父进程中不会存在大批的内存数据变化,写时拷贝也不会触发很多次。
-
子进程负责进行写文件,生成快照的过程。父进程继续接收客户端的请求,继续正常的提供服务。
-
子进程完成整体的持久化过程之后就会通知父进程,父进程更新统计信息之后子进程就可以销毁了。
-
-
RDB
文件是存放在Redis
的工作目录的,也是在Redis
配置文件中进行配置的。-
RDB
机制生成的镜像文件是二进制文件,把内存中的数据以压缩的形式保存到这个二进制文件中,消耗一定的CPU资源但是能节省存储空间。 -
后续
Redis
服务器重新启动,就会加载这个rdb
文件,如果发现格式错误就可能会加载失败,所以别轻易改。 -
Redis
提供了rdb
文件检测工具:redis-check-rdb*
。 -
当执行生成
rdb
镜像操作的时候,就会把要生成的快照数据先保存到一个临时文件中,当快照生成完毕之后再删除原来的rdb
文件,把新创造的临时的rdb
文件名改为刚才的dump.rdb
文件。 -
rdb
文件中的数据不是这边插入新的键值对之后就会立即更新,触发时机是包含手动触发(save,bgsave
),和自动触发(在配置文件中进行设置).所以在一段时间以内就可能存在数据上的偏差,那么如果在这一小段时间内服务器挂了,这些数据就丢了。sudo vim etc/redis/redis.conf
- 由上图可知比如两次生成
rdb
之间的间隔最少得是60s.12:00:00
生成了rdb
(硬盘上的快照数据和内存中一致)12:00:01
开始Redis
接收到大量的key的变化请求,12:01:00
生成下一个快照文件,两个快照中间的数据就丢了.
-
-
-
手动执行
bgsave
之后就会在/var/lib/redis
目录下生成dump.db
文件,当重启Redis
服务器之后,就会加载这个文件中保存的内容,实现持久化的操作. -
Redis
生成快照的操作,不仅仅是手动执行命令bgsave
才会触发,也可以自动触发.- 通过配置文件中的
save
执行M时间内修改N次… - 通过
shutdown
命令(Redis
里面的命令)关闭Redis
服务器也会触发.(属于正常关闭 service命令关闭) Redis
进行主从复制的时候,主节点也会自动生成RDB
快照,然后把rdb
快照文件内容传输给从节点.- 如果使用异常重启,比如使用
kill
命令直接终止Redis
服务器进程,此时就不会保存.
- 通过配置文件中的
-
bgsave
操作流程是创建子进程,子进程完成持久化操作.持久化会把数据写入到新的文件中,然后使用新的文件替换旧的文件.可以使用stat
命令,查看文件的inode
编号.如果发现发生了替换,说明已经是不同的文件了. -
如果是save命令,就不会触发子进程和文件替换逻辑,就直接在当前进程中往刚才的同一个文件中写入数据了.
-
-
自动触发:
在
Redis
配置文件中设置一下save A && B
,让Redis
每隔多长时间,每产生多少次修改就触发。- 执行
flushall
也会清空rdb
文件.
修改配置文件之后重启服务器之后才能生效.
save ""
关闭自动生成快照.- 如果把
RDB
文件故意搞坏会发生什么呢?
注意: 使用kill进程的方式重新启动
Redis
服务器当手动的把rdb
文件内容搞坏时,这样才能验证实验效果.重启之后将搞坏的
rdb
文件加载到内存中,貌似是没有收到影响,能够正确获取到数据,具体Redis
会咋样,具体取决于文件坏的地方在哪里.如果确实改错地方了,可能Redis
服务器都启动不了了,redis
日志中会有记录. - 执行
RDB
优缺点
RDB
是⼀个紧凑压缩的⼆进制⽂件,代表Redis
在某个时间点上的数据快照。⾮常适⽤于备份,全量复制等场景。⽐如每 6 ⼩时执⾏bgsave
备份,并把RDB
⽂件复制到远程机器或者⽂件系统中(如hdfs
)⽤于灾备。
Redis
加载RDB
恢复数据远远快于AOF
的⽅式。RDB
是使用二进制的方式,所以比文本解析的方式更快.
RDB
⽅式数据没办法做到实时持久化 / 秒级持久化。因为bgsave
每次运⾏都要执⾏ fork 创建⼦进程,属于重量级操作,频繁执⾏成本过⾼。
RDB
⽂件使⽤特定⼆进制格式保存,Redis
版本演进过程中有多个RDB
版本,兼容性可能有⻛险.
AOF
=> Append Only File
实时备份数据
类似于MySQL
中的binlog
存储的是用户的每个操作,都记录到文件中.当Redis
重新启动的时候就会读取这个aof
文件中的内容,用来回复数据.
-
当开启
aof
的时候RDB
就不再生效了,启动的时候就不再读取. -
默认是关闭的,需要在配置文件中开启,修改配置文件后要重新启动
Redis
服务器.
aof
是一个文本文件,每次进行的操作,都会被记录在文本文件中.通过一些特殊符号作为分隔符,来对命令的细节做出区分.
aof
性能问题
redis
虽然是单线程,但是速度很快,原因是只是操作内存,引入aof
之后又要写硬盘.实际上并没有影响到Redis
处理请求的速度.
-
aof
机制并没直接让工作线程写入硬盘,而是先写入一个内存中的缓冲区积累一些之后再统一写入硬盘,大大减少了写入硬盘的次数.-
写硬盘的效率和每一次写入的数据多少关系不大,主要是写入硬盘的次数影响最大.
-
如果在写入缓冲区的时候进程挂了,缓冲区的数据就丢了.
Redis
给出选项,让程序员根据实际情况来取舍,缓冲区的刷新策略.-
刷新频率越高,性能影响越大,数据可靠性越高.
-
刷新频率越低,性能影响越小,数据可靠性越低.
查看配置文件中的默认缓冲区同步策略就是
everysec
.
-
-
-
硬盘上读写数据,顺序读写的速度还是比较快的(还是比操作内存慢很多),随机访问则速度比较慢.
AOF
是每次把新的操作写入到原有文件的末尾,数据顺序写入.
aof
重写
AOF
文件持续增长,体积越来越大,会影响下次redis
启动的时间,因为Redis
启动的时候要读取AOF
启动时候要读取文件的内容.
AOF
文件中一些内容是冗余的,比如一下举例操作:
lpush key 111
lpush key 222
lpush key 333
# 整合为一条命令即可
lpush key 111 222 333set key 111
set key 222
set key 333
# 只关注最后一条更新命令
set key 333set key 111
del key
set key 222
del key
# 啥都不做就可以
因此Redis
存在一个机制,能够针对aof
文件进行整理操作,能够剔除其中的冗余操作,并且合并一些操作,达到给aof
文件瘦身的效果=>aof
的重写机制.
AOF
重写过程可以手动触发和自动触发:
-
手动触发:调用
bgrewriteaof
命令。 -
自动触发:根据
auto-aof-rewrite-min-size
和auto-aof-rewrite-percentage
参数确定自动触发时机。-
auto-aof-rewrite-min-size
:表示触发重写时AOF
的最小文件大小,默认为64MB
。 -
auto-aof-rewrite-percentage
:代表当前AOF
占用大小相比较上次重写时增加的比例。
-
aof
重写流程
- 执行
AOF
重写请求。
如果当前进程正在执行AOF
重写,请求不执行。如果当前进程正在执行bgsave
操作,重写命令延迟到bgsave
完成之后再执行。 - 父进程执行
fork
创建子进程。 - 重写
- 主进程 fork 之后,继续响应其他命令。所有修改操作写入
AOF
缓冲区并根据appendfsync
策略同步到硬盘,保证AOF
文件机制正确。 - 子进程只有 fork之前的所有内存信息,父进程中需要将 fork之后这段时间的修改操作写入
AOF
重写缓冲区中,aof-rewrite-buf
.
- 子进程根据内存快照,将命令合并到新的
AOF
文件中。
- 重写的时候并不关注
aof
原有文件原来都有啥,只是关心内存中最终的数据状态,就已经相当于把aof
文件结果整理后的模样了. aof
重写是按照aof
要求的文本格式来生成的.
- 子进程完成重写
- 新文件写入后,子进程发送信号给父进程,告知完成瘦身操作了。
- 父进程把
AOF
重写缓冲区内临时保存的命令追加到新AOF
文件中。 - 用新
AOF
文件替换老AOF
文件。
- 如果在执行
bgrewriteaof
的时候,当前Redis
重写流程正在进行,此时不会再执行重写操作,直接返回. - 如果在执行
bgrewriteaof
的时候,发现当前Redis
正在进行RDB
快照,此时会等待快照生成结束之后再进行重写操作.
-
RDB
理念就是定期备份,并不是实时备份,没有好坏之分,只有使用场景的不同. -
父进程
fork
完毕之后,子进程已经再写新的aof
文件了,父进程还有必要写这个即将消亡的文件吗? 有,如果在重写的过程中服务器挂了,这个即将消亡的文件算是一个备份. -
aof
本来是按照文本的方式写入文件,但是文本的方式写文件,后续加载的成本还是比较高的,Redis
就引入了混合持久化的方式,结合了RDB
和aof
的特点,按照aof
的方式,每一个请求操作都记录在文件中,在出发aof
重写之后,就会把当前内存的状态按照rdb
的二进制格式写入到新的aof
文件中,后续再进行的操作,仍然是按照aof
文本的方式追加到文件的后面.aof-use-rdb preamble yes
在redis.conf
中进行配置,打开混合持久化选项.
当同时存在rdb
和aof
机制,以aof
为主.
总结
Redis
提供了两种持久化方案:RDB
和AOF
。RDB
视为内存的快照,产生的内容更为紧凑,占用空间较小,恢复时速度更快。但产生RDB
的开
销较大,不适合进行实时持久化,一般用于冷备和主从复制。AOF
视为对修改命令保存,在恢复时需要重放命令。并且有重写机制来定期压缩AOF
文件。RDB
和AOF
都使用fork
创建子进程,利用 Linux 子进程拥有父进程内存快照的特点进行持久化,
尽可能不影响主进程继续处理后续命令。
提供了两种持久化方案:
RDB和
AOF`。RDB
视为内存的快照,产生的内容更为紧凑,占用空间较小,恢复时速度更快。但产生RDB
的开
销较大,不适合进行实时持久化,一般用于冷备和主从复制。AOF
视为对修改命令保存,在恢复时需要重放命令。并且有重写机制来定期压缩AOF
文件。RDB
和AOF
都使用fork
创建子进程,利用 Linux 子进程拥有父进程内存快照的特点进行持久化,
尽可能不影响主进程继续处理后续命令。