浣熊say

2.Redis常见面试题和详解(中)

时间:2024-02-26

作者:浣熊say

字数:4684 字

阅读时间:18 分钟

本站访客数:人次

  • 友情提示

  • 知识星球:知识星球专属面试小册/一对一提问交流/简历修改/职业规划建议/offer选择/模拟面试,欢迎加入 成都央国企指南 知识星球(点击链接即可查看星球的详细介绍,一定确认自己需要再加入!)
  • 浣熊say&公众号:日常更新分享央国企相关干货内容,帮助大家快速获取央国企招聘、面试信息,欢迎加入 浣熊say 公众号,免费公众号希望都大家关注下~
  • 版权声明:该网站上所有文章的版权均为作者(浣熊say)本人所有,转发、分享请注明出处,任何形式用于商业用途的请联系邮 箱:tsinghualeix@gmail.com咨询合作方案,否则必将追究法律责任!

Redis常见面试题&详解

如何解决缓存击穿?

缓存击穿指在高并发的系统中,一个热点数据缓存过期或者在缓存中不存在 ,导致大量并发请求直接访问数据库,从而给数据库造成巨大压力,甚至可能引起宕机。

具体来说,当某个热点数据在缓存中过期时,如果此时有大量并发请求同时访问这个数据,由于缓存中不存在,所有请求都会直接访问数据库,导致数据库负载急剧增加。

img.png

一般来说,解决缓存击穿的主要方法分为三种:加分布式锁、热点数据预加载以及热点数据永不过期。

1. 分布式锁

分布式锁的解决方案就是保证只有一个请求可以访问数据库,其它请求等待结果。这样可以避免大量的请求同时访问数据库。

img_1.png

但是这种的话有一个弊端,那就是获取分布式锁的请求,都会执行一遍查询数据库,并更新到缓存。理论上只有第一个加载数据库记录请求是有效的。所以可以通过双重判定锁的形式,在获取到分布式锁之后,再次查询一次缓存是否存在,如果缓存中存在数据,就直接返回;如果不存在,才继续执行查询数据库的操作。这样就可以避免大量请求访问数据库。双重判定锁有效提升了解锁性能以及数据库访问。

img_2.png

2. 热点数据预加载

热点数据预加载,指的是在活动或者大促开始前,针对已知的热点数据从数据库加载到缓存中,这样可以避免海量请求第一次访问热点数据需要从数据库读取的流程。可以极大减少请求响应时间,有效避免缓存击穿。

3. 热点数据永不过期

热点数据永不过期,指的就是可以预知的热点数据,在活动开始前,设置过期时间为-1,这样的话,就不会有缓存击穿的风险。这个可以搭配热点数据预加载一起完成。等对应热点缓存的活动结束后,这些数据访问量就比较低了,可以通过后台任务的方案对指定缓存设置过期时间,这样可以有效降低 Redis 存储压力。

如何解决缓存穿透?

缓存穿透是指在缓存中查询一个不存在的数据 ,由于缓存不命中,导致请求直接访问数据库,这将导致大量的请求打到数据库上,可能会导致数据库压力过大。

通常情况下,缓存是为了提高数据访问速度,避免频繁查询数据库。但如果攻击者故意请求缓存中不存在的数据,就会导致缓存不命中,请求直接访问数据库。

img_3.png

缓存穿透一般有几种解决方案:

1. 空对象值缓存

img_4.png

当查询结果为空时,也将结果进行缓存,但是设置一个较短的过期时间。这样在接下来的一段时间内,如果再次请求相同的数据,就可以直接从缓存中获取,而不是再次访问数据库。

这种方式是比较简单的一种实现方案,可以很好解决缓存穿透问题,但是会存在一些弊端。那就是当短时间内存在大量恶意请求,缓存系统会存在大量的内存占用。如果要解决这种海量恶意请求带来的内存占用问题,需要搭配一套风控系统,对用户请求缓存不存在数据进行统计,进而封禁用户。整体设计就较为复杂,不推荐使用。

2. 使用锁

img_5.png

当请求发现缓存不存在时,可以使用锁机制来避免多个相同的请求同时访问数据库,只让一个请求去加载数据,其他请求等待。

这种方式可以解决数据库压力过大问题,如果会出现“误杀”现象,那就是如果缓存中不存在但是数据库存在这种情况,也会等待获取锁,用户等待时间过长,不推荐使用。

3. 布隆过滤器

img_6.png

布隆过滤器是一种数据结构,可以用于判断一个元素是否存在于一个集合中。它可以在很大程度上减轻缓存穿透问题,因为它可以快速判断一个数据是否可能存在于缓存中。

这种方式较为推荐,可以将所有存量数据全部放入布隆过滤器,然后如果缓存中不存在数据,紧接着判断布隆过滤器是否存在,如果存在访问数据库请求数据,如果不存在直接返回错误响应即可。

但是这种问题还是会有一些小概率问题,那就是如果使用一种小概率误判的缓存进行攻击,依然会对数据库造成比较大的压力。

4. 组合方案

img_7.png

上面的这些方案或多或少都会有些问题,应该用三者进行组合用来解决缓存穿透问题。如果说缓存不存在,那么就通过布隆过滤器进行初步筛选,然后判断是否存在缓存空值,如果存在直接返回失败。如果不存在缓存空值,使用锁机制避免多个相同请求同时访问数据库。最后,如果请求数据库为空,那么将为空的 Key 进行空对象值缓存。

如果说缓存不存在,那么就通过布隆过滤器进行初步筛选,然后判断是否存在缓存空值,如果存在直接返回失败。如果不存在缓存空值,使用锁机制避免多个相同请求同时访问数据库。最后,如果请求数据库为空,那么将为空的 Key 进行空对象值缓存。

请求布隆过滤器和缓存空值判断会向 Redis 发起两次网络 IO,如果想优化的话,可以使用管道或者 Lua 命令来提高性能。详情参考:✅ 如何提升Redis批量访问性能?(opens new window)

如何解决缓存雪崩?

缓存雪崩是应用系统指在某个时间点上,缓存中的大部分数据同时失效 ,导致大量的请求直接访问底层数据库或后端服务,从而造成数据库负载剧增,甚至导致数据库崩溃的情况。

通常情况下,缓存中的数据会设置不同的过期时间,以避免同时失效的情况。然而,如果某个不可控的事件导致了大量缓存同时失效,就会出现缓存雪崩。

需要从以下几个方面去解决缓存雪崩问题:

1. 热点数据永不过期

对于一些热点数据,可以设置永不过期,以保证这部分数据始终在缓存中可用。

2. 合理设置缓存失效时间

避免所有缓存在同一时间点失效,可以采用随机分布的方式设置缓存失效时间,或者使用带有随机偏移的失效时间。

3. 缓存预热

在系统启动时,将热门数据预先加载到缓存中,避免在高并发时出现缓存失效问题。

4. 使用本地锁或者分布式锁

可以使用锁机制来避免多个相同的请求同时访问数据库,只让一个请求去加载数据,其他请求等待。

通过以上几种方案组合使用,可以一定程度上减少缓存雪崩的可能性。

如何解决Redis大Key问题?

Redis大key问题指的是某个key对应的value值所占的内存空间比较大,导致Redis的性能下降、内存不足、数据不均衡以及主从同步延迟等问题。

到底多大的数据量才算是大key?通常认为字符串类型的key对应的value值占用空间大于1M,或者集合类型的k元素数量超过1万个,就算是大key。

img_8.png

当处理大 Key 问题的时候,我们可以分别从开发层面、业务层面和架构层面这三个层面出发,通过一些手段来解决或者优化大 Key 问题:

1. 开发层面

1.1. 数据压缩存储

在存储数据之前,使用适当的压缩算法来减小数据的大小。比如,对于文本型数据,可以使用 Gzip这种压缩算法进行压缩。而对于一些用于标识的唯一键,可以考虑使用 MD5 或 SHA-256 这类摘要算法进行加密,以减小数据的长度。

1.2. 数据拆分

将大型的数据对象按照业务需求拆分成多个更小的数据对象。例如,如果存储用户的订单历史,可以将每个订单存储为一个单独的Key,而不是将所有订单存储在一个大Key中。这样可以降低单个Key的大小,提高内存利用率。

1.3. 调整数据结构

使用合适的Redis数据结构来存储数据,以减小内存占用。例如,使用Set来存储唯一值,使用Hash或List来组织多个相关数据,以减少重复数据的存储。

1.4. 使用合理的命令

避免使用会长时间阻塞主线程的命令,尤其是在删除大 Key 时,优先使用异步删除命令 UNLINK,而不是 DEL,以避免阻塞主线程。

2. 业务层面

2.1. 调整存储策略

重新规划缓存策略,只在 Redis 存储那些频繁访问的数据。不必要的数据可以在从接口或其他存储引擎中获取。

2.2. 优化业务逻辑

优化业务逻辑,尽量使用更小的数据集来满足业务需求,从根本上避免大 Key 的产生。

2.3. 规划数据生命周期

规划好数据的生命周期,定期清理不必要的数据,或在一开始就设置好合理的过期时间。

3. 架构层面

3.1. 集群部署

如果数据量非常大,可以考虑直接部署集群,将数据分布在多个节点上,降低单个节点的负担。

3.2. 更换存储引擎

比起 Redis ,一些数据可能适合使用更有针对性的存储引擎来存储,比如 Hadoop 或者 ClickHouse。

3.3. 增加资源

如果其他方面的优化无法解决问题,可以考虑直接为 Redis 分配更多资源,例如更大内存、更强性能的服务器。

Redis缓存如何预热?

缓存预热是在系统启动或者运行过程中,提前将部分数据加载到缓存中,以确保在实际请求到来时,缓存已经包含了部分常用数据,从而提升系统的响应速度。

一般来说都是在活动或者需要调用对应缓存前,通过定时任务从数据源中加载到缓存。这些数据一般都会有一个周期,如果缓存过期时间设置不好,还会存在缓存雪崩问题,参考:🛎️ 如何解决缓存雪崩?(opens new window)

其次,可以在预热过程中记录日志或者使用监控工具来监视预热的效果,确保热点数据已经被成功加载到缓存中。

1. 为什么不在项目启动时通过初始化任务加载?

如果一个项目启动多个节点,那么就会涉及到加载多次缓存,进而造成资源浪费。或者可以使用分布式锁等工具,来保证缓存预热仅加载一次,但这会加重代码复杂度。

如果项目中使用了 XXL-Job 等分布式定时任务框架,可以直接使用定时任务解决缓存预热。反之,可以使用分布式锁以及其它方式进行项目启动时预热。

2. 如何确认缓存预热数据

需要预热的数据一般都有一个特点,那就是已知这些数据会被大量访问。

大部分情况下,苹果15不显眼,但是如果参加了活动,比如某直播间1块钱抢购苹果15、某商品大降价或者抢购茅台酒等,像这种已知会很火的数据,不用想直接加缓存。

还有一种就是类似于直播带货里的商品橱窗,就算商品价格没有什么优势,但是人数上来后,大家可能感兴趣在那点点点。

如何发现缓存中热Key?

1. 读写分离

当热 Key 来自于读请求时,可以考虑通过读写分离,并且增加更多的从节点来降低对单个实例的读请求压力。

这种方案的缺点在于,不是所有的业务都适合读写分离,并且集群架构的复杂度提升也会带来额外的运维压力。

2. 热 Key 备份

我们可以将热 Key 复制到多个 Redis 实例,并在请求时随机选择一个备份实例来分散请求压力。

比如,假如现有热 Key “foo”,我们将其复制到三个分片中,分别叫做 foo_1、foo_2 与 foo_3,当请求 “foo” 时,我们为其随机添加一个后缀,从而实现分散请求压力的效果:

1
2
3
4
5
6
7
// 生成1到3的随机数
int randomValue = new Random().nextInt(3) + 1;
// 将随机数作为后缀拼接到key上
String updatedKey = key + "-" + randomValue;
// 将key作为Redis中的键,随机值作为值存入Redis
redisTemplate.opsForValue().get(updatedKey);
这种策略可以减轻对单个 Redis 实例的负载,但是在涉及到增删改操作时,需要考虑数据一致性问题。

3. 二级缓存

对于一些热 Key,我们可以考虑使用一些本地缓存工具(比如 Guava 或 Ehcache) 直接将其缓存到 JVM 内存中,从根本上避免对 Redis 造成压力。

不过,这种方案下缓存会占用额外的运行时内存,因此需要有选择进行缓存,以避免占用过多内存资源。并且当与 Redis 缓存共用时,也要考虑数据一致性问题。

  • 免责声明

  • 本文发布的信息,最终解释权归作者所有,如有疑问或需要进一步了解,请直接与作者联系。
  • 本文部分信息来源于网络,其版权归原作者所有,本文使用该信息仅出于分享给求职者,无任何商业用途,如有侵权,请联系作者删除。
  • 本文发布的信息旨在为应届生和社会人士提供了解相关企业招聘信息的便利,不涉及任何机密信息,且所有信息均为网络公开获取。 本人亦无任何渠道可以了解任何机密信息, 如果您认为某些内容存在侵权、泄密问题,请及时联系作者删除。
使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

更多央国企信息、面试辅导、简历修改等,请关注知识星球:[成都央国企指南]