zoukankan      html  css  js  c++  java
  • 缓存击穿的解决方案

    一.什么样的数据适合缓存?


    这里写图片描述

    二.什么是缓存击穿


    这里写图片描述

    三.缓存击穿的解决办法

    方案一

       后台定义一个job(定时任务)专门主动更新缓存数据.比如,一个缓存中的数据过期时间是30分钟,那么job每隔29分钟定时刷新数据(将从数据库中查到的数据更新到缓存中).

    • 这种方案比较容易理解,但会增加系统复杂度。比较适合那些 key 相对固定,cache 粒度较大的业务,key 比较分散的则不太适合,实现起来也比较复杂。

    方案二

         将缓存key的过期时间(绝对时间)一起保存到缓存中(可以拼接,可以添加新字段,可以采用单独的key保存..不管用什么方式,只要两者建立好关联关系就行).在每次执行get操作后,都将get出来的缓存过期时间与当前系统时间做一个对比,如果缓存过期时间-当前系统时间<=1分钟(自定义的一个值),则主动更新缓存.这样就能保证缓存中的数据始终是最新的(和方案一一样,让数据不过期.)

    • 这种方案在特殊情况下也会有问题。假设缓存过期时间是12:00,而 11:59 
      到 12:00这 1 分钟时间里恰好没有 get 请求过来,又恰好请求都在 11:30 分的时 
      候高并发过来,那就悲剧了。这种情况比较极端,但并不是没有可能。因为“高 
      并发”也可能是阶段性在某个时间点爆发。

    方案三

       采用 L1 (一级缓存)和 L2(二级缓存) 缓存方式,L1 缓存失效时间短,L2 缓存失效时间长。 请求优先从 L1 缓存获取数据,如果 L1缓存未命中则加锁,只有 1 个线程获取到锁,这个线程再从数据库中读取数据并将数据再更新到到 L1 缓存和 L2 缓存中,而其他线程依旧从 L2 缓存获取数据并返回。

    • 这种方式,主要是通过避免缓存同时失效并结合锁机制实现。所以,当数据更 
      新时,只能淘汰 L1 缓存,不能同时将 L1 和 L2 中的缓存同时淘汰。L2 缓存中 
      可能会存在脏数据,需要业务能够容忍这种短时间的不一致。而且,这种方案 
      可能会造成额外的缓存空间浪费。

    方案四

     加锁

    方法1

    1

    1. // 方法1:
    2.  
      public synchronized List<String> getData01() {
    3.  
      List<String> result = new ArrayList<String>();
    4.  
      // 从缓存读取数据
    5.  
      result = getDataFromCache();
    6.  
      if (result.isEmpty()) {
    7.  
      // 从数据库查询数据
    8.  
      result = getDataFromDB();
    9.  
      // 将查询到的数据写入缓存
    10.  
      setDataToCache(result);
    11.  
      }
    12.  
      return result;
    13.  
      }
    • 这种方式确实能够防止缓存失效时高并发到数据库,但是缓存没有失效的时候,在从缓存中拿数据时需要排队取锁,这必然会大大的降低了系统的吞吐量.
    方法2
      1. static Object lock = new Object();
      2.  
         
      3.  
        public List<String> getData02() {
      4.  
        List<String> result = new ArrayList<String>();
      5.  
        // 从缓存读取数据
      6.  
        result = getDataFromCache();
      7.  
        if (result.isEmpty()) {
      8.  
        synchronized (lock) {
      9.  
        // 从数据库查询数据
      10.  
        result = getDataFromDB();
      11.  
        // 将查询到的数据写入缓存
      12.  
        setDataToCache(result);
      13.  
        }
      14.  
        }
      15.  
        return result;
      16.  
        }
    • 这个方法在缓存命中的时候,系统的吞吐量不会受影响,但是当缓存失效时,请求还是会打到数据库,只不过不是高并发而是阻塞而已.但是,这样会造成用户体验不佳,并且还给数据库带来额外压力.
    方法3
    1. public List<String> getData03() {
    2.  
      List<String> result = new ArrayList<String>();
    3.  
      // 从缓存读取数据
    4.  
      result = getDataFromCache();
    5.  
      if (result.isEmpty()) {
    6.  
      synchronized (lock) {
    7.  
      //双重判断,第二个以及之后的请求不必去找数据库,直接命中缓存
    8.  
      // 查询缓存
    9.  
      result = getDataFromCache();
    10.  
      if (result.isEmpty()) {
    11.  
      // 从数据库查询数据
    12.  
      result = getDataFromDB();
    13.  
      // 将查询到的数据写入缓存
    14.  
      setDataToCache(result);
    15.  
      }
    16.  
      }
    17.  
      }
    18.  
      return result;
    19.  
      }
    • 1
    方法4
      1. static Lock reenLock = new ReentrantLock();
      2.  
         
      3.  
        public List<String> getData04() throws InterruptedException {
      4.  
        List<String> result = new ArrayList<String>();
      5.  
        // 从缓存读取数据
      6.  
        result = getDataFromCache();
      7.  
        if (result.isEmpty()) {
      8.  
        if (reenLock.tryLock()) {
      9.  
        try {
      10.  
        System.out.println("我拿到锁了,从DB获取数据库后写入缓存");
      11.  
        // 从数据库查询数据
      12.  
        result = getDataFromDB();
      13.  
        // 将查询到的数据写入缓存
      14.  
        setDataToCache(result);
      15.  
        } finally {
      16.  
        reenLock.unlock();// 释放锁
      17.  
        }
      18.  
         
      19.  
        } else {
      20.  
        result = getDataFromCache();// 先查一下缓存
      21.  
        if (result.isEmpty()) {
      22.  
        System.out.println("我没拿到锁,缓存也没数据,先小憩一下");
      23.  
        Thread.sleep(100);// 小憩一会儿
      24.  
        return getData04();// 重试
      25.  
        }
      26.  
        }
      27.  
        }
      28.  
        return result;
      29.  
        }
    • 最后使用互斥锁的方式来实现,可以有效避免前面几种问题.
  • 相关阅读:
    数据结构-王道2017-绪论-1.2 算法和算法评价
    数据结构-王道2017-绪论-1.1 数据结构的基本概念
    挑战程序设计竞赛-2.1最基础的“穷竭搜索”-宽度优先搜索-迷宫的最短路径
    挑战程序设计竞赛-2.1最基础的“穷竭搜索”-Lake Counting
    挑战程序设计竞赛-1.6节-三角形
    C++注意事项
    Software Project Management -- HW1
    Software Testing -- HW2
    Software Testing -- HW1
    c语言与c++基础知识
  • 原文地址:https://www.cnblogs.com/jianmingyuan/p/10728206.html
Copyright © 2011-2022 走看看