Redis面试题(三):持久化文件与淘汰策略

在这里插入图片描述

原文地址:Redis面试题(三):持久化文件与淘汰策略
原文地址:Redis面试题(三):持久化文件与淘汰策略
原文地址:Redis面试题(三):持久化文件与淘汰策略

欢迎关注我的公众号【意姆斯Talk】来聊聊Java面试,对线面试官系列持续更新中,因为模版图片不兼容,建议去原文地址观看。


纠正一下:AOF三种写回策略是同步写,但AOF重写机制可以fork子进程来执行。

Redis作为MySQL的缓存,能提供快速的读写,是基于内存的,当Redis宕机后,数据全部丢失,如何恢复缓存中的数据呢,最容易的办法是从数据库中同步这些数据到缓存中,若Redis本身既做缓存又做数据库,那如何处理?

AOF

先写缓存,然后再写AOF文件,AOF文件中记录的是每一条正确写入到缓存中的命令,Redis是一切从简,先写缓存后写日志的好处是:避免了像MySQL中分析器的作用:语法分析,词法分析,语义分析,也可以保证写入AOF文件中的命令是正确无误的。

优点:在命令执行完后,写入AOF文件,不会阻塞当前的写操作。

但AOF文件具备两个风险:

若刚执行完一个命令,还未来得及写AOF文件,Redis宕机了怎么办?

解决办法:

always:同步写回,每个写命令执行完,立马同步进磁盘。

everysec:每秒写回,每个写命令执行完,先把日志写进内存缓冲区,每隔一秒同步进磁盘中。

no:操作系统控制的写回,每个写命令执行完后,先把日志写进内存缓冲区,由操作系统决定何时同步进磁盘。

always可以确保数据基本不丢失,每个写命令后都有落盘操作,不可避免会带来主线程性能影响。
everysec采用一秒写回一次的频率,避免了写一次同步一次的操作,减少了对性能的影响,若发生宕机,也只是会丢失一秒的数据。作为高级开发,权衡是必修课,everysec只能确保影响最小,性能最大。
no性能最好了,Redis每次都把数据存储在缓冲区中,放完就走了,何时同步进磁盘都由操作系统决定,若宕机了,丢失的数据最多。

如果要获取高性能,则用no,如果要保证高可靠,就用always,如果既要保证高性能又要保证高可靠,就用everysec。
AOF的三种策略同步进磁盘,并非使用异步,也是由主线程来控制的。

随着命令执行得越多,AOF的文件大小就越大,Redis又提供了一种优化手段,AOF重写机制。

AOF重写机制:Redis根据数据库的现状创建一个新的AOF文件,读取数据库中的所有键值对,然后对每一个键值对用命令记录它的写入,比如读到String类型的key和value,则写入set key value。AOF同步写入磁盘是追加写的,可以对一个key进行多次修改,比如set peter 01,set peter 02,set peter 03,追加写会导致AOF中保存了这三种命令,但AOF重写机制能保证只保存最新的命令,这样就大大减少了文件内存大小。

执行AOF重写机制时,是否会阻塞主线程?
每次执行重写时,主线程都会fork出一个子进程,在fork子进程时,子进程会复制父进程的内存页表,即虚拟内存和物理内存的映射关系表,在不拷贝物理内存的情况下,也能共享父进程的内存数据,因此,看起来好像是拷贝了最新的AOF文件数据,此时子进程和主线程互不影响,主线程还可以处理新来的请求。
此时若有写操作,Redis会把这个写操作记录到缓冲区,同时fork子进程未执行完时,也会写进老AOF文件中,这里两个新老AOF都会写,若宕机,缓冲区的数据丢失,老AOF文件中还是有的,若未宕机,待子进程把所有的数据重写完成后,再把缓冲区中的最新写操作,同步进新的AOF文件中,保证此时的AOF文件是最新的,然后用新AOF文件代替旧的AOF文件,固不会阻塞主线程。
如果频繁的执行AOF重写命令,也会对主线程进行阻塞,因为在主线程fork子进程时,fork这个操作就是阻塞主线程的,子进程会拷贝父线程的内存页表,如果内存页很大,那么拷贝的时间也会很长,拷贝未完成之前是会一定会阻塞主线程的,拷贝完内存页之后,子进程和主线程都指向同一份内存地址,此时子进程才开始AOF重写。

在子进程重写期间,若有新的写请求执行,主线程会重新开辟一份空间,此时就做到了子进程和主线程真正的内存分离。

redis log中可以看见每次AOF重写后,主线程申请了多大的内存空间。

优化点:关闭内存大页机制(huge page),目的为了减少主线程申请内存时阻塞的概率。

何时出发AOF重写机制?
手动执行bgrewriteaof命令,主线程fork子进程,来重写AOF文件,缩小文件大小。
通过配置自动触发。

在恢复内存数据时,AOF是一行一行的执行命令,而RDB可以直接让快照数据加载进内存中。
RDB
类似于拍照,记录时间段内的快照数据,支持主线程fork()子线程,来异步进行快照同步。
注意:快照只是记录某一时刻的原始数据。
步骤
执行bgSave命令时,主线程fork()子进程,使用子线程去执行持久化
在RDB期间,子进程跟主线程是互不影响的,也是采用写实复制。子进程通过拷贝了主线程的内存页表,共享了主线程的数据,当主线程要修改正在快照中的数据时,会开启一块缓冲区,将修改的最新值记录在此,不会影响快照保存的数据。

bgsave是写时复制机制, Redis借助操作系统提供的写时复制技术(Copy-On-write), 在生成快照的同时, 依然可以正常处理写命令,
原理: bgsave子进程是由fork生成的, 可以共享主线程的所有内存数据, bgsave子进程运行后, 开始读取主线程的内存数据, 并把他们写入到RDB文件中

比如:t0时刻,执行了rdb持久化,此时peter:20,假设RDB持久化时间为20s,在t0+5时刻,peter:20修改为peter:30,此时我们应该记录的是t0时刻的peter:20,最新的数据只会在下一次快照时同步进RDB文件中。
图片

主进程fork()出的子进程,在进行RDB文件同步时,底层是使用的写实复制技术。当子进程拷贝完主线程的内存页地址后,主子进程都指向同一个内存地址,当有写操作触发的时候,才会进行分离,主进程会单独开辟一块新的空间记录这个修改操作。
图片

尽量要避免频繁的持久化,频繁持久化会带来两个影响:不断地fork()子进程和写入磁盘的消耗,用Redis时,应当时时刻刻具备权衡判断能力和是否会阻塞主线程的思想。一般我们为了权衡利弊:通常使用AOF和RDB混合持久化

一般我们推荐使用混合持久化:新的AOF文件前半段是RDB格式的全量数据后半段是AOF格式的增量数据。在redis重启的时候, 可以先加载在RDB的内容, 再加载剩余的aof文件。

淘汰策略
noeviction
redis3.0版本后,默认情况下,当Redis中的内存空间超过阈值,则不会淘汰数据,再来了写请求,Redis将不会提供服务,而是直接返回错误。

以下四种跟过期时间有关,无论是过期时间快到了,或者到达了Redis的内存空间阈值,都会根据规则进行淘汰
volatile-random
在设置过期时间的键值对中,进行随机删除。
volatile-ttl
在设置过期时间的键值对中,根据过期时间的先后进行删除,越早过期的越先删除。
volatile-lru
会使用LRU算法筛选设置了过期时间的键值对
volatile-lfu
会使用LFU算法选择设置了过期时间的键值对

若无设置过期时间,则范围会扩大,针对于所有的键值对
allkeys-lru
使用LRU算法在所有键值对中进行筛选删除。
allkeys-random
从所有的键值对中随机选择并删除数据。
allkeys-lfu
使用LFU算法在所有键值对中进行筛选删除。

如果一个键值对被淘汰策略选中,那么有可能他过期时间还未到,或者未设置过期时间,他都会被删除。

LRU算法,Redis面试题(一):常见的底层结构,在redisObject中有一个字段就是记录LRU属性的

typedef struct redisObject {// 总空间: 4 bit + 4 bit + 24 bit + 4 byte + 8 byte = 16 byte
unsigned type:4; // 分别存储五种常用的数据类型,String,List,Set,Hash,Zset
unsigned encoding:4; // 更细分,存储上面的编码方式
unsigned lru:LRU_BITS; // lru时间, 用于redis的淘汰机制的
int refcount; // 共享对象,被引用了多少次
void *ptr; // 指向sds地址,sds分多个结构体
} robj;
记录当前对象最近被访问的时间,LRU算法是根据最少使用的原则来进行删选
LRU会把所有的数据组织成一个链表,链表的头和尾分别表示MRU端和LRU端,MRU表示最近刚访问的数据,LRU表示很久没有访问的数据。假设咱们有1,2,3,4,5。五条数据,当先访问了5,则5位于表头,再访问了4,则4在表头,依此访问,链表的最终顺序为5,4,3,2,1。其实Redis的内存空间不够了,要写入6时,会把LRU端的数据给删掉,更新链表的数据为6,5,4,3,2。

LRU算法思想:最早被访问的优先淘汰,最近访问的,可能会再访问,在缓存内存满时,优先删除LRU端的元素。
但每次有数据被访问时,都需要挪动链表顺序,时间复杂度是O(n),为了防止降低Redis性能。Redis在每个RedisObject中设置了lru字段,对LRU算法进行了优化,会随机选出一批数据(数据大小根据配置文件设置),组成一个回收集,从这回收集中把lru字段值最小的数据从缓存中淘汰出去,减去了链表的维护。
当需要再次淘汰数据时,Redis继续挑选一批数据进入到回收集,进入回收集的必要条件:新的一批数据必须是小于回收集中最小的lru值,当回收集的个数到达配置值时,又会优先把最小的lru值元素给淘汰。
CONFIG SET maxmemory-samples 100
// 回收集好像有点G1回收垃圾的味道

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值