2020-09-16 Redis缓存穿透、缓存雪崩问题分析

把redis作为缓存使用已经是司空见惯,当redis中的数据量起来了以后你就得考虑以下几个问题:

(一)缓存和数据库间数据一致性问题

分布式环境下(单机就不用说了)非常容易出现缓存和数据库间的数据一致性问题,针对这一点的话,只能说,如果你的项目对缓存的要求是强一致性的,那么请不要使用缓存。我们只能采取合适的策略来降低缓存和数据库间数据不一致的概率,而无法保证两者间的强一致性。合适的策略包括 合适的缓存更新策略,更新数据库后要及时更新缓存、缓存失败时增加重试机制,例如MQ模式的消息队列。


()缓存穿透问题

现象:用户大量并发请求的数据(key)对应的数据在redis和数据库中都不存在,导致尽管数据不存在但还是每次都会进行查DB。

为什么key对应数据在缓存和db中不存在还会每次都进行DB查询呢?因为很多开发同学写的代码写的逻辑都是先从redis缓存中查一把,如果缓存中为空则从DB中查,如果DB中查到的数据不为空则设置到缓存并返回给接口。那么问题来了,如果从DB中查询的数据为空呢??

解决方案

  • 从DB中查询出来数据为空,也进行空数据的缓存,避免DB数据为空也每次都进行数据库查询;
  • 使用布隆过滤器,但是会增加一定的复杂度及存在一定的误判率;

bloomfilter就类似于一个hash set,用于快速判某个元素是否存在于集合中,其典型的应用场景就是快速判断一个key是否存在于某容器,不存在就直接返回。布隆过滤器的关键就在于hash算法和容器大小,下面先来简单的实现下看看效果,我这里用guava实现的布隆过滤器:


 
 
  1. <dependencies>
  2. <dependency>
  3. <groupId>com.google.guava</groupId>
  4. <artifactId>guava</artifactId>
  5. <version> 23.0</version>
  6. </dependency>
  7. </dependencies>

 
 
  1. public class BloomFilterTest {
  2. private static final int capacity = 1000000;
  3. private static final int key = 999998;
  4. private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), capacity);
  5. static {
  6. for ( int i = 0; i < capacity; i++) {
  7. bloomFilter.put(i);
  8. }
  9. }
  10. public static void main(String[] args) {
  11. /*返回计算机最精确的时间,单位微妙*/
  12. long start = System.nanoTime();
  13. if (bloomFilter.mightContain(key)) {
  14. System.out.println( "成功过滤到" + key);
  15. }
  16. long end = System.nanoTime();
  17. System.out.println( "布隆过滤器消耗时间:" + (end - start));
  18. int sum = 0;
  19. for ( int i = capacity + 20000; i < capacity + 30000; i++) {
  20. if (bloomFilter.mightContain(i)) {
  21. sum = sum + 1;
  22. }
  23. }
  24. System.out.println( "错判率为:" + sum);
  25. }
  26. }

 
 
  1. 成功过滤到 999998
  2. 布隆过滤器消耗时间: 215518
  3. 错判率为: 318

可以看到,100w个数据中只消耗了约0.2毫秒就匹配到了key,速度足够快。然后模拟了1w个不存在于布隆过滤器中的key,匹配错误率为318/10000,也就是说,出错率大概为3%,跟踪下BloomFilter的源码发现默认的容错率就是0.03:


 
 
  1. public static <T> BloomFilter<T> create(Funnel<T> funnel, int expectedInsertions /* n */) {
  2. return create(funnel, expectedInsertions, 0.03); // FYI, for 3%, we always get 5 hash functions
  3. }

我们可调用BloomFilter的这个方法显式的指定误判率:

private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), capacity,0.01);
 
 

我们断点跟踪下,误判率为0.02和默认的0.03时候的区别:

对比两个出错率可以发现,误判率为0.02时数组大小为8142363,0.03时为7298440,误判率降低了0.01,BloomFilter维护的数组大小也减少了843923,可见BloomFilter默认的误判率0.03是设计者权衡系统性能后得出的值。要注意的是,布隆过滤器不支持删除操作。用在这边解决缓存穿透问题就是:


 
 
  1. public String getByKey(String key) {
  2. // 通过key获取value
  3. String value = redisService.get(key);
  4. if (StringUtil.isEmpty(value)) {
  5. if (bloomFilter.mightContain(key)) {
  6. value = userService.getById(key);
  7. redisService.set(key, value);
  8. return value;
  9. } else {
  10. return null;
  11. }
  12. }
  13. return value;
  14. }

(三)缓存雪崩问题

现象:大量key同一时间点失效,同时又有大量请求打进来,导致流量直接打在DB上,造成DB不可用。

解决方案

  • 设置key永不失效(热点数据);
  • 设置key缓存失效时候尽可能错开;
  • 使用多级缓存机制,比如同时使用redsi和memcache缓存,请求->redis->memcache->db;
  • 购买第三方可靠性高的Redis云服务器;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值