美文网首页redis Cache程序员
对缓存击穿的一点思考

对缓存击穿的一点思考

作者: 张丰哲 | 来源:发表于2017-10-28 20:33 被阅读2121次

    前言

    缓存(内存 or Memcached or Redis.....)在互联网项目中广泛应用,本篇博客将讨论下缓存击穿这一个话题,涵盖缓存击穿的现象、解决的思路、以及通过代码抽象方式来处理缓存击穿。

    什么是缓存击穿?

    缓存击穿

    上面的代码,是一个典型的写法:当查询的时候,先从Redis集群中取,如果没有,那么再从DB中查询并设置到Redis集群中。

    注意,在实际开发中,我们一般在缓存中,存储的数据结构是JSON。(JDK提供的序列化方式效率稍微比JSON序列化低一些;而且JDK序列化非常严格,字段的增减,就很可能导致反序列失败,而JSON这方面兼容性较好)

    假设从DB中查询需要2S,那么显然这段时间内过来的请求,在上述的代码下,会全部走DB查询,相当于缓存被直接穿透,这样的现象就称之为“缓存击穿”!

    避免缓存击穿的思路分析

    加synchronized?

    同步方式

    如果synchronized加在方法上,使得查询请求都得排队,本来我们的本意是让并发查询走缓存。也就是现在synchronized的粒度太大了。

    缩小synchronized的粒度?

    缩小粒度

    上面代码,在缓存有数据时,让查询缓存的请求不必排队,减小了同步的粒度。但是,仍然没有解决缓存击穿的问题。

    虽然,多个查询DB的请求进行排队,但是即便一个DB查询请求完成并设置到缓存中,其他查询DB的请求依然会继续查询DB!

    synchronized+双重检查机制

    双重检查

    通过synchronized+双重检查机制:

    在同步块中,继续判断检查,保证不存在,才去查DB。

    代码抽象

    发现没有,其实我们处理缓存的代码,除了具体的查询DB逻辑外,其他都是模板化的。下面我们就来抽象下!

    一个查询DB的接口:

    CacheLoader

    既然查询具体的DB是由业务来决定的,那么暴露这个接口让业务去实现它。

    一个模板:

    Template

    Spring不是有很多Template类么?我们也可以通过这种思想对代码进行一个抽象,让外界来决定具体的业务实现,而把模板步骤写好。(有点类似AOP的概念)

    改进后的代码:

    改进后的调用代码

    从这里可以看出,我们并不关心缓存的数据从哪里加载,而是交给具体的使用方,而且使用方在使用时再也不必关注缓存击穿的问题,因为我们都给抽象了。

    好了,到这里,关于缓存击穿就讨论到这里。

    2017.10.28 zhangfengzhe

    相关文章

      网友评论

      • yfsheng:向大佬学习
      • 联想桥南:把锁 this换成”studuntCache“.intern()昵
        张丰哲:嗯嗯,string的intern的作用是:每次取数据的时候,如果常量池中有,直接拿常量池中的数据;如果常量池中没有,将数据写入常量池中并返回常量池中的数据,可以达到锁的目的。😄
      • LBQY:如果DB中也不存在对应的查询项,比如系统被人家攻击了,这种情况的缓存击穿怎么防止呢?
        张丰哲:嗯嗯,这个问题是个好问题~
        或许可以这样改进,我们的查询始终是到缓存里面取,如果取不到,不要去db里面查了;存在另一个线程负责定时更新缓存(从db同步至缓存);这样即便db中不存在对应查询项,那么即便存在攻击,从缓存查询也很快,问题不大,实际应用中,对于攻击,很多时候在nginx这层就拦截住了,不会让攻击传导到查询缓存这个步骤了。
      • 月_297d:写的很好受教了,但是这个方法对分布式没有用的吧
      • c2a7818607bd:忘加了一个return
      • xiangtan:TypeReference<T> clazz 放在参数里面有点丑,不知道有没有手段去除?
        张丰哲:@xiangtan 嗯嗯,谢谢分享,封装下是好看多了,😄~
        xiangtan:我想了一种办法,你看看是否可行。把classloader 从接口转成抽象类。
        /**
        * Cache loader <br/>
        * Load data from basic record system,such as mysql , hbase ...
        *
        * @Author: yicai.liu
        */

        public abstract class CacheLoader<T> {

        public TypeReference<T> rawType() {
        return new TypeReference<T>() {
        };
        }

        /**
        * Load
        *
        * @Return data
        */
        public abstract T load();

        }
        在方法内获取:
        TypeReference<V> clazz = cacheLoader.rawType();
      • 易本正经的其名为坤:麻烦问一下 TypeReference 和 cacheloader是那个包里面的呢?
        易本正经的其名为坤:另外,似乎在集群情况下依旧会出现击穿的问题
      • 3777983077f9:这篇我竟然看懂了,写的很简单易懂。
      • bb31f50acc6b:深入浅出,持续关注!👍
        bb31f50acc6b:@antony_sun 线程的问题确实比较绕,需要多理解,而且解决并发的方式有很多,有些时候我们都可以自己定义锁,来控制并发.
        bb31f50acc6b:@antony_sun Synchronized修饰的方法或者代码块,其目的就是加锁,某个线程抢到锁后,其他线程则只能排队等待.谁获得锁,就只能有它在执行.
        antony_sun:请问, 用Synchronized修饰的都是在单线程环境执行吧? 有点糊涂了 单线程 多线程 线程池。。。
      • xiangtan:@张丰哲 我仔细想了一下,这个CacheTemplateService是单例,Synchronized(this)同步锁,是不是就对不同缓存相互影响。举个例子,获取key为A的值时,锁了。在未解锁前,是不是获取key为B的值也是同步阻塞的?
        不知道我有没有描述清楚,希望您回答一下。
        谢谢
        张丰哲:@xiangtan 恩恩,Guava Striped 实现细粒度锁,学习了,谢谢分享~
        xiangtan:@张丰哲 相同key是应该锁的。细粒度到key级别的解决方法我找了一下。可以用guava的Striped。
        Striped<Lock> striped = Striped.lazyWeakLock(1024);
        Lock lock = striped.get(key);
        try{
        lock.lock();
        //process business
        }finally{
        lock.unlock();
        }

        类似的文章:
        https://stackoverflow.com/questions/11124539/how-to-acquire-a-lock-by-a-key
        https://stackoverflow.com/questions/5639870/simple-java-name-based-locks/28723518#28723518
        张丰哲:恩,你说的情况是存在的。不仅仅是不同的KEY,相同的KEY也会出现阻塞现象。为了减少阻塞,使用了双重检查机制。
      • 不将就51y:好文,之前在其他地方看双重检查没看明白,居然在这里看明白了
      • xiangtan:写得很棒!给您赏一个
        张丰哲:谢谢啦~
      • 阿龙的学与思:学习了👍
        张丰哲:有空常来看看,:blush:
      • 一帅:越看越像guava的cache了:smile:
      • 2bb84e08d233:赞一个👍,看到cacheloader突然想起来guava好像也有一个
        张丰哲:恩恩,是的,guava的本地缓存很好用~
      • 那个码农:双重检查是不是少了个return。😂😂
        张丰哲:恩恩,火眼金睛,谢谢指出,:smile:
      • 轩辕宇虹:这篇文章写得真好,周五晚上去网易面试,二面,正好考察到这个问题,虽然换了种方式去问~不过,synchronized+双重检查机制,确实考察到了,由于我个人对缓存木有太多应用,现在看到这篇文章,豁然开朗😊😊

        张丰哲:@轩辕宇虹 恩恩,持续更新,:smile:
        轩辕宇虹: @张丰哲 你的文章很好,我基本都看完了,MQ系列,多分享一些噢🤗🤗
        张丰哲:常来看看~:smile:

      本文标题:对缓存击穿的一点思考

      本文链接:https://www.haomeiwen.com/subject/snwguxtx.html