Redis缓存击穿、缓存穿透、缓存雪崩原理及解决方案

一.前言

  • Redis缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面。但同时,它也带来了一些问题。其中,最要害的问题,就是数据的一致性问题,从严格意义上讲,这个问题无解
  • 如果对数据的一致性要求很高,那么就不能使用缓存。在缓存使用的过程中也存在一些典型的问题:缓存击穿、缓存穿透、缓存雪崩

二.缓存击穿

(1)概念
  • 缓存击穿:大并发集中对某一数据进行访问,并且缓存中不存在访问的数据(第一次访问或者缓存过期),持续的大并发就穿破缓存,直接请求数据库。
(2)如何解决
  • 双重检测锁机制(提升效率)
//ItemService
public Item queryItemById(String itemId){
    Item item = itemCacheHander.getItemFromCache(itemId);
    if(item == null) {
        synchronized (this) {
            item = itemCacheHander.getItemFromCache(itemId);
            if (item == null) {
                System.out.println("‐‐‐‐‐‐‐‐‐‐‐‐‐‐查询数据库");
                item = itemDAO.queryItem(itemId);
                itemCacheHander.addItemToCache(item);
            }
        }
    }
    return item;
}
  • 步骤小结:
  • ①高并发情况下,当第一次访问的时候先从缓存数据库中查询,如果为空
  • ②同步锁锁住
  • ③再次从缓存中查询是否为空(这里只有第一个到达的访问会继续进行下一步,因为缓存中没有)
  • ④第一个到达的访问会从再次判断为空,从数据库中查询,查询完结果放入缓存当中
  • ⑤从第二个当达的访问开始每一个都可以从缓存中查取
  • ⑥从而避免大量访问同时进入导致数据库崩掉的情况。

补充:synchronized 关键字,代表这个方法加锁,相当于不管哪一个线程(例如线程A),运行到这个方法时,都要检查有没有其它线程B(或者C、 D等)正在用这个方法(或者该类的其他同步方法),有的话要等正在使用synchronized方法的线程B(或者C 、D)运行完这个方法后再运行此线程A,没有的话,锁定调用者,然后直接运行。


三.缓存穿透

(1)概念
  • 缓存穿透,是指查询一个数据库不存在的数据。首先查询缓存,缓存中不存在则查询数据库,数据查询到的数据依然为空,设置到缓存中也为空;因此后续所有对次数据的查询都会先查询缓存,缓存不存在继而又查询数据库。

  • 案例:(想象一下这个情况,如果传入的参数为-1,会是怎么样?这个-1,就是一定不存在的对象。就会每次都去查询数据库,而每次查询都是空,每次又都不会进行缓存。假如有恶意攻击,就可以利用这个漏洞,对数据库造成压力,甚至压垮数据库。)

(2)如何解决
  • 即使数据库查询为空,也向缓冲中写入非空值

解决缓存穿透的思路就是:如果从数据库查询的对象为空,也放入缓存,只是设定的缓存过期时间较短,比如设置为60秒。

  • ItemCacheHandler(缓存帮助类)
/**
 * 存缓存
 * @param item
 */
public void addItemToCacheEx(Item item){
    String json = new Gson().toJson(item);
    //设置过期时间
    redisTemplate.boundValueOps("item‐"+item.getItemId()).set(json,5);
}
  • ItemService
//查询操作
public Item queryItemById(String itemId){
    Item item = itemCacheHander.getItemFromCache(itemId);
    if(item == null) {
        synchronized (this) {
            item = itemCacheHander.getItemFromCache(itemId);
            if (item == null) {
                System.out.println("‐‐‐‐‐‐‐‐‐‐‐‐‐‐查询数据库");
                item = itemDAO.queryItem(itemId);
                // 如果从数据库查询信息为null,则创建一个对象写入到缓冲,并设置过期时间
                if(item == null) {
                    item = new Item();
                    item.setItemId(itemId);
                    itemCacheHander.addItemToCacheEx(item);
                }else {
                    itemCacheHander.addItemToCache(item);
                }
            }
        }
    }
    return item;
}
  • 步骤小结
  • ①高并发大量访问进来,先从缓存中查取若为空
  • ②同步锁从缓存中再次查如为空
  • ③执行从数据库中查询
  • ④如果从数据库查询信息为null,则创建一个对象写入到缓冲,并设置过期时间
  • ⑤返回结果

四.缓存雪崩

(1)概念
  • 缓存雪崩,是指在某一个时间段,缓存集中过期失效,则大量的并发访问查询都落到了数据库上。
  • 案例:例如马上就要到双十一零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。
(2)如何解决
  • 1.一般是采取不同分类商品,缓存不同周期。在同一分类中的商品,加上一个随机因子。这样能尽可能分散缓存过期时间,而且,热门类目的商品缓存时间长一些,冷门类目的商品缓存时间短一些,也能节省缓存服务的资源

其实集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网。因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,那么那个时候数据库能顶住压力,这个时候,数据库也是可以顶住压力的。无非就是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。

  • 2.如果缓存数据库是分布式,可以将热点数据均匀随机分布在不同的缓存数据库中,对于热点数据可以设置不过期等.

Please enjoy the pain which is unable to avoid.
在这里插入图片描述
2020.03.06

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员橙子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值