Redis——持久化
AOF日志
概述:
AOF日志用于记录写操作命令,在redis重启后通过逐步执行写命令实现内存的数据再现。
*3表示当前命令有三个部分,$+数字表示这部分命令、键或值包含多少字节。
执行顺序:先执行写操作再进行日志记录
优点:
- 避免额外检查开销:执行后可保证名类都是正确的,无需额外检查直接写入即可。
- 不会阻塞当前写操作执行:只有当写操作执行成功后才记录到日志里。
缺点:
- 崩溃时数据丢失:记录日志前意外宕机,数据有丢失风险。
- 阻塞后续命令执行:记录日志是磁盘IO操作,可能会阻塞下一条命令。
日志记录过程:
与MySQL类似,AOF日志先记录到Redis的内存中,再通过write操作写入内核的页缓冲区,最后由内核选择合适的时机写入到磁盘中
三种写回策略:
- Always:每次写操作命令执行完后,都直接刷盘AOF
- Everysec:每次写操作命令执行完后,先写入到AOF文件的内核缓冲区,每隔一秒刷盘
- No:每次写操作命令执行完后,先写入到内核缓存区,再由操作系统决定什么时候刷盘
这三种策略都无法完美解决主线程阻塞和数据丢失,且这些策略实际上是控制fsync方法的调用时机(参考MySQL)。
- 高性能采用No(永不执行fsync)
- 高可靠采用Always(每次都调用fsync)
- 折中采用Everysec。
AOF重写机制
AOF日志存储在文件中,随着执行的写操作命令越来越多,文件会越来越大,进而导致恢复内存数据的性能变差,所以Redis设定了一个阈值,当AOF日志大小超过阈值后会启用AOF重写机制来压缩日志。
行为:
AOF重写实质是读取当前数据库的所有键值对,转化为对应的命令记录到新的AOF文件中,全部记录完成后替换原有的AOF日志(这样即使重写失败也不会影响原日志)。
特点:
AOF重写相当于只记录最新命令,过滤了原日志中key的历史命令和删除key的命令,从而无需复现键值对的多次修改过程,并达到压缩效果。
AOF后台重写
重写日志是比较耗时的,不应该由主进程进行,所以Redis使用了后台子进程bgrewriteaof来完成
- 子进程重写期间,主进程可以继续处理请求,避免堵塞
- 子进程重写可触发写时复制:子进程和主进程可直接共享内存数据,但若其中一方修改了共享内存,就会为对应进程生成独立的数据副本以供修改。
- 如果使用线程,多线程之间会共享内存,且修改操作是直接影响内存的,不会生成数据副本,需要加锁来控制,性能较低。
初始状态—共享物理内存
- 主进程通过fork调用子进程时,操作系统会把主进程的页表复制一份给子进程,页表条目指向相同的物理内存页,且这些物理内存页会标记为只读。
- 这样二者不同的虚拟内存空间就会指向同一个物理内存,从而大幅节约了物理内存资源(避免创建子进程时就复制一份独立的物理内存导致主进程阻塞)
写操作触发—物理内存分离
- 页错误触发:当其中一个进程进行写操作时,CPU检测到只读权限冲突,触发页错误。
- 分配新物理内存页:内核为修改的进程分配新的物理内存页(局部物理内存),复制原页的内容到新页,并更新页表的对应条目,使其指向新页(标记为可写)。
- 页表独立更新:修改进程的页表指向了新物理内存页,更新只会发生在新页中,父子进程的页表映射完全独立。
主进程进行了修改操作后,子进程记录的AOF日志与主进程的实际命令不一致
解决数据不一致:
Redis设置了一个AOF重写缓冲区,执行的写命令会同时追加到AOF缓冲区和AOF重写缓冲区
子进程完成重写操作后,会异步向主进程发送一条信号,主进程收到信号后调用处理函数:
- 将AOF重写缓冲区的所有内容追加到新的AOF文件中,使得新旧两个AOF文件数据库状态一致
- 新的AOF文件改名,覆盖旧AOF文件
对主进程的阻塞时机:
- 执行信号函数时,主进程是阻塞状态
- 写时复制时,主进程是阻塞状态
- 复制页表时,主进程是阻塞状态
RDB快照
概述:
AOF记录的是写操作命令,RDB记录的是二进制数据,也就是记录Redis某一个瞬间的实际内存数据。因此恢复数据时,RDB的效率要比AOF更高,因为不用再逐步执行命令,直接读取RDB文件即可。
快照的使用:
- save命令:主进程生成RDB文件,可能会阻塞主进程
- bgsave命令:创建子进程生成RDB文件,避免阻塞主进程
save 900 1
save 300 10
save 60 10000
- save指令默认执行的是bgsave,意思是900秒内对数据库进行了1次修改。就会执行bgsave快照。
- RDB快照是全量快照(AOF是增量写入),所以执行快照是比较损耗性能的操作。我们通常选择至少5分钟才执行一次快照,但这样也会导致我们丢失更多数据。
执行快照时是否可以修改?
与AOF重写一致,主线程是可以修改的,bgsave命令会通过fork调用子进程进行快照操作
- 使用的依然是写时复制技术,但写时复制会导致父子进程数据不一致的问题。
- 所以快照时的修改操作只能在下一次快照时生效,如果Redis恰好在快照文件完成后崩溃,会丢失主进程在快照期间修改的数据。
- 极端情况下,若所有的共享内存都被修改,内存占用会是原来的两倍
混合持久化:
虽然RDB快照恢复比AOF快,但频率过低时丢失较多数据,频率较高则会带来额外性能开销。
所以Redis4.0后使用了混合持久化,混合lAOF日志与RDB快照
工作流程:
- 在AOF重写日志时,子进程会将共享内存数据中的键值对以RDB的形式写入到AOF文件中
- 主进程重写过程中修改的数据会进入AOF重写缓冲区中,缓冲区中的命令会以AOF形式写入到AOF文件中
- 最后AOF文件前半部分是RDB类型的全量数据,后半部分是AOF格式的增量数据
优点和劣势:
- 重启时数据加载速度很快,且保存时数据丢失较少,兼备了AOF和RDB的优点
- 但文件内部格式较为混乱,可读性差
大Key问题
对AOF日志三种写回策略的影响:
- 对Always:主线程每次执行完命令都执行fsync都会阻塞较长时间
- 对EverySec:异步线程执行fsync,不阻塞主线程
- 对No:不执行fsync,不会阻塞主线程
对AOF重写和RDB快照影响:
AOF日志写入较多大Key后很快就会触发AOF重写。重写和快照时会复制一份主进程的页表给子进程,若页表较大,复制过程会比较耗时,可能会造成主进程阻塞
如果在AOF重写或RDB快照过程中修改了大Key键值,则会触发写时复制,在物理内存中拷贝大Key数据副本,这个过程占用内存比较大,且比较耗时,可能会造成主进程阻塞
其他阻塞影响:
客户端超时阻塞:Redis单线程处理命令,操作大Key比较耗时进而阻塞
网络阻塞:每次获取大Key产生的网络流量较大,在每秒访问量高时对服务器的压力极大
工作线程阻塞:网络I/O大key时,会阻塞工作线程,无法处理其他任务
内存分布不均:集群模式可能会出现数据和查询倾斜,有大Key的Redis节点占用内存多
避免大key:
设计阶段将大key拆分成一个个小key
定时检测是否存在大key,如果可以删除则使用unlink命令异步删除,del会阻塞主线程