开篇

​ 将redis的面试题分两个部分

image-20230729224539408

使用场景

缓存

❤️如果发生了缓存穿透、击穿、雪崩,该如何解决

这三种情况都可以通过添加降级限流策略来缓解

  • 缓存穿透是客户端请求了一个不存在的数据,在redis缓存中,就会去查询数据库重建缓存,但是因为数据不存在,所以没办法写入缓存,就会导致每次查询都访问数据,所以就有了被人开启大量线程访问,数据库被大量访问崩溃的风险
  • 解决方案一:存空对象,如果数据库查询不存在那就将空对象存入redis,那么下一次查询就不会进入数据库查询,缺点是内存消耗较大
  • 解决方案二:布隆过滤器,布隆过滤器采用位图,存储和查询都是将key经过三次hash,将每次的结果设置为1和比较是否为1,如果都为1则认为redis有数据,放行查询redis,否则拒绝访问,所以布隆过滤器需要在redis缓存预热时,预热布隆过滤器,同时布隆过滤器有一定的误判的概率,一般这个位图的越大,概率越小,所以我们可以设置误判的概率,一般是5%就比较合适,既满足系统可接受的程度,又不会有太大的内存消耗
  • 缓存击穿是指高并发环境下,热点key过期失效了,此时大量线程尝试重建缓存去访问数据库,有可能造成数据库崩溃
  • 解决方案一:加上互斥锁,强一致性,效率较低
  • 解决方案二:逻辑过期,热点key多存储一个过期时间的字段,由客户端取出来判断是否过期了,如果过期了,加互斥锁开启一个新线程去执行缓存重建的工作,旧的线程返回旧数据,其他加锁失败的线程也返回旧数据,高可用,延迟一致
  • 缓存雪崩指的是某一时刻大量的key失效或者是redis服务宕机,造成大量的访问数据库,有数据库崩溃风险
  • 一般解决办法有设置key的时候加上一定范围的随机时间,添加本地缓存,还有搭建redis集群
❤️redis做为缓存,mysql的数据如何与redis进行同步呢?(双写一致性)

一定、一定、一定要设置前提,先介绍自己的业务背景

  • 最终一致性,我的项目采用的是当有数据修改时,先修改数据库的数据,再删除redis缓存,主要是在缓存重建的过程中查询数据库是耗时较久的,而向redis存数据的操作是很快的,所以很少会发生过期失效后重建查询数据库之后,切换线程修改了数据,再次切换将旧的数据存入redis。如果真要解决这种情况可以在删除线程后延迟一段时间再次删除,再次重建缓存,如果数据库是主从结构的也可以采用延迟双删的策略,解决主从未同步成功时采用从库的旧数据重建了缓存。总的来说延迟双删的策略还是无法保证缓存和数据库的强一致性,具体要看业务是否可以接受
  • 其他最终一致性解决方案:阿里的canal中间件,部署一个canal服务,canal把自己伪装成MySQL的一个从节点,当MySQL数据更新后,canal读取binlog日志,然后通过canal的客户端获取到的数据更新缓存即可
  • 如果要保证强一致性那么要么可以使用redisson的读写锁
❤️redis做为缓存,数据的持久化是怎么做的?

redis中提供了两种持久化方案,RDB和AOF,RDB是备份当前时刻的快照数据写到磁盘,AOF是记录写命令,执行bgrewriteaof命令,可以让AOF文件执行重写功能,其中RDB是二进制文件保存时体积小,恢复快,但是有可能丢失数据,AOF虽然恢复慢,但是丢失数据风险小

❤️假如redis的key过期之后,会立即删除吗?(过期策略)

redis提供了两种过期策略,惰性过期和定期过期

  • 惰性过期,当key过期后,不管他,只有下一次查询时判断是否过期了决定是否删除它

  • 定期过期,每隔一段时间检查一部分数据是否过期,删除过期key,定期过期有两种模式:

    SLOW模式是定时任务,执行频率默认为10hz,每次不超过25ms,以通过修改配 置文件redis.conf 的 hz 选项来调整这个次数

    FAST模式执行频率不固定,每次事件循环会尝试执行,但两次间隔不低于2ms, 每次耗时不超过1ms

Redis的过期删除策略:惰性删除 + 定期删除两种策略进行配合使用。

❤️假如缓存过多,内存是有限的,内存被占满了怎么办?(淘汰策略)

这个在redis中提供了很多种,默认是noeviction,不删除任何数据,内部不足直接报错是可以在redis的配置文件中进行设置的,里面有两个非常重要的概念,一个是LRU,另外一个是LFU

LRU的意思就是最少最近使用,用当前时间减去最后一次访问时间,这个值 越大则淘汰优先级越高。

LFU的意思是最少频率使用。会统计每个key的访问频率,值越小淘汰优先级越高, 我们在项目设置的allkeys-lru,挑选最近最少使用的数据淘汰,把一些经常 访问的key留在redis中

noeviction: 不淘汰任何key,但是内存满时不允许写入新数据,默认就是这种策略。
volatile-ttl: 对设置了TTL的key,比较key的剩余TTL值,TTL越小越先被淘汰
allkeys-random:对全体key ,随机进行淘汰。
volatile-random:对设置了TTL的key ,随机进行淘汰。
allkeys-lru: 对全体key,基于LRU算法进行淘汰
volatile-lru: 对设置了TTL的key,基于LRU算法进行淘汰
allkeys-lfu: 对全体key,基于LFU算法进行淘汰
volatile-lfu: 对设置了TTL的key,基于LFU算法进行淘汰

分布式锁

❤️redis分布式锁,是如何实现的?

redis分布式锁在我的项目中是为了解决tomcat集群情况下,避免优惠券超卖的问题,redis实现分布式锁主要是利用了redis的setnx 命令,这种方案需要我们自己实现线程对比或者锁续期(看门狗),避免锁被其他线程误删

另一种redisson分布式锁方案(底层是setnx和lua脚本),引入看门狗机制对锁续期,未获得锁的线程自旋尝试加锁,一般使用他的可重入锁即可,如果是redis集群情况下,他是通过hash算法找到对应的一台redis执行lua脚本加锁,但是如果是redis主从结构就有可能主库宕机,从库变成主库,此时锁丢失,其他线程又可以加锁造成多线程问题,解决这种问题就是使用redisson的红锁(效率差,非要保证数据的强一致性,建议采用zookeeper),红锁的实现原理是对多个主节点setnx,只有超过一半的节点写成功才算成功加锁,这样即使有一台redis主节点宕机,那么其他线程永远不可能写入超过一般的节点

其他面试题

❤️Redis集群有哪些方案

主从复制
哨兵模式
分片集群

❤️redis主从数据同步的流程是什么?
❤️怎么保证redis的高并发高可用?
❤️你们使用redis是单点还是集群,哪种集群?
❤️Redis分片集群中数据是怎么存储和读取的?
❤️edis集群脑裂,该怎么解决呢?