redis是运行在内存中的数据库,数据的存储位置在内存这使得它具有更快的数据读写速度,但这并不意味着关个机数据全没了,redis有他自己的持久化机制
AOF持久化
简单来说就是创建一个AOF日志,它的作用是记录执行过的各种写指令,下次恢复状态时再执行一遍。
向AOF日志中写的时候会造成一次文件IO,所以一般在指令执行结束后进行写操作,这样一来可以避免指令错误二来不会阻塞写指令的执行,这个过程需要满足原子性,所以还是会对下一跳指令产生阻塞,参考MySQL这里可以加一个缓存池(server.aof_buf
)用来缓存指令。
现在问题姑且是解决了,但是什么时候将缓存池的内容写入文件呢?
AOF写回策略
redis提供了3种写回策略,可以在redis.conf
配置文件中的appendfsync
配置项进行选择。
写回策略 | 写回时机 | 优点 | 缺点 |
---|---|---|---|
Always | 同步写回 | 可靠性高,最大程度保证数据不丢失 | 性能开销大 |
Everysec | 每秒写回 | 性能适中 | 服务器宕机时丢失1s内的数据 |
No | 由操作系统控制写回 | 性能好 | 服务器宕机时丢失的数据量未知 |
实现起来其实通过控制fsync
的调用时机控制的
- Always 策略就是每次写入 AOF 文件数据后,就执行 fsync() 函数;
- Everysec 策略就会创建一个异步任务来执行 fsync() 函数;
- No 策略就是永不执行 fsync() 函数;
AOF重写机制
写回策略是确定了,但是还是存在问题,一般情况看下一个服务器一旦开始跑就一直要跑下去(除非特殊情况),那么AOF日志的大小岂不太大了,里面的指令再次重启的时候又要跑多久呢?为了解决这个问题,redis引入了重写机制
简单来说就是文件大小到达一个阈值后会按照当前存储的数据给出复现指令,十分合理,这种情况下可以有效过滤掉一些中间指令,甚至可以按照当前情况进行指令优化。
下面介绍AOF重写过程
- fork重写子进程
- 子进程创建一个新的AOF日志,进行数据的反向复现,此时主进程还可以处理业务,但是写入的时候会触发写时复制,这个过程中新的写操作会写入旧的AOF缓冲区和AOF重写缓冲区
- 子进程处理完后向主进程发送一个信号告知处理完毕,主进程接收到信号后调用信号处理函数,将AOF缓冲区内的临时数据拷贝到新的AOF日志中,新的AOF日志改名覆盖掉旧的日志
细节详解:
- 为什么使用子进程而不使用子线程,假设使用子线程的话试想一种情景对方取反,你这里还没有解析到,AOF重写缓冲区记录到了,你记录的也是修改后的,最后和并的时候就会出错,不记录缓冲区的话解析完string类型数据你又加入新数据也会出问题,加锁可以解决问题,但是代价太大了,不如借助父子进程间的写时复制原则保证原子性
- 阻塞环节:
- 创建子进程时复制页表的环节
- 写时复制的环节
- 调用信号处理函数环节
RDB快照
启动快照的情况下redis会按照约定的时间拍快照,这个过程依旧是通过子进程完成的,相较于AOF日志这里存储的是真实数据,以二进制的方式存储到文件中,这使得快照恢复起来更加高效,高效的同时是更高的代价,极端情况下拷贝数据的时候如果父进程的数据全修改了就需要2x大小的物理内存。
巨大的性能开销决定了RDB快照无法经常进行,所以使用它的时候服务器宕机可能会造成大量数据丢失。
AOF结合RDB
这个方法是在 Redis 4.0 提出的,该方法叫混合使用 AOF 日志和内存快照,也叫混合持久化
,启用的话将下面这个配置设置为true就行了。
aof-use-rdb-preamble yes
过程也很简单,在AOF重写的过程中进行,重写时直接拷贝数据并储存到新的AOF日志中,然后拷贝完将AOF重写缓冲区的内容以AOF指令的形式存储到新的AOF日志中,现在AOF由RDB快照和AOF指令组成,这种方式启动起来更加高效同时服务器宕机时数据保存效果好,简直是妙蛙种子吃着妙脆角进了米奇妙妙屋妙到家了。