一、缓存穿透

  缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透(恶意用户模拟请求很多缓存中不存在的数据)

解决办法:

最基本的就是做好参数校验,一些不合法的参数请求直接抛出异常信息返回给客户端。比如查询的数据库id不能小于0、传入的邮箱格式不对的时候直接返回错误消息给客户端等等。

1、简单办法:持久层查询不到就缓存空结果(这里的空值不能存null,用个cacheWrapper包装类,redis存不了null。实际应用中redis存byte[],这是为了安全性),查询时先判断缓存中是否exists(key) ,如果有直接返回空,没有则查询后返回。注意insert时需清除查询的key(注意操作顺序必须先改库提交事务后,再清缓存),否则即便DB中有值也查询不到(当然也可以设置空缓存的过期时间)。另外这个空结果缓存过期时间需要设置短点,防止redis中存入大量无效key,一般不超过5分钟。

2、布隆过滤器:

  由二进制向量(或者说位数组)和一系列随机映射函数(哈希函数)两部分组成的数据结构。相比于我们平时常用的的 List、Map 、Set 等数据结构(普通集合都是将数据完整存起来再比较),它占用空间更少并且效率更高,但是缺点是其返回的结果是概率性的,而不是非常准确的。理论情况下添加到集合中的元素越多,误报的可能性就越大。并且,存放在布隆过滤器的数据不容易删除。

    总结:一个名叫 Bloom 的人提出了一种来检索元素是否在给定大集合中的数据结构,这种数据结构是高效且性能很好的,但缺点是具有一定的错误识别率和删除难度。并且,理论情况下,添加到集合中的元素越多,误报的可能性就越大

原理介绍:当我们需要判断一个元素是否存在于布隆过滤器的时候,会进行如下操作:

1、对给定元素再次进行相同的哈希计算;
2、得到值之后判断位数组中的每个元素是否都为 1,如果值都为 1,那么说明这个值在布隆过滤器中,如果存在一个值不为 1,说明该元素不在布隆过滤器中。

 

 使用场景:

1、判断给定数据是否存在:比如判断一个数字是否在于包含大量数字的数字集中(数字集很大,5亿以上!)、 防止缓存穿透(判断请求的数据是否有效避免直接绕过缓存请求数据库)等等、邮箱的垃圾邮件过滤、黑名单功能等等。
2、去重:比如爬给定网址的时候对已经爬取过的 URL 去重

guava实现:

    private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), 100);
    public static void main(String[] args) {
        for(int i=0;i<100;i++) {
            bloomFilter.put(i);
        }
        System.out.println(bloomFilter.mightContain(99));
    }

打印:true

 二、缓存雪崩

雪崩:缓存大量失效的时候,引发大量查询数据库。

解决办法:

(1)用锁/分布式锁或者队列串行访问(使用类似于单例模式的双重检锁机制,而不是对整个判断都加锁,可以提高效率)

(2)缓存失效时间均匀分布

 三、热点key

热点key:某个key访问非常频繁,当key失效的时候有大量线程来构建缓存,导致负载增加,系统崩溃

解决办法:

  • (1)使用锁,单机用synchronized,lock等,分布式用分布式锁
  • (2)二级缓存,注意分布式环境下内存缓存的更新要通过zk同时刷新集群所有机器
  • (3)缓存过期时间不设置,而是设置在key对应的value里。如果检测到存的时间超过过期时间则异步更新缓存
  • (4)在value设置一个比过期时间t0小的过期时间值t1,当t1过期的时候,延长t1并做更新缓存操作