对比
- AOF 文件的内容是操作命令;
- RDB 文件的内容是二进制数据。
- RDB 快照就是记录某一个瞬间的内存实际数据,而 AOF 文件记录的是命令操作的日志
- RDB 恢复数据的效率会比 AOF 高些,因为直接将 RDB 文件读入内存就可以,不需要像 AOF 那样还需要额外执行操作命令的步骤才能恢复数据。
- RDB 丢失的数据会更多,因为保存是全量快照,所以频率不能太高否则性能不好,所以会丢失更多数据
AOF
怎么用
// redis.conf
appendonly yes
appendfilename "appendonly.aof"# 同步方式
appendfsync everysec# aof重写期间是否同步
no-appendfsync-on-rewrite no# 重写触发配置
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb# 加载aof时如果有错如何处理
aof-load-truncated yes # yes表示如果aof尾部文件出问题,写log记录并继续执行。no表示提示写入等待修复后写入# 文件重写策略
aof-rewrite-incremental-fsync yes
读命令不会记录,只会记录写命令
为什么先执行写命令,之后记录写命令到日志
- 减少额外检查命令合法性的开销,能执行成功就一定是合法的
- 不会阻塞当前执行的命令,但是注意因为二者是单线程同步的,所以会阻塞下一条命令的执行。
命令记录的格式是什么
- 总共几部分
- 每部分以
$数字
开头,表示占用多少个字节
数据存储流程涉及几个存储位置
三种写回策略appendfsync
是什么(页缓存到磁盘)
- Always,每次都同步将 AOF 日志数据写回硬盘;
- 高可靠
- Everysec,每次先将命令写入到 AOF 文件的内核页缓存,每秒将页缓存的内容写回到硬盘;
- 折中
- No,每次先将命令写入到 AOF 文件的内核页缓存,再由操作系统决定何时将缓冲区内容写回硬盘。不由 Redis 控制写回硬盘的时机,转交给操作系统控制写回的时机,
- 高性能
进程阻塞和数据丢失是对立的,Always保证数据最大不丢失,但是阻塞性能,No 不会过多阻塞但是刷盘不可预知数据容易丢失,Everysec这种性能和数据完整性。会丢失一秒的数据。
三种写回策略的原理--fsync()的调用时机不一样
fsync() 是内核发起的写操作对应的系统调用,立即将页缓存数据刷写磁盘,等到硬盘写操作完成后,该函数才会返回。
- always每次写入命令到文件之后都执行fsync
- no永远不执行
- everysec异步任务执行
为什么需要AOF重写机制
随着执行的命令越多,AOF 文件的体积自然也会越来越大,为了避免日志文件过大, Redis 提供了 AOF 重写机制。
它会直接扫描数据中所有的键值对数据,然后为每一个键值对生成一条写操作命令,接着将该命令写入到新的 AOF 文件,重写完成后,就替换掉现有的 AOF 日志。重写的过程是由后台子进程完成的,这样可以使得主进程可以继续正常处理命令。
AOF重写机制压缩的原理是什么
文件大小超过设定的阈值,就会启动重写来压缩。重写工作完成后,就会将新的 AOF 文件覆盖现有的 AOF 文件,这就相当于压缩了 AOF 文件,使得 AOF 文件体积变小了。
压缩的原理就是同一个key的写命令只保留最新的结果,以前的没必要保留直接删除。
为什么重写机制新建一个文件覆盖而不是直接修改原文件
重写过程失败,原来的文件也会污染失效,分开执行,保证不会对现有的文件造成影响
AOF重写机制为什么不会阻塞--bgrewriteaof子进程而不是子线程
因为AOF重写的时候文件比较大,不像写命令的时候写入的内容不多,所以这里需要使用后台子进程执行,而不是使用主进程执行。
后台子进程 bgrewriteaof 的好处:
- 主进程可以继续处理请求不会阻塞
- 子进程会复制数据副本来修改(写时复制),而不是像子线程一样直接操作两个线程的共享数据,如果是子线程的话需要加锁保证数据的安全。子进程就不需要。性能提升,避免死锁
fork 子进程 **bgrewriteaof **写时复制的原理
主进程在fork **bgrewriteaof **子进程的时候,OS 会将主进程的页表复制一份给子进程,页表记录有虚拟内存和物理地址的映射关系,不会复制物理内存,虚拟内存标记页表内容为只读。
父子进程有一个写操作,就会触发违反权限导致的缺页中断。OS在缺页中断处理函数里面实现物理内存的复制,重新设置内存的映射关系以及将父子进程的内存读写权限设置为可读写。最后才对内存写操作。这个叫做写时复制。
在发生写操作的时候,操作系统才会去复制物理内存,这样是为了防止 fork 创建子进程时,由于物理内存数据的复制时间过长而导致父进程长时间阻塞的问题。复制虚拟内存的页表也会阻塞但是比较快,所以会在创建的时候执行。
processon
重写缓冲区满了会发生什么
打印日志,退出Redis进程
重写缓冲区是边用边申请的,也就是说是动态申请的,并不是一次性就分配好的。
如果一直分配内存,当耗尽系统的内存资源的时候,zmalloc() 就无法申请成功,就会打印一条日志,随后就 Redis 进程就退出了。
RDB
怎么用
# save 900 1:表示15分钟(900秒钟)内至少1个键被更改则进行快照。
# save 300 10:表示5分钟(300秒)内至少10个键被更改则进行快照。
# save 60 10000:表示1分钟(60秒)内至少10000个键被更改则进行快照
# 这3个是默认配置,只要满足一个就会持久化,也可以自己根据需要配置(添加、减少、修改)。
save 900 1 # 900s内至少一次写操作则执行bgsave进行RDB持久化
save 300 10
save 60 10000# 文件名称,rdb文件
dbfilename dump.rdb# 如果持久化出错,主进程是否停止写入
stop-writes-on-bgsave-error yes# 是否压缩
rdbcompression yes# 导入时是否检查
rdbchecksum yes# 文件保存路径
dir ./
RDB 是 全量快照,操作比较重,安全和性能决定了最后的频率。
禁用RDB
只需要在save的最后一行写上:save ""
save 命令和bgsave的区别是什么
- 执行 save 命令,会在主进程生成 RDB 文件,如果写入 RDB 文件的时间太长,会阻塞主进程;
- 执行 bgsave 命令,会创建一个子进程来生成 RDB 文件,避免主进程阻塞。
- 通过配置文件的选项来实现每隔一段时间自动执行一次 bgsave 命令,名叫 save,实际上执行的是 bgsave 命令,也就是会创建子进程来生成 RDB 快照文件。
RDB 文件的加载工作是在服务器启动时自动执行的,Redis 并没有提供专门用于加载 RDB 文件的命令。
何时使用save
数据很大的时候,fork复制页表会消耗很多性能,此时直接暂停服务使用Redis主进程save,避免了大页表的复制,同时没有子进程竞争资源,效率更高
数据小的时候使用bgsave合理。
快照执行时候数据可以修改吗
可以,和AOF的原理是一样的:
- 复制的时候只复制虚拟内存的映射关系,设置为只读(主子进程都是)的,要修改的时候就会触发缺页中断,OS 复制物理内存之后修改映射关系,开始执行写操作
- 这样就减少了创建子进程时候复制可能会造成的阻塞
RDB何时数据丢失
Redis 在使用 bgsave 快照过程中,如果主线程修改了内存数据,不管是否是共享的内存数据,RDB 快照都无法写入主线程刚修改的数据,因为此时主线程的内存数据和子线程的内存数据已经分离了,子线程写入到 RDB 文件的内存数据只能是原本的内存数据。如果系统恰好在 RDB 快照文件创建完毕后崩溃了,那么 Redis 将会丢失主线程在快照期间修改的数据。
极端情况下的内存消耗是原来的2倍
快照子进程bgsave刚刚创建,执行命令的主进程将共享数据全部修改,此时快照依旧使用之前的共享数据生成,内存消耗就是原来的2倍了。
如何选?综合AOF和RDB--混合持久化
AOF丢失数据少,写入快。恢复慢。
RDB丢失数据多,写入慢,恢复快。
aof-use-rdb-preamble yes
日志重写的时候,新的AOF文件里面前半部分是共享数据的RDB快照,后半部分是保存共享数据时候的AOF增量缓冲区的命令。
重启 Redis 加载数据的时候,RDB 加载的时候速度会很快。后半部分的 AOF 内容是 Redis 后台子进程重写 AOF 期间,主线程处理的操作命令,可以使得数据更少的丢失。
Redis如何读取混合AOF到内存?
打开 AOF 文件之后,首先读取 5 个字符如果是「REDIS」,那么就说明这是一个混合持久化的 AOF 文件,因为 RDB 格式一定是以「REDIS」开头,而纯 AOF 格式则一定以「*」开头。
所以如果开头的 5 个字符是 「REDIS」 会先进入 rdbLoadRio() 函数来加载 RDB 内容。
redis 单线程与这里的子进程矛盾吗?
这个单线程指的是 **从网络 IO 处理到实际的读写命令处理 **都是由单个线程完成的,并不是说整个 Redis 里只有一个主线程。有些命令操作可以用后台子进程执行(比如快照生成、AOF 重写)。
Redis 4.0 之后并不是单线程架构了,除了主线程外,它也有后台线程在处理一些耗时比较长的操作,例如清理脏数据、无用连接的释放、大 Key 的删除等等。
Redis 6.0 版本支持了多线程技术,不过这个并不是指多个线程同时在处理读写命令,而是使用多线程来处理 Socket 的读写,最终执行读写命令的过程还是只在主线程里。采用多线程 IO 是因为Redis 处理请求时,网络处理经常是瓶颈,通过多个 IO 线程并行处理网络操作,可以提升整体处理性能。
为什么处理操作命令的过程只在单线程里呢?
因为 Redis 不存在 CPU 成为瓶颈的情况,主要受限于内存和网络。而且使用单线程的好处在于,可维护性高、实现简单。
如果采用多线程模型来处理读写命令,虽然能提升并发性能,但是它却引入了程序执行顺序的不确定性,带来了并发读写的一系列问题,增加了系统复杂度、同时可能存在线程切换、甚至加锁解锁、死锁造成的性能损耗。
参考链接:https://mp.weixin.qq.com/s?__biz=MzUxODAzNDg4NQ==&mid=2247495027&idx=1&sn=217af306b07ed0f2a064773541d46721&chksm=f98da9d9cefa20cf7ce2ef0ebf60cf378b79958a2e34559c9da7922be4a946a800ff815f950f&scene=178&cur_album_id=1790401816640225283#rd
https://blog.csdn.net/qq_22049773/article/details/102551630
https://www.cnblogs.com/baijinshuo/p/13672264.html