Redis持久化策略
Redis 的数据全部在内存里,如果突然宕机,数据就会全部丢失,因此必须有一种机制来保证 Redis 的数据不会因为故障而丢失,这种机制就是 Redis 的持久化机制。
Redis 的持久化机制有两种,第一种是快照,第二种是 AOF 日志。
快照
快照是一次全量备份,是内存数据的二进制序列化形式,在存储上非常紧凑。
快照原理
Redis 是单线程程序,在服务线上请求的同时,Redis 还需要进行内存快照,内存快照要求 Redis 必须进行文件 IO 操作,可文件 IO 操作是不能使用多路复用 API。
为了不阻塞线上的业务,就需要边持久化边响应客户端请求,因此Redis使用操作系统的多进程 COW(Copy On Write) 机制来实现快照持久化。
fork(多进程)
Redis 在持久化时会调用 glibc 的函数fork
产生一个子进程,快照持久化完全交给子进程来处理,父进程继续处理客户端请求。子进程刚刚产生时,它和父进程共享内存里面的代码段和数据段。
fork
函数会在父子进程同时返回,在父进程里返回子进程的 pid,在子进程里返回零。如果操作系统内存资源不足,pid 就会是负数,表示fork
失败。
子进程做数据持久化,它不会修改现有的内存数据结构,它只是对数据结构进行遍历读取,然后序列化写到磁盘中。但是父进程不一样,它必须持续服务客户端请求,然后对内存数据结构进行不间断的修改。
COW 机制
此时使用操作系统的 COW 机制来进行数据段页面的分离。数据段是由很多操作系统的页面组合而成,当父进程对其中一个页面的数据进行修改时,会将被共享的页面复制一份分离出来,然后对这个复制的页面进行修改。这时子进程相应的页面是没有变化的,还是进程产生时那一瞬间的数据。
随着父进程修改操作的持续进行,越来越多的共享页面被分离出来,内存就会持续增长。但是也不会超过原有数据内存的 2 倍大小。另外一个 Redis 实例里冷数据占的比例往往是比较高的,所以很少会出现所有的页面都会被分离,被分离的往往只有其中一部分页面。每个页面的大小只有 4K,一个 Redis 实例里面一般都会有成千上万的页面。
AOF
Redis 将所有对数据库进行过写入的命令(及其参数)记录到 AOF 文件,AOF 文件通过同步 Redis 服务器所执行的命令, 从而实现了数据库状态的记录, 但是, 这种同步方式会造成一个问题: 随着运行时间的流逝, AOF 文件会变得越来越大。因此需要AOF 重写的实现
AOF 重写的实现
“重写”其实是一个有歧义的词语, 实际上, AOF 重写并不需要对原有的 AOF 文件进行任何写入和读取, 它针对的是数据库中键的当前值。
AOF 重写的实现原理:根据键的类型, 使用适当的写入命令来重现键的当前值。AOF 重写的目的是用更小的体积来保存数据库状态,整个重写过程基本上不影响 Redis 主进程处理命令请求。
举例:
如果服务器对键 list
执行了以下四条命令:
1 2 3 4 5 6 7 |
RPUSH list 1 2 3 4 // [1, 2, 3, 4] RPOP list // [1, 2, 3] LPOP list // [2, 3] LPUSH list 1 // [1, 2, 3] |
---|
那么当前列表键 list
在数据库中的值就为 [1, 2, 3]
。
如果我们要保存这个列表的当前状态, 并且尽量减少所使用的命令数, 那么最简单的方式不是去 AOF 文件上分析前面执行的四条命令, 而是直接读取 list
键在数据库的当前值, 然后用一条 RPUSH 1 2 3
命令来代替前面的四条命令。
AOF 后台重写
Redis 决定将 AOF 重写程序放到(后台)子进程里执行, 这样处理的最大好处是:
- 子进程进行 AOF 重写期间,主进程可以继续处理命令请求。
- 子进程带有主进程的数据副本,使用子进程而不是线程,可以在避免锁的情况下,保证数据的安全性。
不过, 使用子进程也有一个问题需要解决: 因为子进程在进行 AOF 重写期间, 主进程还需要继续处理命令, 而新的命令可能对现有的数据进行修改, 这会让当前数据库的数据和重写后的 AOF 文件中的数据不一致。
为了解决这个问题, Redis 增加了一个 AOF 重写缓存, 这个缓存在 fork 出子进程之后开始启用, Redis 主进程在接到新的写命令之后, 除了会将这个写命令的协议内容追加到现有的 AOF 文件之外, 还会追加到这个缓存中:
当子进程在执行 AOF 重写时, 主进程需要执行以下三个工作:
- 处理命令请求。
- 将写命令追加到现有的 AOF 文件中。
- 将写命令追加到 AOF 重写缓存中。
这样可以保证:
- 现有的 AOF 功能会继续执行,即使在 AOF 重写期间发生停机,也不会有任何数据丢失。
- 所有对数据库进行修改的命令都会被记录到 AOF 重写缓存中。
当子进程完成 AOF 重写之后, 它会向父进程发送一个完成信号, 父进程在接到完成信号之后, 会调用一个信号处理函数, 并完成以下工作:
- 将 AOF 重写缓存中的内容全部写入到新 AOF 文件中。
- 对新的 AOF 文件进行改名,覆盖原有的 AOF 文件。
当步骤 1 执行完毕之后, 现有 AOF 文件、新 AOF 文件和数据库三者的状态就完全一致了。
当步骤 2 执行完毕之后, 程序就完成了新旧两个 AOF 文件的交替。
在整个 AOF 后台重写过程中, 只有最后的写入缓存和改名操作会造成主进程阻塞, 在其他时候, AOF 后台重写都不会对主进程造成阻塞, 这将 AOF 重写对性能造成的影响降到了最低。
这也是BGREWRITEAOF
命令的工作原理。
AOF 可以由用户通过调用BGREWRITEAOF
手动触发,也可以由服务器自动触发。
AOF 的适用场景
如果对数据比较关心,分秒必争,可以用 AOF 持久化,而且AOF 文件很容易进行分析。
Redis 4.0 混合持久化
- RDB 持久化能够快速地储存和恢复数据, 但是在服务器停机时却会丢失大量数据;
- AOF 持久化能够有效地提高数据的安全性, 但是在储存和恢复数据方面却要耗费大量的时间
Redis 4.0 为了解决这个问题,带来了一个新的持久化选项——混合持久化。将 rdb 文件的内容和增量的 AOF 日志文件存在一起。
RDB 数据位于 AOF 文件的开头, 它们储存了服务器开始执行重写操作时的数据库状态: 至于那些在重写操作执行之后执行的 Redis 命令, 则会继续以 AOF 格式追加到 AOF 文件的末尾, 也即是 RDB 数据之后。
于是在 Redis 重启的时候,可以先加载 rdb 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,重启效率因此大幅得到提升。
区别
AOF 持久化和 RDB 持久化的最主要区别在于,前者记录了数据的变更,而后者是保存了数据本身。
参考
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- PAT (Basic Level) Practice (中文)1008 数组元素循环右移问题 (20 分)
- PAT (Basic Level) Practice (中文)1037 在霍格沃茨找零钱 (20 分)
- 编译原理实战入门:用 JavaScript 写一个简单的四则运算编译器(三)模拟执行
- PAT (Basic Level) Practice (中文)1011 A+B 和 C (15 分)
- SAP Spartacus里ng-template的一个实际应用
- PAT (Basic Level) Practice (中文)1039 到底买不买 (20 分)
- PAT (Basic Level) Practice (中文)1013 数素数 (20 分)
- PAT (Basic Level) Practice (中文)1041 考试座位号 (15 分)
- 《Java 面试问题 一 Spring 、SpringMVC 、Mybatis》
- SAP Spartacus里的product carousel控件的实现cx-product-carousel
- PAT (Basic Level) Practice (中文)1014 福尔摩斯的约会 (20 分)
- 《数据结构与算法_插入排序》
- UGL之标准位图
- Linux(Centos7.X ) 配置Java 环境变量
- CNS图表复现05—免疫细胞亚群再分类