Redis缓存雪崩、缓存穿透
目录
一、缓存雪崩
缓存雪崩:由于原有缓存失效,新缓存未到期间(例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。
缓存正常从redis获取
缓存失效瞬间
解决方案
1.加锁排队(线程阻塞,用户体验差,真正高并发场景下很少使用)
通过加锁排队从而避免缓存失效时大量的并发请求落到底层存储系统上。
[code]//伪代码 public Object getProductList() { int cacheTime = 30; String cacheKey = "product_list"; String lockKey = cacheKey; //从缓存中获取值 String cacheValue = CacheHelper.get(cacheKey); if (cacheValue != null) { return cacheValue; } else { //加锁排队 线程阻塞 假设有10000万个请求,那么9999个请求阻塞中 治标不治本 用户体验差 synchronized(lockKey) {//缓存重建期间key是锁着 cacheValue = CacheHelper.get(cacheKey); if (cacheValue != null) { return cacheValue; } else { //新缓存未到期间 //db查询数据 cacheValue = getProductListFromDB(); //更新数据缓存 CacheHelper.add(cacheKey, cacheValue, cacheTime); } } return cacheValue; } }
2.缓存标记(推荐)
缓存标记:记录缓存数据是否过期,如果过期会触发通知另外的线程在后台去更新实际key的缓存;
缓存数据:它的过期时间比缓存标记的时间延长1倍,例:标记缓存时间30分钟,数据缓存设置为60分钟。 这样,当缓存标记key过期后,实际缓存还能把旧数据返回给调用端,直到另外的线程在后台更新完成后,才会返回新缓存。
[code]//伪代码 public Object getProductList() { int cacheTime = 30; String cacheKey = "product_list"; //给要缓存的数据加个缓存标记 String cacheSign = cacheKey + "_sign"; //从缓存里获取 缓存标记 String sign = CacheHelper.get(cacheSign); //从缓存里获取 缓存数据 String cacheValue = CacheHelper.get(cacheKey); if (sign != null) { return cacheValue; } else { //缓存标记过期,刷新缓存标记 CacheHelper.add(cacheSign, "1", cacheTime); //通知另一个线程在后台更新缓存 ThreadPool.QueueUserWorkItem((arg) -> { //db查询数据 cacheValue = getProductListFromDB(); //日期设缓存时间的2倍 用于脏读 CacheHelper.add(cacheKey, cacheValue, cacheTime * 2); }); return cacheValue; } }
3.二级缓存(暂没)
二、缓存穿透
缓存穿透:就是用户查询数据,数据库里没有,缓存里自然也没有,缓存中找不到,每次都去查询数据库,相当于缓存没用。这也就是常说的提高缓存命中率。
解决方案:
1.布隆过滤器
将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
使用BloomFilter布隆过滤器解决缓存击穿、垃圾邮件识别、集合判重
https://www.geek-share.com/detail/2710415783.html
缓存击穿之布隆过滤器bloom Filter实现方式
https://www.geek-share.com/detail/2719984917.html
2.空结果缓存(欠优化)
如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。通过这个直接设置的默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库。
[code]//伪代码 public object getProductList() { int cacheTime = 30; String cacheKey = "product_list"; String cacheValue = CacheHelper.get(cacheKey); if (cacheValue != null) { return cacheValue; } cacheValue = CacheHelper.get(cacheKey); if (cacheValue != null) { return cacheValue; } else { //数据库查询不到,为空 cacheValue = getProductListFromDB(); if (cacheValue == null) { //如果发现为空,设置个默认值,也缓存起来 cacheValue = string.Empty; } CacheHelper.add(cacheKey, cacheValue, cacheTime); return cacheValue; } }
阅读更多
- Redis缓存穿透、缓存雪崩
- Redis缓存穿透、缓存并发、缓存雪崩
- Redis缓存穿透、缓存雪崩、redis并发问题分析
- Redis缓存雪崩、缓存穿透、热点Key解决方案和分析
- Redis缓存穿透、缓存雪崩、redis并发问题分析
- redis-缓存穿透与缓存雪崩
- 缓存穿透,缓存击穿,缓存雪崩解决方案分析
- 缓存穿透与缓存雪崩
- 缓存各种问题汇总:缓存雪崩和缓存穿透等问题
- 缓存穿透与缓存雪崩
- 缓存穿透与缓存雪崩(转)
- 常用场景下缓存穿透,缓存雪崩,缓存并发处理策略
- 缓存穿透与缓存雪崩
- 关于Redis中缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级等概念的入门及简单解决方案
- 缓存穿透与缓存雪崩
- 名词解释“缓存穿透”和“缓存雪崩”
- 缓存穿透 缓存雪崩
- 分布式缓存--序列4--缓存更新策略/缓存穿透/缓存雪崩
- 缓存雪崩,缓存穿透解决方案
- 缓存穿透与缓存雪崩的解决方案