Redis持久化存储机制:从RDB到AOF再到混合持久化的配置与实践

概念简介

  持久化这个概念,和mysql事务的四大核心特性(原子性、一致性、持久性、隔离性)中的持久性是同一个意思。mysql的持久化实现方式是:把数据存储到硬盘上,重启主机后数据依然存在。

  redis的数据存在内存中,那怎么保证数据的持久化呢?答案同样是保存到硬盘上。可这样一来,redis和mysql不就没区别了吗?要知道redis最大的优势就是快,而快的主要原因正是数据存储在内存中。

  怎么解决这个矛盾呢?事实上redis的数据在内存和硬盘上都会存一份,理论上两者的数据是完全相同的,当然也可能存在差异,这取决于具体业务。

  增删改查等这些操作是在内存上完成,而硬盘上存的数据主要起到备份的作用,通常会在redis重启时用来恢复数据。虽然内存和硬盘都写,但redis通过一些策略依旧能保证高效。

redis的持久化策略有两种:

  • RDB(Redis DataBase):定期备份,比如每天更新一次,备份到硬盘。
  • AOF(Append Only File):实时备份,每操作一次数据就备份一次。

虽然是持久化保存,但如果硬盘坏了(而硬盘恰恰是电脑中最容易坏的部件),数据不就没了吗?针对这种情况,可以再拿另一块移动硬盘作为备用硬盘。

RDB持久化策略

RDB:定期把redis内存中所有数据,都写入硬盘中,生成一个快照。

它又分为两种触发方式:

  • 手动触发:在redis客户端通过指令触发
  • 自动触发:通过redis配置文件设置完成

手动触发有两个命令:

  • save:执行save时,redis会进行"快照生成"操作,此时会阻塞其他客户端命令,会导致类似于keys *的后果,一般不会用。
  • bgsave:即background,不会影响redis服务器处理其他客户端的请求和命令。

bgsave如何做到不影响其他客户端请求呢?其实是通过新开一个进程来完成的。

  1. 判断当前是否已经存在其他正在工作的子进程,比如现在已经有一个子进程在执行bgsave,此时就直接把当前bgsave返回
  2. 如果没有其他的工作子进程,就通过fork这样的系统调用创建出子进程。
  3. 子进程负责写文件,完成快照的生成。
  4. 完成后通知父进程。

这样一来,尽管数据很多,拷贝效率并不低,因为Linux有写时拷贝机制,只有在修改时才会进行真实的空间拷贝。

  完成快照之后会生成一个dump.rdb文件,如果已经写过了,也就是这个快照已经存在了,那么会被新的快照覆盖。即当执行rdb操作时,会把要生成的快照数据先保存到一个临时文件中,当这个快照生成后,再删除之前的rdb文件,把新的rdb文件名改成刚才的dump.rdb,保证rdb文件只有一个。

  dump.rdb是一个压缩的二进制文件,压缩需要消耗cpu但可以减少存储空间。

  那么dump.rdb被保存在哪呢?通过redis的配置文件可以查看,配置文件通常在/etc/redis中,名为redis.conf

打开dump.rdb文件:

  dump.rdb文件如果格式错误会导致加载失败,从而使服务启动失败,所以不能随便改动。但即使自己不动,也可能出现问题,比如网络传输过程中文件被破坏,同样会导致redis服务无法启动。redis也考虑到了这一点,提供了rdb文件检查工具:

RDB是怎么自动触发的呢?这需要我们查看配置文件

这里数值可以修改,但要注意生成一次rdb快照成本比较高,不能频繁执行这个操作。

  rdb生成不能太频繁,否则成本过高;但也不能太稀疏,否则会导致当前实时数据和快照数据不一致,如果在下一次快照生成前服务器挂了,那么中间这些数据就丢了。而AOF就是解决这个问题的主要方案。

这里可以更改生成rdb文件的名字。

演示:

快照前dump.rdb

更新数据并手动快照:

快照后dump.rdb数据:

重启服务后数据还在(系统将dump.rdb的数据导入内存)

那么我们更新数据后,不手动执行快照直接将服务重启看看会发生什么。

可以发现新修改的数据依旧没有丢失,查看dump.rdb文件确实也有了更新。这是什么原因呢?其实快照还有其他触发方式,就是在服务关闭的时候也会进行快照。

所以正常关闭服务并不用担心数据丢失问题,更怕的是异常关闭。

模拟一下,直接kill杀死服务:

异常场景下,并没有给服务保存快照的时间

验证快照保存是新文件替换旧文件,而不是在旧文件上更新 :通过查看inode编号(文件的唯一性标识)来判断。

save命令与bgsave以及自动触发快照不同:它不会创建子进程,也没有文件替换过程,而是直接在当前进程、当前文件中写入。

验证通过配置自动生成快照:

故意把rdb文件改坏会有什么后果?

会导致不可预期的后果:要么格式校验错误,redis启动失败;要么redis能启动,但数据已经坏了,不是原来的数据。

验证:

假如我们也不知道改了什么、无法恢复了,那么怎么让redis服务正常启动呢?删除这个坏掉的rdb文件(或者给它改个名字)即可,然后再手动启动服务。

当服务启动失败时,可能的原因有很多种,可以通过查日志来定位:/var/log/redis/路径下的redis-server.log即为日志文件。

RDB特点小结

  1. 周期性备份。
  2. 通过二进制组织数据,恢复更快,占用空间也更小。
  3. 不能实时保存数据,数据容易丢。
  4. 老版本的rdb放到新版本redis中可能有兼容性问题。

AOF持久化策略

AOF即append only file,类似于mysql的binlog,会把用户的每个操作都记录到文件中。当redis重新启动的时候,就会读取这个aof文件中的数据来恢复数据。

注意:aof默认是关闭的,一旦开启aof,rdb就不再生效了。

同样打开redis配置文件redis.conf,找到appendonly改no为yes就行。

更改后重启服务才能生效。文件所在的位置和rdb一样,而且同样可配置。

aof策略每次操作都会进行写入,通过文本的方式记录,如下:

通过kill -9杀死服务,服务重启后依旧不会出现数据丢失问题。

aof也是写入硬盘,而且这么频繁是否会影响redis的性能?

  实际上并没有影响到redis处理请求的速度。AOF机制并不是直接写入硬盘,而是先写到内存缓冲区,到一定量后再统一写入硬盘,从而降低了写入硬盘的次数。影响性能的主要是写入硬盘的次数而不是数据量,所以redis原有的吞吐并不会受影响。

这里就有这么一个问题:如果把数据写到缓冲区里,本质还是在内存中,那么进程挂了或是主机掉电了,是不是数据就丢失了?确实是这样,这也是这个策略的一大缺点。

关于刷新频率,是可以配置的,刷新频率越高数据可靠性越高,但性能越低,反之亦然。有下面这些策略:

  • always:写入立即刷新,数据可靠性最高,性能最低。
  • everysec(默认):每秒刷新,性能和可靠性之间的权衡。
  • no:由OS决定频率最低,可靠性最低,性能最高。

  aof文件记录的是redis的每一步操作,随着时间推移这个文件体积会越来越大,进而影响下次启动的时间。而这里面的操作又往往是冗余的,比如设置一个key、再删除这个key,等价于什么都没有做。

  而实际上我们只需要关注最终状态。因此redis提供了一个机制,针对aof文件整理操作,剔除冗余、合并操作,达到瘦身效果。

这个机制叫AOF重写,它的触发方式如下:

  • 手动触发:调用 bgrewriteaof 命令。
  • 自动触发:
    • auto-aof-rewrite-min-size:表示触发重写时AOF的最小文件大小,默认为64MB。
    • auto-aof-rewrite-percentage:代表当前AOF占用大小相比上次重写时增加的比例。

aof重写流程:

  重写只关心内存的最终状态,把内存中的数据直接读出来写给aof,所以并不用特意去整理旧的aof。最后再用新整理出来的aof替换旧的aof。

  子进程写数据的过程类似于rdb生成快照,不过格式不同,一个是文本,一个是二进制。

  在重写aof时父进程仍然在处理请求,而子进程里拿不到fork之后新来的请求,所以父进程会准备一个aof_rewrite_buf,把fork之后的新请求也写到旧的aof文件里。

  如果在执行bgrewriteaof时redis正在进行aof重写,那么不会再次执行,直接返回;如果发现当前redis正在生成rdb快照,则会等rdb生成完后再进行aof重写。

  既然fork后已经在写新的aof文件了,为什么还要把新请求写到旧的aof文件里,有什么意义呢?这是为了应对极端情况:如果不这样做,一旦重写进行到一半服务器挂了,那么这期间的新数据就都丢失了。

混合持久化

  在重写后我们可以打开appendonly.aof看一下,会发现里面和rdb一样是二进制文件。

  aof本来是按文本方式写文件的,但文本方式后续的成本非常高,比如占用空间大、服务启动慢。

所以redis引入了混合持久化,结合了rdb和aof两者的特点。

混合持久化默认是开启的:

注意:是在重写后触发。但在后续写aof时仍然是文本格式。

非常感谢您能耐心读完这篇文章。倘若您从中有所收获,还望多多支持呀!🎉

相关推荐
小张同学a.1 小时前
OpenStack 私有云实战 2—— Glance 镜像与 Nova 计算服务搭建
linux·运维·服务器·openstack
xiaoye-duck1 小时前
《Linux网络编程》TCP 网络编程(下):基于 TCP 多线程通用字典服务 + 远程命令执行实战
linux·服务器·tcp/ip
陈让然2 小时前
VS Code C/C++ 插件 cpptools 内存占用异常
linux·运维
2401_894915534 小时前
GEO 优化源码全解析:从搜索引擎到 AI 引擎的底层改写逻辑
java·服务器·前端·数据库·人工智能·分布式·搜索引擎
正儿八经的少年9 小时前
布隆过滤器(解决redis缓存穿透步骤之一)
数据库·redis·缓存
xrandzj10 小时前
MySQL8.0 从零通关核心操作手册(Ubuntu实战版)
数据库·mysql
我不会插花弄玉10 小时前
4.数据类型【由浅入深-MySQL】
数据库·mysql
denggun1234510 小时前
yield
前端·数据库·python
ltl10 小时前
QUIC 握手实现:Initial 到 1-RTT 的最小路径
linux