Redis——持久化
认识持久化
MySQL 的事务,有四个比较核心的特性:
1)原子性
2)一致性
3)持久性 == 持久化
4)隔离性
把数据存储在内存上 ——> 不持久
把数据存储在硬盘上 ——> 持久
持久化的判断依据 ——> 重启进程 / 重启主机后,数据是否存在
Redis 是一个 内存 数据库,把数据存储在内存中(不持久)
Redis 相比于 MySQL 这样的关系型数据库,最明显的优势就是 快(数据存储在内存中)
为了保证速度快,数据肯定还是要存储在内存上,但是为了持久,数据就得存储在硬盘上
——> 因此 Redis 决定,内存中存储数据,硬盘上也存储数据(两份数据理论上完全相同,小概率的差异,取决于持久化的具体方式)
当插入一个新的数据时,需要把这个数据同时写入内存和硬盘
但是,查询时还是从内存中读取,硬盘的数据只是在 Redis 重启时,用来恢复内存中的数据
Redis 实现持久化的策略:
1)RDB Redis DataBase 定期备份
2)AOF Append Only File 实时备份
RDB
定期把当前进程的数据生成快照保存到硬盘中
当 Redis 重启后,可以根据 快照 把内存中的数据恢复回来
触发机制
1)手动触发
通过 Redis 客户端,执行特定的命令,来触发快照
save
阻塞当前 Redis 服务器,直到 RDB 过程完成为止,对于内存较大的实例会造成长时间的阻塞
bgsave
Redis 进程执行 fork 操作创建子进程,RDB 持久化过程由子进程负责,完成后自动结束。阻塞只发生在 fork 阶段,一般时间很短
2)自动触发
在 Redis 配置文件中,设置参数,让 Redis 每个多长时间 / 每产生多少次修改就触发
1)执行 bgsave 命令,Redis 父进程判断当前进程是否存在其他正在执行的子进程,如RDB \ AOF 子进程,如果存在,basave 命令直接返回
2)父进程执行 fork 创建子进程,fork 过程中父进程会阻塞(通过 info stats 命令查看 lastest_fork_usec 选项,可以获取最近一次 fork 操作的耗时,单位为微秒)
3)父进程 fork 完成后, bgsave 命令返回 “Background saving started” 信息,不再阻塞父进程,可以继续相应其他命令
4)子进程创建 RDB 文件,根据父进程内存生成临时快照,完成后对原文件进行原子替换
5)进程发送信号给父进程表示完成,父进程更新信息,子进程可以结束销毁了
fork 创建子进程,直接把当前的进程(父进程)复制一份作为子进程(pcb,虚拟地址空间(内存中的数据),文件描述附表 ...),一旦复制完成,父子进程就是两个独立的进程
复制出来的子进程中的数据和父进程是一样的,安排子进程去执行“持久化”操作,也就相当于是把父进程这里的内存给持久化了
fork 进行内存拷贝时是通过“写时拷贝”机制来完成的 ——>
如果子进程中的内存数据,和父进程中的内存数据完全一样,不会触发真正的拷贝动作(双方共用一份数据)
但是,两个进程的内存空间应该是相互独立的,如果一方针对内存数据进行修改,就会出发真正的 物理内存 上的拷贝
RDB镜像文件
Redis 生成的 RDB 文件,是存放在 Redis 的工作目录中的 /etc/redis/redis/conf(aof 文件也在)
可以在 Redis 配置文件中进行设置
RDB 机制生成的镜像文件(Redis 服务器默认开启 RDB)
dump.rdb 是二进制文件,把内存中的数据,以压缩的形式保存到这个二进制文件中
消耗一定的 CPU 资源,但能节省空间
Redis 重启时,会加载在这个 rdb 文件,如果格式错误,数据可能加载错误,导致服务器无法启动
RDB 持久化操作,可以多次触发
当执行生成 rdb 镜像操作的时候,会把生成的快照数据保存到一个临时文件中
当这个快照生成完毕后,删除之前的 rdb 文件,把新生成的临时 rdb 文件名字改成刚才的 dump.rdb rdb 文件始终只有一个
RDB 效果演示
rdb 文件位置
Redis 工作目录
rdb 文件默认名称
查看 rdb 文件
rdb 文件中的数据,不是立即更新的
触发机制:
1)手动触发(save,bgsave)
2)自动触发(配置文件中配置)
生成一次 rdb 快照,是一个成本比较高的操作,因此不能操作太频繁
因此,rdb 生成不能太频繁,导致和当前实时的数据情况可能存在偏差
service redis-server stop
ps aux | grep redis-server
service redis-server start
1)手动执行 bgsave,触发一次快照
插入数据前
写入数据
手动执行 bgsave
t通过上述操作,Redis 服务器在重启时加载 rdb 文件的内容,恢复了内存之前的状态
2)插入新的 key,不手动执行快照
Redis 生成快照自动触发:
1. 通过配置文件中 save 执行 M 时间内,修改 N 次
2. shutdown 命令关闭服务器(service redis-server restart)正常关闭
如果正常流程重启 Redis 服务器,此时 Redis 服务器在退出时,自动触发生成 rdb 文件
异常重启(kill -9 服务器掉电)Redis 服务器来不及生成 rdb,数据会丢失
3. redis 进行主从复制时,主节点也会自动生成 rdb 快照,再把快照内容传输给从节点
3)bgsave 操作流程是创建子进程,子进程完成持久化操作
持久化操作会把数据写入到新的文件中,再使用新的文件替换旧的文件
持久化速度太快(数据少),难以观察子进程,可以观察文件的替换
直接使用 save 命令,不会触发 子进程&文件替换逻辑,直接在当前进程中,往刚才的文件中写入数据
使用 linux 的 stat 命令,查看文件编号
重启 redis
文件不是同一个文件,只不过内容是一样的(inode 编号,相当于是文件的身份标识)
linux 文件系统
文件系统典型的组织方式(ext4)主要吧文件系统分成三大部分:
1)超级块(管理信息)
2)inode 区(存放 inode 节点,每个文件都会分配一个inode数据结构,包含文件的各种元素)
3)block 区(文件的数据内容)
4)通过配置自动生成 rdb 快照
执行 flushall 会清空 rdb 文件
对于 Redis,配置文件修改后,一定要重启服务器才能生效
如果想立即生效,可以通过命令的方式执行
5)故意破坏 rdb 文件
手动破坏 rdb 文件,要通过 kill 进程 的方式来重启 Redis 服务器
通过 service redis-server restart ,会在 Redis 服务器退出时,重新生成 rdb 快照,把之前的更换
rdb 文件是二进制的,把破坏的文件交给 Redis 服务器去使用,结果是不可预测的
查看 Redis 日志 cd /var/log/redis/
总结
1)rdb 是一个紧凑压缩的二进制文件,代表 Redis 在某个时间点上的数据快照,适用于备份,全 量复制等场景
rdb 使用二进制组织数据,直接把数据读取到内存中,按照字节的格式读取出来,放到 结构体 / 对象 即可
aof 使用文本的方式来组织数据,需要进行一系列的字符串切分操作
2)Redis 加载 rdb 恢复数据远远快于 aof 的方式
3)rdb 不能做到 实时持久化 / 秒级持久化
bgsave 每次运行都要执行 fork 创建子进程,属于重量级操作,频繁执行成本过高
4)rdb 文件使用特定的二进制格式保存,Redis 版本不同,可能有兼容风险
遇到不兼容问题,可以通过写一个程序的方式,直接遍历接的 Redis 中所有的 key,把数据取出来,插入到新的 Redis 服务器中即可
rdb 最大的问题是,不能实时的持久化存储数据
在两次生成快照之间,实时的数据可能会随着重启而丢失
AOF
AOF append only file
类似于 mysql 的 binlog,会把用户的每个操作都记录到文件中
当 Redis 重启时,会读取这个 aof 文件中的内容来恢复数据
aof 默认是关闭状态,可以通过配置文件来修改(开启 aof 后,Redis就不生效了,不在读取 rdb文件内容)
开启 aof
aof 文件名
/var/lib/redis 工作路径
aof 是一个文本文件,每次操作都会记录到 文本文件 中
通过一些特殊符号作为分隔符,对命令的细节作出区分
在引入 AOF 之后,既要写硬盘,又要写内存,但是对 Redis 的处理速度没有影响
1. AOF 机制并非直接让工作线程把数据写入硬盘,而是先写入一个内存中的缓冲区,积累一会后,再统一写入硬盘
2. 硬盘上读取数据,顺序读写的速度是比较快的(还是比内存慢的多),随机访问比较慢
AOF 是每次把新的操作写入到原有文件的末尾,属于顺序写入
缓冲区的刷新策略
把数据写入缓冲区,本质还是在内存中,数据还是可能会丢失
缓冲区的刷新策略:
可配置值 | 说明 |
always | 命令写入 aof_buf 后调用 fsync 同步,完成后返回 |
everysec | 命令写入 aof_buf 后调用 write 操作,不进行 fsync 每秒有同步线程进行 fsync |
no | 命令写入 aof_buf 后只执行 write 操作,由 OS 控制 fsync 频率 |
频率越高,数据可靠性越高,性能越低
重写机制(rewrite)
AOF 文件持续增长,体积越来越大,会影响下次 Redis 启动的时间
在上述的 aof 文件中,有些内容是
Redis 存在一个机制,能够对 aof 文件进行 整理 操作(Redis 只关心最终结果)
能够提出其中的操作,并且合并一些操作,给 aof 文件“瘦身”
手动触发:调用 bgrewriteaof 命令
自动触发:
auto-aof-rewrite-min-size 表示触发重写时 aof 文件的最小文件大小,默认为64MB
auto-aof-rewrite-percentage 代表当前 aof 占用大小相较于上次重写时增加的比例
重写流程
创建子进程 fork
父进程仍然负责接受请求
子进程负责针对 aof 文件进行重写(只关心最终状态)
子进程只需要把内存中的数据获取出来,以 aof 的格式写入到一个新的 aof 文件中(内存中的状态,已经相当于是 aof 把文件整理后的结果)
此处子进程写数据的过程,类似于 rdb 生成一个镜像快照
是 rdb 是按照二进制的形式来生成,aof 按照 文本的格式来生成
子进程写新的 aof 文件的同时,父进程仍然在不停的接收客户端的新请求
父进程会把这些请求产生的 aof 文件数据先写入缓冲区,再刷新到原有的 aof 文件中
在创建子进程的瞬间,子进程就继承了当前父进程的内存状态
当前,子进程里的内存数据是父进程 fork 之前的状态
fork 之后,新来的请求对内存造成的修改,子进程是不知道的
此时,父进程准备了一个 aof_rewrite_buf 缓冲区,存放 fork 之后的数据
子进程会把 aof 数据写完后,通过 信号 通知一下父进程
父进程再把 aof_rewrite_buf 缓冲区1的内容也写到新的 aof 文件里
此时就可以用新的 aof 文件替代旧的 aof 文件了
AOF 重写流程
1)如果当前进程正在执行 AOF 重写,请求不执行
若果当前进程正在执行 bgsave 操作,重写命令会延迟到 bgsave 完成后再执行
2)父进程成执行 fork,创建子进程
3)重写
1.主进程 fork 之后,继续响应其他命令。所有修改操作写入 AOF 缓冲区并根据 appendfsync 策略同步到硬盘中,保证旧 AOF 文件正确
2.子进程只有 fork 之前的所有内存信息,父进程中需要将 fork 之后一段时间的修改操作写 入 AOF 重写缓冲区
4)子进程根据内存快照,将命令合并到新的 AOF 文件中
5)子进程完成重写
1.新文件写入后,子进程发送信号给父进程
2.父进程把 AOF 缓冲区内临时保存的命令追加到新 AOF 文件中
3.用新 AOF 文件替换 旧 AOF 文件
rdb 对于 fork 之后的数据,直接置之不理(定期备份)
aof 对于 fork 之后的数据,采用 aof_rewrite_buf 缓冲区的方式来处理(实时备份)
父进程在 fork 完成之后,子进程已经开始写 新的 aof 文件,父进程继续写一个即将消亡的 aof 文件的原因 ——>
考虑到极端情况,在重写过程中,服务器挂了,子进程的数据会丢失,新的 aof 文件内容还不完整
如果父进程不继续写 旧 aof 文件,重启就无法保证数据的完备性
混合持久化
aof 本来是按照 文本的形式写入文件的,但是文本的形式写文件,后续记载的成本是比较高的
Redis 引入了 “混合持久化”,结合 rdb 和 aof 的特点:
按照 aof 的方式,每个 请求 / 操作 都会记录入文件
在触发 aof 重写后,会把当前内存的状态按照 rdb 的二进制格式写入到新的 aof 文件中
后续再进行的操作,仍然是按照 aof 文本的方式追加到文件后面
当 Redis 上同时存在 aof 文件 和 rdb 快照时,以 aof为主,rdb被直接忽略(aof 中的数据更全面)
信号
信号,可以认为是 linux 的神经系统(进程间的相互租用)
信号能表达的信息有限,不像 socket 可以传输任意数据
信号 在 Java 体系中,更接近 js 里的 “事件”:
1)事件 信号(可以理解成 linux 内核版本的事件机制)
2)事件源 关注信号发给谁
3)事件的类型 信号的类型(kill -9 等)
4)时间的处理函数 信号的处理函数(给指定进程发9号信号)