• 缓存击穿问题


    1、缓存击穿问题及其原因

       背景:用户向后端查询数据时先查询缓存是否存在,如果存在直接获取,如果不存在就去找数据库,然而数据库的查找是慢的,多查询时性能不佳。

       缓存击穿原因:黑客向后端发送大量缓存中不存在的数据,导致后端查询缓存不到,转而去查询数据库,大量查询堆积在数据库,数据库可能会挂掉。

    2、解决方案:

      1、使用互斥锁

              该方法是比较普遍的做法,即,在根据key获得的value值为空时,先锁上,再从数据库加载,加载完毕,释放锁。若其他线程发现获取锁失败,则睡眠50ms后重试。

        至于锁的类型,单机环境用并发包的Lock类型就行,集群环境则使用分布式锁( redis的setnx)集群环境的redis的代码如下所示:

    String get(String key) {  
        String value = redis.get(key);  
        if (value  == null) {  
         if (redis.setnx(key_mutex, "1")) {  
             // 3 min timeout to avoid mutex holder crash  
             redis.expire(key_mutex, 3 * 60)  
             value = db.get(key);  
             redis.set(key, value);  
             redis.delete(key_mutex);  
        } else {  
            //其他线程休息50毫秒后重试  
            Thread.sleep(50);  
            get(key);  
        }  
      }  
    }  

        分布式环境下,使用setnx分布式锁(redis下的)key_mutex加锁,让redis向数据库请求值,其他的线程获取不到互斥锁,所以只能先休息50毫秒在来试,缓解了数据库的负担。

        优点:思路简单,能一直保证缓存一致性

        缺点:代码复杂,有可能存在死锁风险:如果另外的线程获取的资源正好是当前线程锁定的资源,同时当前线程即将要去访问另外线程正锁定的内容,就出现了死锁。

    2、布隆过滤器:

      布隆过滤器可以迅速从庞大的集合中查找到是否含有某个元素。

        <dependencies>
            <dependency>
                <groupId>com.google.guava</groupId>
                <artifactId>guava</artifactId>
                <version>22.0</version>
            </dependency>
        </dependencies
    import com.google.common.hash.BloomFilter;
    import com.google.common.hash.Funnels;
    
    public class Test {
        private static int size = 1000000;
    
        private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size);
    
        public static void main(String[] args) {
            for (int i = 0; i < size; i++) {
                bloomFilter.put(i);
            }
            long startTime = System.nanoTime(); // 获取开始时间
    
            //判断这一百万个数中是否包含29999这个数
            for (int i = 10000; i <= 50000; i *= 2) {
                if (bloomFilter.mightContain(i)) {
                    System.out.println("命中了");
                }
            }
            long endTime = System.nanoTime();   // 获取结束时间
    
            System.out.println("程序运行时间: " + (endTime - startTime) + "纳秒");
    
        }
    }

    其误判率越低,所占数组长度就越长。

    实例化时在构造函数内的第三个参数添加误判率,必须大于0.0,默认为0.3;

    实际在缓存击穿上的应用:  

     String get(String key) {  
        String value = redis.get(key);  
        if (value  == null) {  
             if(!bloomfilter.mightContain(key)){
                 return null;
             }else{
                value = db.get(key);  
                redis.set(key, value);  
             }
        } 
        return value;
    } 

     优点:思路简单,能保证缓存一致性,性能强;

    缺点:代码复杂,需要另外维护一个集合来存放缓存的key,不支持删值操作

  • 相关阅读:
    AVR汇编初探之二《AVR的指令与汇编系统》
    Fedora 18 装完后干的事
    重建tempdb
    sqlserver 2008 r2 SqlClrProvider报错解析
    Analysis Memory In Sqlserver
    《设计模式之禅》学习笔记(七)
    容易弄错的繁体字
    《C Primer Plus》学习笔记
    什么是 Windows 能干而 Linux 干不了的事情
    MySQL中BLOB字段类型介绍
  • 原文地址:https://www.cnblogs.com/television/p/9481384.html
Copyright © 2020-2023  润新知