Redis---缓存雪崩,缓存穿透,缓存击穿的区别及解决方案
2021/8/19 19:06:42
本文主要是介绍Redis---缓存雪崩,缓存穿透,缓存击穿的区别及解决方案,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
一、缓存处理流程
前台请求,后台先从缓存中取数据,取到直接返回结果,取不到时从数据库中取,数据库取到更新缓存,并返回结果,数据库也没取到,那直接返回空结果。
二:缓存雪崩
概念:当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候由于查询数据量巨大,引起数据库压力过大甚至down机。
解决方案:
- 缓存数据的过期时间在一个基础的时间上加一个随机值,防止同一时间大量数据过期现象发生。
- 如果缓存数据库是分布式部署,将热点数据均匀分布在不同缓存数据库中。
- 设置热点数据永远不过期。
三:缓存穿透
概念: 缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,缓存层和存储层都不会命中,通常出于容错的考虑,如果从存储层查不到数据则不写入缓存层。
例如用户不断发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。这时可以在接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
解决方案一:缓存空对象
/** * 缓存空对象: * 此种方式存在漏洞,不经过判断就直接将Null对象存入到缓存中, * 如果恶意制造很多不存在的id,那么缓存中的键值就会很多,恶意攻击时,很可能会被打爆,所以需设置较短的过期时间。 */ public Object getObjectInclNullById(Integer id) { // 从缓存中获取数据 Object cacheValue = cache.get(id); // 缓存为空 if (cacheValue != null) { // 从数据库中获取 Object storageValue = storage.get(key); // 缓存空对象 cache.set(key, storageValue); // 如果存储数据为空,需要设置一个过期时间(300秒) if (storageValue == null) { // 必须设置过期时间,否则有被攻击的风险 cache.expire(key, 60 * 5); } return storageValue; } return cacheValue; }
缓存空对象会有一个必须考虑的问题:
缓存空对象的时候缓存层中会存更多的键,需要更多的内存空间(如果是攻击,问题更严重),比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。
解决方案二:布隆过滤器拦截
布隆过滤器(英语:Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。如果想判断一个元素是不是在一个集合里,一般想到的是将集合中所有元素保存起来,然后通过比较确定。链表、树、散列表(又叫哈希表,Hash table)等等数据结构都是这种思路。但是随着集合中元素的增加,我们需要的存储空间越来越大。同时检索速度也越来越慢,上述三种结构的检索时间复杂度分别为 O(n),O(log n),O(n/k)。
布隆过滤器的原理是,当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。这就是布隆过滤器的基本思想。
示例:google guava包下有对布隆过滤器的封装,BloomFilter。
import com.google.common.hash.BloomFilter; import com.google.common.hash.Funnels; public class BloomFilterTest { // 初始化一个能够容纳10000个元素且容错率为0.01布隆过滤器 private static final BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), 10000, 0.01); //初始化布隆过滤器 private static void initLegalIdsBloomFilter() { // 初始化10000个合法Id并加入到过滤器中 for (int legalId = 0; legalId < 10000; legalId++) { bloomFilter.put(legalId); } } //id是否合法有效,即是否在过滤器中 public static boolean validateIdInBloomFilter(Integer id) { return bloomFilter.mightContain(id); } public static void main(String[] args) { // 初始化过滤器 initLegalIdsBloomFilter(); // 误判个数 int errorNum=0; // 验证从10000个非法id是否有效 for (int id = 10000; id < 20000; id++) { if (validateIdInBloomFilter(id)){ // 误判数 errorNum++; } } System.out.println("judge error num is : " + errorNum); } }
实现布隆过滤器拦截
设置过期时间,让其自动过期失效,这种在很多时候不是最佳的实践方案。
我们可以提前将真实正确的商品Id,在添加完成之后便加入到过滤器当中,每次再进行查询时,先确认要查询的Id是否在过滤器当中,如果不在,则说明Id为非法Id,则不需要进行后续的查询步骤了。
/** * 防缓存穿透的:布隆过滤器 */ public Object getObjectByBloom(Integer id) { // 判断是否为合法id if (!bloomFilter.mightContain(id)) { // 非法id,则不允许继续查库 return null; } else { // 从缓存中获取数据 Object cacheValue = cache.get(id); // 缓存为空 if (cacheValue == null) { // 从数据库中获取 Object storageValue = storage.get(id); // 缓存空对象 cache.set(id, storageValue); } return cacheValue; } }
四:缓存击穿
概念:缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
补充:通常使用缓存 + 过期时间的策略来帮助我们加速接口的访问速度,减少了后端负载,同时保证功能的更新,一般情况下这种模式已经基本满足要求了。但如下两个问题如果同时出现,可能就会对系统造成致命的危害:其一:这个key是一个热点key,其二是key的访问量非常大缓存的构建是需要一定时间的。(可能是一个复杂计算,例如复杂的sql、多次IO、多个依赖(各种接口)等等),于是就会出现一个致命问题:在缓存失效的瞬间,有大量线程来构建缓存,造成后端负载加大,甚至可能会让系统崩溃 。
解决方案:
- 设置热点数据永远不过期。
- 加互斥锁,互斥锁参考代码如下:
缓存击穿和缓存雪崩的区别在于这里针对某一key缓存,而缓存雪崩是很多key。
这篇关于Redis---缓存雪崩,缓存穿透,缓存击穿的区别及解决方案的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-12-07Redis高并发入门详解
- 2024-12-07Redis缓存入门:新手必读指南
- 2024-12-07Redis缓存入门:新手必读教程
- 2024-12-07Redis入门:新手必备的简单教程
- 2024-12-07Redis入门:新手必读的简单教程
- 2024-12-06Redis入门教程:从安装到基本操作
- 2024-12-06Redis缓存入门教程:轻松掌握缓存技巧
- 2024-12-04Redis入门:简单教程详解
- 2024-11-29Redis开发入门教程:从零开始学习Redis
- 2024-11-27Redis入门指南:快速掌握Redis基础操作