设置log缓存_Redis中缓存击穿 缓存穿透 缓存雪崩解决方案
戳上方蓝字 “给自己一个smile” 关注我 并 置顶星标!
你的关注意义重大!
目录
1. 缓存击穿
2. 缓存穿透
3. 缓存雪崩
4. 小结
一 缓存击穿
指的是单个key在缓存中查不到,去数据库查询,这样如果数据量不大或者并发不大的话是没有什么问题的
如果数据库数据量大并且是高并发的情况下那么就可能会造成数据库压力过大而崩溃
1. 执行流程
2. 业务场景
假设有个获取文章详情接口,后台发布一篇新文章,文章为置顶推荐文章,这时有2000用户在同一秒钟同时访问这篇文章详情
如果这篇文章没有缓存,会导致数据压力过大!
3. 举个栗子
获取文章详情 getContentDetail
/** * 获取文章详情 01-- 防止缓存击穿 * * @date: 2020/12/2 14:10 * @return: Content */ @GetMapping("getContentDetail") public Content getContentDetail(@RequestParam(value = "contentId") Long contentId) {
log.info("getContentDetail.req contentId={}", contentId); Content content; String detail = CONTENT + ":" + DETAIL + ":" + contentId; String value = redisService.get(detail); if (StringUtils.isNotEmpty(value)) { log.info("从缓存获取数据....."); return JSON.parseObject(value, Content.class); } content = getData(contentId); // 查询文章内容不空设置缓存为10min if (Objects.nonNull(content)) { redisService.setKeyByMINUTES(detail, JSON.toJSONString(content), 10); } return content;
}
模拟数据库查询数据
/** * 模拟数据库查询数据 * * @param contentId 文章ID * @date: 2020/12/30 10:50 * @return: com.zlp.entity.Content */ private Content getData(Long contentId) {
log.info("getData查询数据库获取数据count={}.....", count.incrementAndGet()); try { Thread.sleep(40); Optional con = contentList.stream().filter(content -> content.getContentId().equals(contentId)).findFirst();if (con.isPresent()) {return con.get(); } } catch (InterruptedException e) { e.printStackTrace(); }return null; }
contentList装载数据容器
/** * 构建一个contentList数据容器 */ private static List contentList = new ArrayList<>();static { contentList.add(new Content(1L, "说好不哭", "没有联络,后来的生活!", 1)); contentList.add(new Content(2L, "告别气球", "塞拉河畔,左岸的咖啡", 1)); contentList.add(new Content(3L, "等你下课", "你住的学校旁,我租了一间公寓", 1));
4. 模拟测试
假设用1000用户,在同一秒时间内对这个接口进行访问,我们用AtomicInteger记录需要查询数据库次数!
Jmeter参数配置如下
线程数:1000
循环次数:10
任务执行时间 :5
控制台打印日志
结果
发现在同一时刻访问接口,设置缓存没有启动作用,怎样只能查询数据一次,其它到查询过滤到缓存数据,就会减轻数据库压力
这时我们可以添加分布式锁,只允许一个线程查询数据库,让其它线程自旋调用该方法,在查询缓存数据
/** * 获取文章详情 -- 防止缓存穿透 * * @date: 2020/12/2 14:10 * @return: Content */ @GetMapping("getContentDetail02") public Content getContentDetail02(@RequestParam(value = "contentId") Long contentId) {
log.info("getContentDetail02.req contentId={}", contentId); Content content; String detail = CONTENT + ":" + DETAIL + ":" + contentId; String conLock = CONTENT + ":" + LOCK + ":" + contentId; String lock = ""; String value = redisService.get(detail); if (StringUtils.isNotEmpty(value)) { log.info("从缓存获取数据....."); return JSON.parseObject(value, Content.class); } try { lock = redisService.getLock(conLock, 10); if (StringUtils.isNotEmpty(lock)) { content = getData(contentId); // 查询文章内容不空设置缓存为10min if (Objects.nonNull(content)) { redisService.setKeyByMINUTES(detail, JSON.toJSONString(content), 10); } // 查询文章内容为空设置缓存为1min,避免缓存穿透 redisService.setKeyByMINUTES(detail, JSON.toJSONString(content), 1); return content; } // 休眠重新尝试调用方法 Thread.sleep(300); getContentDetail(contentId); } catch (Exception e) { e.printStackTrace(); redisService.unLock(detail, lock); } finally { redisService.unLock(detail, lock); } return null; }
测试结果如下,只查询数据库一次
二 缓存穿透
一般是出现这种情况是因为恶意频繁查询才会对系统造成很大的问题: key缓存并且数据库不存 在所以每次查询都会查询数据库从而导致数据库崩溃。
1. 解决思路
从DB中查询出来数据为空,也把空数据进行缓存,避免DB数据为空也每次都进行数据库查询,过期时间设置短一些
// 查询文章内容不空设置缓存为10min if (Objects.nonNull(content)) { redisService.setKeyByMINUTES(detail, JSON.toJSONString(content), 10); } // 查询文章内容为空设置缓存为1min,避免缓存穿透 redisService.setKeyByMINUTES(detail, JSON.toJSONString(content), 1);
缺点 :会造成缓存数据库有无效的key,指标不能本
2. 布隆过滤器
使用布隆过滤器,但是会增加一定的复杂度及存在一定的误判率(判断不存在肯定是不存在,判断存在可能会不存在)
布隆过滤器原理
布隆过滤器(Bloom Filter)的核心实现是一个超大的位数组和几个哈希函数。假设位数组的长度为m,哈希函数的个数为k
以上图为例,具体的操作流程:假设集合里面有3个元素{x, y, z},哈希函数的个数为3。首先将位数组进行初始化,将里面每个位都设置位0。对于集合里面的每一个元素,将元素依次通过3个哈希函数进行映射,每次映射都会产生一个哈希值,这个值对应位数组上面的一个点,然后将位数组对应的位置标记为1。查询W元素是否存在集合中的时候,同样的方法将W通过哈希映射到位数组上的3个点。如果3个点的其中有一个点不为1,则可以判断该元素一定不存在集合中。反之,如果3个点都为1,则该元素可能存在集合中。注意:此处不能判断该元素是否一定存在集合中,可能存在一定的误判率。可以从图中可以看到:假设某个元素通过映射对应下标为4,5,6这3个点。虽然这3个点都为1,但是很明显这3个点是不同元素经过哈希得到的位置,因此这种情况说明元素虽然不在集合中,也可能对应的都是1,这是误判率存在的原因。
布隆过滤器添加元素
将要添加的元素给k个哈希函数
得到对应于位数组上的k个位置
将这k个位置设为1
布隆过滤器查询元素
将要查询的元素给k个哈希函数
得到对应于位数组上的k个位置
如果k个位置有一个为0,则肯定不在集合中
如果k个位置全部为1,则可能在集合中
布隆过滤器实现
我们借助Redisson来实现布隆顾虑器
首先我们初始化布隆过滤器数据
/** * 初始化布隆过滤器数据 * * @date: 2020/12/30 13:34 * @return: void */ @GetMapping("initContentBloomData") public void initContentBloomData() {
log.info("初始化布隆过滤器数据==>initContentBloomData..."); RBloomFilter bloomFilter = redissonClient.getBloomFilter(BloomFilterConstants.CONTENT_PREFIX);//初始化布隆过滤器,var1表示容量大小,var3表示容错率 bloomFilter.tryInit(1000L, 0.0001);for (long i = 1L; i 1000; i++) { bloomFilter.add(i); }log.info("CONTENT_PREFIX:1 是否存在:" + bloomFilter.contains(1));log.info("CONTENT_PREFIX:2 是否存在:" + bloomFilter.contains(1002));log.info("预计插入数量:" + bloomFilter.getExpectedInsertions());log.info("容错率:" + bloomFilter.getFalseProbability());log.info("hash函数的个数:" + bloomFilter.getHashIterations());log.info("插入对象的个数:" + bloomFilter.count()); }
/** * 获取文章详情03 -- 防止缓存穿透 * * @date: 2020/12/2 14:10 * @return: Content */ @GetMapping("getContentDetail03") public Content getContentDetail03(@RequestParam(value = "contentId") Long contentId) {
log.info("getContentDetail03.req contentId={}", contentId); Content content; String detail = CONTENT + ":" + DETAIL + ":" + contentId; String conLock = CONTENT + ":" + LOCK + ":" + contentId; RBloomFilter bloomFilter = redissonClient.getBloomFilter(BloomFilterConstants.CONTENT_PREFIX);if (Boolean.FALSE.equals(bloomFilter.contains(contentId))) { log.info("bloomFilter.contentId={},非法的文章ID", detail);return null; } String lock = ""; String value = redisService.get(detail);if (StringUtils.isNotEmpty(value)) { log.info("从缓存获取数据.....");return JSON.parseObject(value, Content.class); }try { lock = redisService.getLock(conLock, 10);if (StringUtils.isNotEmpty(lock)) { content = getData(contentId);// 查询文章内容不空设置缓存为10minif (Objects.nonNull(content)) { redisService.setKeyByMINUTES(detail, JSON.toJSONString(content), 10); }// 查询文章内容为空设置缓存为1min,避免缓存穿透 redisService.setKeyByMINUTES(detail, JSON.toJSONString(content), 1);return content; }// 休眠重新尝试调用方法 Thread.sleep(300); getContentDetail(contentId); } catch (Exception e) { e.printStackTrace(); redisService.unLock(detail, lock); } finally { redisService.unLock(detail, lock); }return null; }
测试
我们首先初始化数据,再调用getContentDetail03接口如下
我们做两次测试
第一次contentId=5
控制台输出
第二次contentId=1003
控制台输出
三 缓存雪崩
雪崩指的是多个key查询并且出现高并发,缓存中失效或者查不到,然后都去db查询,从而导致db压力突然飙升
从而崩溃。出现原因: 1 key同时失效, 2 redis本身崩溃了
1. 解决思路
在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。(跟击穿的第一个方案类似,但是这样是避免不了其它key去查数据库,只能减少查询的次数)
实现redis高可用
不同的key,设置不同的过期时间,具体值可以根据业务决定,让缓存失效的时间点尽量均匀
可以设置备用缓存,A设置有过期时间,B缓存没有过期时间
四 小结
不管是缓存击穿和还是缓存雪崩,都是缓存穿透的一种表现形式,针对访问频率过高的接口,我们可以针对接口限流和服务降级保护,达到服务可用性,可以利用白名单黑名单防止接口恶意攻击
参考连接
https://www.cnblogs.com/cpselvis/p/6265825.html
https://blog.csdn.net/zouliping123456/article/details/111995888
推荐阅读
SpringCloud Alibaba教程系列-使用Nacos实现服务注册与发现(一)
- Spring Cloud Alibaba基础教程:使用Nacos作为配置中心(二)
- SpringCloud Alibaba教程系列-Nacos多环境配置详解 (三)
SpringBoot整合多数据源
- 《肖申克的救赎》观后感
如何优雅利用SpringSecurity+JWT实现验证授权
如何优雅使用:Stream API 操作数据
- SpringBoot整合ELK日志收集
Spring中涉及设计模式总结
给自己一个smile
欢迎关注,点个在看
设置log缓存_Redis中缓存击穿 缓存穿透 缓存雪崩解决方案相关推荐
- 【Redis系列】缓存击穿、穿透、雪崩解决方案详解
文章目录 前言 一.击穿 1.介绍 2.产生原因 3.解决方案 二.穿透 1.介绍 2.产生原因 3.解决方案 三.雪崩 1.介绍 2.产生原因 3.解决方案 结尾 前言 众所周知,计算机的瓶颈之一就 ...
- 《如何与面试官处朋友》系列-缓存击穿、穿透、雪崩场景原理大调解
前面我们提到分布式多级缓存架构的全貌,但总感觉少了些什么东西.在这样大的场景下面,如果遇到缓存使用问题那可咋办?但自古英雄出少年,相信此刻你已踏马西去,正走在寻找答案上得夕阳西下.每每面谈Redis大 ...
- Redis常见面试题(缓存击穿、穿透、雪崩)
Redis常见面试题(缓存击穿.穿透.雪崩) 击穿 场景: 一般由于redis中的数据到期,同时并发用户特别多,此时大量请求压到数据库上. 解决思路: 根据redis是单进程单实例的特性,当高流量进入 ...
- 使用jedis连接redis-cluster模拟缓存击穿,穿透,雪崩场景
上一篇演示了通过redis实现ID生成器,本篇模拟缓存击穿,穿透,雪崩的场景. package com.coderman.jedis.clusterdemo.hack;import com.coder ...
- beyond compare4过期解决方法_面试必备:缓存穿透、雪崩解决方案及缓存击穿的四种解决方案...
前言 设计一个缓存系统,不得不要考虑的问题就是:缓存穿透.缓存击穿与失效时的雪崩效应. 缓存穿透 缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到 ...
- php怎么解决雪崩或穿透,Redis之缓存击穿、穿透、雪崩、预热,以及如何解决?...
数据获取的流程,一般是前端请求,后台先从缓存中取数据,缓存取不到则去数据库中取,数据库取到了则返回给前端,然后更新缓存,如果数据库取不到则返回空数据给前端 流程图: 假如缓存的数据没有,后台则会一直请 ...
- Redis——缓存击穿、穿透、雪崩
1.缓存穿透: (1)问题描述:key对应的数据并不存在,每次请求访问key时,缓存中查找不到,请求都会直接访问到数据库中去,请求量超出数据库时,便会导致数据库崩溃.如一个用户id不存在,数据库与缓存 ...
- 缓存击穿,穿透,雪崩
一.缓存击穿 单个热点key,在不停的扛着大并发,在这个key失效的瞬间,持续的大并发请求就会击破缓存,直接请求到数据库 解决方案 使用互斥锁(Mutex Key),只让一个线程构建缓存,其他线程等待 ...
- Redis 击穿、穿透、雪崩产生原因以及解决思路
- 前言 - 大家都知道,计算机的瓶颈之一就是IO,为了解决内存与磁盘速度不匹配的问题,产生了缓存,将一些热点数据放在内存中,随用随取,降低连接到数据库的请求链接,避免数据库挂掉.需要注 ...
最新文章
- 程序员之网络安全系列(三):数据加密之对称加密算法
- MySQL两种表存储结构MyISAM和InnoDB的性能比较测试
- 03 在百度地图上定位到指定位置
- JSR 303约束规则
- 数据科学篇| Seaborn库的使用(四)
- ORACLE---Unit04: SQL(高级查询)
- [20171130]关于rman的一些总结.txt
- Hbase Shell Filter 过滤
- 移动网流量用户身份识别系统的源代码_真武庙车辆识别系统安装效果图
- 美国节日(求某天是星期几)
- 微信小程序会议管理+后台管理系统
- 为什么房价不能跌,房租必须涨
- 品牌稿件怎么写?这些品牌稿件写作技巧值得一看
- 微信上线新功能 看到这个提醒一定要接听
- 在linux下 用户的密码错误,linux中root用户密码错误如何解决
- LDAP(Lightweight Directory Access Protocol)介绍
- 3:表的基本操作-MySQL
- ajax返回字符串长度限制,JS字符串长度判断,超出进行自动截取的实例(支持中文)...
- stinger 小型机器人_具备人脸识别的杀人蜂小型机器人
- cpalgotithm安装大坑
热门文章
- MySQL不能启动 Can't start server : Bind on unix socket: Permission denied
- Python os模块文件操作(一)
- 『ExtJS』表单(一)常用表单控件及内置验证
- 怎样把输入的文本转换成html代码存入数据库啊
- 漫步数理统计十三——特殊的期望
- 漫步数学分析四——集合内部
- [机器学习-总结] 什么是准确率, 精确率,召回率和(精确率和召回率的调和平均)
- 【例题+习题】【数值计算方法复习】【湘潭大学】(五)
- 国外问答网站Quora数据的爬虫 Java
- C++/C--删除string末尾字符的方法【转载】