Redis挂了,我的数据还能恢复吗(一)?

一、内存和磁盘的区别

计算机的CPU只能读取内存里的东西,无法直接磁盘读取数据。因此程序和数据必须先从磁盘中读取到内存中,才能被CPU使用。

电脑硬盘作为计算机最主要的存储设备,具有比较稳定的数据存储能力,又称之为外存,从介质上来区分,可分为 SSD (固态硬盘) 和 HD (普通磁盘),SSD 主要是利用电子来存储数据,而 HD 是利用磁性来存储数据,相对来说,SSD IO 相对较快,但是比较贵,而 HD 相对速度慢,容易损坏,但是价格便宜。

内存用于暂时存放CPU中的运算数据,与硬盘等外部存储器交换的数据。它是外存与CPU进行沟通的桥梁,计算机中所有程序的运行都在内存中进行,内存性能的强弱影响计算机整体发挥的水平。只要计算机开始运行,操作系统就会把需要运算的数据从内存调到CPU中进行运算,当运算完成,CPU将结果传送出来。

从计算机专业的术语来说,内存常常指的是内存条大小,而不是存储介质大小,因此,日常生活中说的手机内存多少,应该是指手机的运行内存,而不是存储大小。

其中,三者的关系可以从下面的简图看出来。
在这里插入图片描述

二、redis 是什么?

redis 是可基于内存亦可持久化的日志型、Key-Value数据库,支持多种语言的API,其单机的qps可以达到几万,而mysql一般只有几千,redis 被丰富用在各种缓存的场景,由于它支持很多的数据结构,很好的弥补了 memcached 的不足,因此深受广大程序员的欢迎,可以说是除了 mysql 数据库之外,第二大常用数据库。

其基于内存的读写方式,有着很大的读写性能优势,但是我们知道存储在内存的东西是很容易丢的,一旦机器重启或者断电,内存的数据便会丢失,为了更好的弥补这种问题,redis 同时也支持持久化存储。主要是 AOF 机制 和 RDB 机制。

三、redis 的 AOF 机制

redis 在作为数据缓存的场景中,最新的数据往往在数据库中,当redis服务器 宕机数据丢失后,一般的做法是程序会从数据库中重新读取数据并存储进 redis 中,但是此时可能会对数据库带来很大的压力,而且读取的性能肯定低于redis ,导致使用这些数据的应用程序响应也会变慢。

AOF 即 Append Only File 是一种写后日志,也就是说,redis 先执行命令,把数据写入内存后,再记录日志。

在这里插入图片描述

和数据库的 WAL 机制不同,redis 是先干活再记账。为啥要这么做呢?

3.1 AOF 文件的数据格式

我们可以在 redis.conf 中打开 aof ,默认是关闭的。

#aof 开关
appendonly yes

#存储文件名称
appendfilename "appendonly.aof"


# 存储策略
# appendfsync always
appendfsync everysec
# appendfsync no


# 工作目录
dir /var/lib/redis

此时,我们在redis-cli 中,输入

set name mclink

再打开 /var/lib/redis/appendonly.aof 文件

root@VM-7-115-ubuntu:/var/lib/redis# cat appendonly.aof
*2
$6
SELECT
$1
0
*3
$3
set
$4
name
$6
mclink

其中,“*3”表示当前命令有三个部分,每部分都是由“$+数字”开头,后面紧跟着具体的命令、键或值。这里,“数字”表示这部分中的命令、键或值一共有多少字节。例如,“$3 set”表示这部分有 3 个字节,也就是“set”命令。

我们可以看到实际上,aof 记录了两个命令。

select 0
set name mclink
3.2 先吃饭后记账的好处和风险

到底是应该先给钱再吃饭,还是吃饭再给钱呢。抛开场景说话都是在耍流氓。

所以我们要针对 redis 的特性来分析。

首先 redis 为了避免额外的检查开销,在向 AOF 里面记录日志的时候,并不会先去对这些命令进行语法检查,如果先记日志再执行命令的话,日志中就有可能记录了错误的命令,Redis 在使用日志恢复数据时,就可能会出错。

而且在吃完饭后再给钱,也不会影响到吃饭的进度,因此在命令执行后才记录日志,不会阻塞当前的写操作。

但是,如果吃饭了忘记了记账,也会有丢失数据的风险。

将日志文件写到磁盘,当磁盘压力比较大的时候,会导致写盘慢,而写日志的操作也是在主线程中执行的,这样虽然不会阻塞当前命令的执行,却可能影响后面的命令执行。

举个例子,假设某个厨师有一个习惯,每次做菜都会有记账的情况,并且记完这次做的菜之后才会做下一道。记账的平均时间设置为n ,做菜的平均时间设置为 m。

如果厨师先记账后做菜,对当前的客人来说,等待的时间为 m + n
如果厨师先做菜后记账,对当前的客人来说,等待的时间为 m

因此,对当前客人来说,先做菜比较好

如果厨师先记账后做菜,对下一个客人来说,等待的时间为 m + n + n + m
如果厨师先做菜后记账,对下一个客人来说,等待的时间为 m + n + m

似乎,对下一个客人来说,也是先做菜比较好。

后来厨师发现,每次做菜都记,似乎有点浪费时间,于是他想是不是可以把批量做几道菜后再批量记账呢。或者是每十分钟记一次呢。

这就是写日志的时机问题。

四、三种写回策略

redis 的 aof 机制目前给了我们三种写回机制。
也就是 appendfsync 的三个可选值

# The fsync() call tells the Operating System to actually write data on disk
# instead of waiting for more data in the output buffer. Some OS will really flush
# data on disk, some other OS will just try to do it ASAP.
#
# Redis supports three different modes:
#
# no: don't fsync, just let the OS flush the data when it wants. Faster.
# always: fsync after every write to the append only log. Slow, Safest.
# everysec: fsync only one time every second. Compromise.
#
# The default is "everysec", as that's usually the right compromise between
# speed and data safety. It's up to you to understand if you can relax this to
# "no" that will let the operating system flush the output buffer when
# it wants, for better performances (but if you can live with the idea of
# some data loss consider the default persistence mode that's snapshotting),
# or on the contrary, use "always" that's very slow but a bit safer than
# everysec.
#
# More details please check the following article:
# http://antirez.com/post/redis-persistence-demystified.html
#
# If unsure, use "everysec".

# appendfsync always
appendfsync everysec
# appendfsync no

官方的说明我就不一一翻译了。

我们可以看到,官方分别提供了三种写回策略,分别是

  • no : don’t fsync, just let the OS flush the data when it wants. Faster(让操作系统决定同步时间,性能高)
  • always: fsync after every write to the append only log. Slow, Safest. (同步写回,每个写命令执行完就跟着写日志,性能低,数据安全性高)
  • everysec: fsync only one time every second. Compromise.(每秒写回,折中方案)

其实最关键的还是两个点,一个是尽量避免主线程阻塞问题,另一个就是数据丢失问题。

因此这三个选项,需要根据真实场景来做选择。

当你选择了 no, 那么说明你对性能的需求大于你对数据丢失风险的评估
当你选择了 always, 那么说明你觉得数据安全比性能更加重要
如果你无法抉择,那么就选 everysec,这是一个折中方案,处理上面两个的中间情况,性能较好并且宕机时丢失的数据少(只会丢失1s的数据)

简单总结如下图:

配置项写回时机优点缺点
NO操作系统控制性能最好宕机数据丢失多
Everysec每秒写一次性能适中宕机丢1秒内数据
Always同步写数据基本不丢失性能差

五、AOF 重写机制

5.1 重写流程

有时候,记账的本子经常会有一些无用的账目,比如说

今日账目

甲:+ 100 ,-20 ,+20 ,-50
乙: + 300
丙: +30 , -30

其实,老板只想知道,每个人的账目情况。对甲来说,实际上 100-20+20-50 等效于 100 - 50 =+50,对乙来说就是+300 ,对丙来说 30 -30 等效于+0

所以有时候,某些命令并没有多大的作用。我们只需要关注最后的结果。无论你如何操作,只要你能保持最终一致,就可以简化账本,这就是 aof 的重写机制。

例如说:

在这里插入图片描述

当我们对一个列表先后做了 6 次修改操作后,列表的最后状态是[“D”, “C”, “N”],此时,我们只需要 LPUSH u:list “N”, “C”, "D"这一条命令就能实现该数据的恢复,这就可以节省五条命令的空间。而对于被修改过成百上千次的键值对来说,重写能节省的空间就更加大了。

此时,你会想,这个帮忙简化账本的人,还是老板自己吗,那么这样的话,老板又要记账又要简化账本,又要照顾客人,很容易造成阻塞吧。

对的,Redis 的 AOF 重写过程实际上是后台的子进程 bgrewriteaof 来完成的。fork子进程时,子进程是会拷贝父进程的页表,即虚实映射关系,而不会拷贝物理内存。子进程复制了父进程页表,也能共享访问父进程的内存数据了,此时,类似于有了父进程的所有内存数据,因此 bgrewriteaof 进程也就等效有着父进程的数据。

既然用了子进程来异步重写,那么理论上就不会造成主线程的阻塞。不过重写也是需要一定的时间的,在此期间,如果还需要增加日志,该如何同步呢?

我们会在启动重写机制的时候,新增一个 AOF 重写缓冲,专门用来存重写过程中新增的命令。也就是说在重写过程中,主线程不仅正常的写原来的AOF 日志缓冲,还需要写 AOF 重写缓冲。这样,重写的日志也不会丢失最新的操作。等重写完了后,再把 AOF重写缓冲中的命令再写进去,这样就保证了 AOF 的完整性,此时就可以用重写后的AOF 文件来取代旧的了,这里你可能会问,为啥不直接用原有的 AOF 缓冲,而要新搞一个 AOF 重写缓冲,这是为了避免同文件两进程竞争资源的问题,同时也是为了避免重写失败时污染到原有的 AOF 日志。

5.2 重写时机

目前redis 提供了两个配置项在控制AOF重写的触发时机:

  • auto-aof-rewrite-min-size: 表示运行AOF重写时文件的最小大小,默认为64MB
  • auto-aof-rewrite-percentage: 这个值的计算方法是:当前AOF文件大小和上一次重写后AOF文件大小的差值,再除以上一次重写后AOF文件大小。也就是当前AOF文件比上一次重写后AOF文件的增量大小,和上一次重写后AOF文件大小的比值。

AOF文件大小同时超出上面这两个配置项时,就会触发AOF重写。

六 总结

本篇文章,我们聊了 内存和磁盘 的区别,以及 redis 确保数据可靠性的一种机制(AOF)。通过三种写回策略我们可以发现, redis 在设计的时候考虑到不同的使用场景,软件设计是没有银弹的,必须要有抉择,是要重视性能还是要重视数据可靠性,往往需要取舍。这也是软件设计中经常需要关注到的点,在不同的业务场景中,侧重点都不相同,因此,根据你的业务选择正确的 redis 配置,也是十分重要的。不要过分依赖默认配置,否则很容易在未来吃到大亏。

下一篇我们继续聊聊 redis 的另一种持久化保证机制 – RDB 机制

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MClink

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值