关于Redis的持久化

Redis与MySQL的不同

MySQL的事务,有四个核心特性:原子性、一致性、持久性和隔离性

把数据存储在磁盘上就是持久化的,把数据存储在内存上则是不持久化的。区别在于重启进程/主机后,数据是否存在。

而Redis是一个内存数据库,是把数据存储在内存中的!!!

而相较于MySQL,Redis最大的优势就是快、效率高

但是如果要保证数据的持久化,就必须要把数据写入磁盘,但又不能破坏掉Redis的高效,所以Redis采用以下策略:

  • 插入一个新数据的时候,需要把这份数据同时写入内存和磁盘

  • 查询某个数据的时候,直接从内存中读取

  • 硬盘中的数据只是在Redis重启的时候,用来恢复内存中的数据的

这样做的代价就是消耗了更多的空间,但是硬盘比较便宜,这样的开销并不会带来太多成本。

问题在于,Redis查询数据时访问内存,效率确实快很多,可是插入数据时,既要写内存又要写硬盘,多了IO操作,该怎么保证效率?

Redis持久化的策略

RDB(Redis DataBase) 定期备份

定期地把Redis内存中的所有数据,都写入硬盘中,生成一个"快照",后续Redis一旦重启,就可以根据快照恢复内存数据

手动触发

程序员通过Redis客户端,执行特定的命令(save、bgsave),触发生成快照

  • save命令在前台进行,执行时会全力以赴地进行生成快照的操作,会阻塞其他的客户端命令,造成类似于keys*的效果

  • bgsave在后台进行,并不影响其他客户端请求,注意这里是采用多进程的方式完成并发编程,Redis中没有多线程

bgsave的工作流程:

当父进程也就是Redis服务器收到一个bgsave的命令,会先判定当前是否已经存在其他正在工作的子进程,如果当前已经有一个子进程在执行bgsave,就直接把当前的bgsave命令返回,否则就创建一个子进程执行bgsave,也就是Redis中任何时候只能有一个客户端执行bgsave。子进程完成整体的持久化过程之后,会通知父进程,父进程进行一些统计信息的更新,随后销毁子进程。

执行RDB会生成一个镜像文件dump.rdb,把内存中的数据以压缩的形式保存到该二进制文件中,虽然需要消耗一定的CPU资源,但是可以节省存储空间

RDB持久化操作是可以触发多次的,在已经存在dump.rdb 文件的情况下再进行rdb操作,会先把要生成的快照数据保存在一个临时文件中,快照生成完毕后会删除之前的rdb文件,再把临时文件名称改为dump.rdb,自始至终都只有一个dump.rdb文件

自动触发

在Redis配置文件中,设置让Redis每隔多长时间/每产生多少次修改就触发生成快照

但因为生成快照的操作不能太频繁,所以快照数据和实时数据可能会存在偏差,所以在不满足生成快照条件的时候,如果Redis服务器挂了,就会导致一部分数据丢失

但是Redis的自动触发生成快照不仅可以通过配置文件的方式,还可以通过Redis的shutdown命令触发,当Redis进行主从复制的时候,主节点也会自动生成rdb快照,然后把rdb快照文件内容传输给从节点。

注意,如果是通过正常流程重新启动Redis服务器,此时Redis服务器会在提出的时候,自动触发生成rdb操作,但如果是异常重启(kill -9 或者 服务器掉电),服务器就来不及生成rdb,此时内存中尚未保存到快照中的数据就会丢失

总结:

  • Redis采用rdb生成快照的操作要对内存中所有数据进行保存,所以适用于全量数据保存的情况

  • 由于rdb操作最终保存的二进制,所以加载数据时要快于AOF(用文本的形式保存)

  • RDB方式没有办法做到实时持久化/秒级持久化,因为bgsave每次执行都要执行fork创建子进程,属于重量级操作,频繁操作成本过高

  • RDB文件使用二进制格式保存,Redis版本演进过程中会有多个RDB版本,兼容性可能会有风险

RDB最大的问题就是,不能实时的持久化保存数据,在两次生成快照之间,实时的数据可能会随着重启而丢失。

AOF(Append Only File) 实时备份

AOF的保存数据方式类似于MySQL中的 binlog,会把用户的每个操作,记录在文件中,当Redis重启的时候,就会读取这个aof文件中的内容,用来恢复数据(当开启aof的时候,就默认rdb失效,不再读取rdb文件中的内容)

还是和RDB同样的问题,Redis把操作写进aof文件,又要写内存,又要写磁盘,怎么保证速度?

  1. AOF机制并非是直接让工作线程把数据写入硬盘,而是先写入一个内存中的缓冲区,积累到一定量之后,再统一写进硬盘

  2. AOF每次都会把新的操作写到原有文件内容的末尾,属于顺序写入,硬盘上读写数据,顺序读写的速度比随机要快(当然还是比内存慢)

跟RDB相同,如果在内存缓冲区中的数据量不够刷新到磁盘时,出现了服务器掉电的情况,这部分数据就会丢失。所以,针对这个情况,Redis给出了一些选项供程序员选择(在刷新频率和效率之间做一个抉择),就像MySQL中的事务隔离级别,安全性和效率往往不能兼顾

Redis的重写机制(rewrite)

对于AOF备份,当操作越来越多,AOF文件的体积就越来越大,虽说磁盘空间不值钱,但是在Redis重启加载数据的时候会有很大的消耗,而且AOF中很可能有一些数据时冗余的(比如先插入数据,再删除数据,实际上Redis关注的只是数据最后的状态)

所以Redis中有一个机制,可以对AOF文件中的冗余信息进行缩减,达成"瘦身"的效果,也就是重写机制。

AOF重写和RDB一样也分为手动和自动,大致原理相同,不再赘述。

AOF重写流程:

创建子进程,父进程仍然负责接收请求,子进程负责对AOF文件进行重写,重写过程中不关心原来的AOF文件中的所有操作记录,只关心当前内存中的数据状态,把当前最新的数据状态以AOF的形式写入一个新文件,把原有的AOF文件删除,再把新文件改为AOF即可

在父进程创建子进程之后,子进程重写之间,父进程可能还会接收到新的请求,这时还会往旧的AOF文件中写入数据,当子进程重写完成后覆盖原有的AOF文件,新写入的数据不就丢失了吗?

为了避免这个情况,Redis的AOF重写机制中,父进程创建完子进程之后,会有另外一个aof_rewrite_buf缓冲区,用于存放fork之后收到的数据。这时父进程收到的新的数据不仅要写入原来的AOF文件,还要写入这个缓冲区,当子进程完成重写之后会通过信号 通知父进程,父进程就会把缓冲区中的内容页写入新的AOF文件中。

特殊情况:

  • 如果在执行bgrewriteaof的时候,发现当前Redis正在进行AOF重写,此时就不会再次执行AOF重写,直接返回

  • 如果在执行bgrewriteaof的时候,发现当前Redis正在生成rdb快照,此时AOF重写操作就会等待rdb快照生成完毕之后,再进行AOF重写

这里可以发现,RDB和AOF的不同,RDB对于fork之后的数据,就直接置之不理了,并不会采用rewrite_buf的方式来处理。

RDB本身的设计理念就是用来"定期备份"的,只要是定期备份,就难以和最新的数据保持一致,但定期备份和实时备份没有绝对的谁好谁坏,还是要看具体的使用场景

既然父进程fork之后,子进程就会往新的AOF文件中写入数据,那么父进程只需要往aof_rewrite_buf中写入数据就行了,为什么还要向旧的AOF文件中写?

考虑极端情况,子进程重写AOF过程中,服务器挂了,子进程内存中的数据就会丢失,新的AOF文件内容还不完整,这时需要父进程中旧的AOF文件保证数据的完整性。

但是,AOF文件并不是任何时候都只有文本类型的数据,可能还包含二进制数据。因为文本的方式写文件,后续的加载成本比较高。于是,Redis引入了"混合持久化"的方式,结合了rdb 和 aof 的特点。针对每一个请求/操作,按照AOF的方式记录进文件;在触发AOF重写之后,会把当前内存中的状态按照RDB的二进制格式写入新的AOF文件中;后续有新的操作,仍然是按照AOF文本的方式追加到文件后面。

当Redis中同时存在AOF文件和RDB快照的时候,以AOF为主,RDB直接忽略,因为AOF中的数据更全

相关推荐
清水白石0086 分钟前
从一个“支付状态不一致“的bug,看大型分布式系统的“隐藏杀机“
java·数据库·bug
qq_364371726 分钟前
Vue 内置组件 keep-alive 中 LRU 缓存淘汰策略和实现
前端·vue.js·缓存
Python私教5 小时前
model中能定义字段声明不存储到数据库吗
数据库·oracle
BestandW1shEs7 小时前
谈谈Mysql的常见基础问题
数据库·mysql
重生之Java开发工程师7 小时前
MySQL中的CAST类型转换函数
数据库·sql·mysql
教练、我想打篮球7 小时前
66 mysql 的 表自增长锁
数据库·mysql
Ljw...7 小时前
表的操作(MySQL)
数据库·mysql·表的操作
哥谭居民00017 小时前
MySQL的权限管理机制--授权表
数据库
wqq_9922502777 小时前
ssm旅游推荐系统的设计与开发
数据库·旅游