缓存击穿问题

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,不支持删值操作

posted @ 2018-08-15 14:51  彩电  阅读(508)  评论(0编辑  收藏  举报