缓存穿透问题

一. 缓存穿透 (请求数据缓存大量不命中):

缓存穿透是指查询一个一定不存在的数据,由于缓存不命中,并且出于容错考虑, 如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。

例如:下图是一个比较典型的cache-storage架构,cache(例如memcache, redis等等) + storage(例如mysql, hbase等等)架构,查一个压根就不存在的值, 如果不做兼容,永远会查询storage。

二. 危害:

对底层数据源(mysql, hbase, http接口, rpc调用等等)压力过大,有些底层数据源不具备高并发性。下载

例如mysql一般来说单台能够扛1000-QPS就已经很不错了(别说你的查询都是select * from table where id=xx 以及你的机器多么牛逼,那就有点矫情了)

例如他人提供的一个抗压性很差的http接口,可能穿透会击溃他的服务。

三. 如何发现:

我们可以分别记录cache命中数, storage命中数,以及总调用量,如果发现空命中(cache,storage都没有命中)较多,可能就会在缓存穿透问题。下载

注意:缓存本身的命中率(例如redis中的info提供了类似数字,只代表缓存本身)不代表storage和业务的命中率。

四. 产生原因以及业务是否允许?

产生原因有很多:可能是代码本身或者数据存在的问题造成的,也很有可能是一些恶意攻击、爬虫等等(因为http读接口都是开放的)

业务是否允许:这个要看做的项目或者业务是否允许这种情况发生,比如做一些非实时的推荐系统,假如新用户来了,确实没有他的推荐数据(推荐数据通常是根据历史行为算出),这种业务是会发生穿透现象的,至于业务允不允许要具体问题具体分析了。下载

五. 解决方法:

解决思路大致有两个,如下表。下面将分别说明

解决缓存穿透 适用场景 维护成本
缓存空对象
1. 数据命中不高

2. 数据频繁变化实时性高


1.代码维护简单

2.需要过多的缓存空间

3. 数据不一致

bloomfilter或者压缩filter提前拦截
1. 数据命中不高

2. 数据相对固定实时性低


1.代码维护复杂

2.缓存空间占用少

1. 缓存空对象下载

(1). 定义:如上图所示,当第②步MISS后,仍然将空对象保留到Cache中(可能是保留几分钟或者一段时间,具体问题具体分析),下次新的Request(同一个key)将会从Cache中获取到数据,保护了后端的Storage。

(2) 适用场景:数据命中不高,数据频繁变化实时性高(一些乱转业务)

(3) 维护成本:代码比较简单,但是有两个问题:

第一是空值做了缓存,意味着缓存系统中存了更多的key-value,也就是需要更多空间(有人说空值没多少,但是架不住多啊),解决方法是我们可以设置一个较短的过期时间。

第二是数据会有一段时间窗口的不一致,假如,Cache设置了5分钟过期,此时Storage确实有了这个数据的值,那此段时间就会出现数据不一致,解决方法是我们可以利用消息或者其他方式,清除掉Cache中的数据。

(4) 伪代码:

Java代码  下载

  1. package com.carlosfu.service;
  2. import org.apache.commons.lang.StringUtils;
  3. import com.carlosfu.cache.Cache;
  4. import com.carlosfu.storage.Storage;
  5. /**
  6. * 某服务
  7. *
  8. * @author carlosfu
  9. * @Date 2015-10-11
  10. * @Time 下午6:28:46
  11. */
  12. public class XXXService {
  13. /**
  14. * 缓存
  15. */
  16. private Cache cache = new Cache();
  17. /**
  18. * 存储
  19. */
  20. private Storage storage = new Storage();
  21. /**
  22. * 模拟正常模式
  23. * @param key
  24. * @return
  25. */
  26. public String getNormal(String key) {
  27. // 从缓存中获取数据
  28. String cacheValue = cache.get(key);
  29. // 缓存为空
  30. if (StringUtils.isBlank(cacheValue)) {
  31. // 从存储中获取
  32. String storageValue = storage.get(key);
  33. // 如果存储数据不为空,将存储的值设置到缓存
  34. if (StringUtils.isNotBlank(storageValue)) {
  35. cache.set(key, storageValue);
  36. }
  37. return storageValue;
  38. } else {
  39. // 缓存非空
  40. return cacheValue;
  41. }
  42. }
  43. /**
  44. * 模拟防穿透模式
  45. * @param key
  46. * @return
  47. */
  48. public String getPassThrough(String key) {
  49. // 从缓存中获取数据
  50. String cacheValue = cache.get(key);
  51. // 缓存为空
  52. if (StringUtils.isBlank(cacheValue)) {
  53. // 从存储中获取
  54. String storageValue = storage.get(key);
  55. cache.set(key, storageValue);
  56. // 如果存储数据为空,需要设置一个过期时间(300秒)
  57. if (StringUtils.isBlank(storageValue)) {
  58. cache.expire(key, 60 * 5);
  59. }
  60. return storageValue;
  61. } else {
  62. // 缓存非空
  63. return cacheValue;
  64. }
  65. }
  66. }

2. bloomfilter或者压缩filter(bitmap等等)提前拦截下载

(1). 定义:如上图所示,在访问所有资源(cache, storage)之前,将存在的key用布隆过滤器提前保存起来,做第一层拦截, 例如: 我们的推荐服务有4亿个用户uid, 我们会根据用户的历史行为进行推荐(非实时),所有的用户推荐数据放到hbase中,但是每天有许多新用户来到网站,这些用户在当天的访问就会穿透到hbase。为此我们每天4点对所有uid做一份布隆过滤器。如果布隆过滤器认为uid不存在,那么就不会访问hbase,在一定程度保护了hbase(减少30%左右)。下载

(2) 适用场景:数据命中不高,数据相对固定实时性低(通常是数据集较大)

(3) 维护成本:代码维护复杂, 缓存空间占用少

第一是空值做了缓存,意味着缓存系统中存了更多的key-value,也就是需要更多空间(有人说空值没多少,但是架不住多啊),解决方法是我们可以设置一个较短的过期时间。

第二是数据会有一段时间窗口的不一致,假如,Cache设置了5分钟过期,此时Storage确实有了这个数据的值,那此段时间就会出现数据不一致,解决方法是我们可以利用消息或者其他方式,清除掉Cache中的数据。

时间: 2024-10-05 04:23:54

缓存穿透问题的相关文章

缓存穿透和缓存失效

缓存穿透 是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个存在的数据每次请求都要到存储层去查询,失去了缓存的意义. 有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力(类似无结果cache).在数据魔方里,我们采用了一个更为简单粗暴的方法,如果一个查询返回的数据为空(

如何处理缓存失效、缓存穿透、缓存并发等问题

缓存失效: 引起这个原因的主要因素是高并发下,我们一般设定一个缓存的过期时间时,可能有一些会设置5分钟啊,10分钟这些:并发很高时可能会出在某一个时间同时生成了很多的缓存,并且过期时间在同一时刻,这个时候就可能引发——当过期时间到后,这些缓存同时失效,请求全部转发到DB,DB可能会压力过重. 处理方法: 一个简单方案就是将缓存失效时间分散开,不要所以缓存时间长度都设置成5分钟或者10分钟:比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降

缓存穿透与缓存雪崩

缓存穿透 什么是缓存穿透? 一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB).如果key对应的value是一定不存在的,并且对该key并发请求量很大,就会对后端系统造成很大的压力.这就叫做缓存穿透. 如何避免? 1:对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存. 2:对一定不存在的key进行过滤.可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤.[感觉

缓存穿透与缓存雪崩(转)

缓存穿透 什么是缓存穿透? 一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB).如果key对应的value是一定不存在的,并且对该key并发请求量很大,就会对后端系统造成很大的压力.这就叫做缓存穿透. 如何避免? 1:对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存. 2:对一定不存在的key进行过滤.可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤.[感觉

【学习】缓存穿透&缓存雪崩

昨天第一次听涛哥讲到缓存穿透与缓存雪崩  网上百度学习了下  Mark一下 [缓存穿透] 缓存穿透其实就是查询一个肯定不存在的数据,每次都会查询数据库,由于数据不存在,也不会写缓存. 这样其实是失去了缓存的意义了. 高并发的时候对数据库的压力就大了~~ 网上查到有几种解决方法: 1.布隆过滤器--这个算法有点复杂,具体应用就是,高效地检索一个元素是否在一个集合中,但是可能存在低概率的误差. 参见[http://www.dataguru.cn/thread-481958-1-1.html] 这里提

缓存穿透 缓存雪崩 缓存并发

参考连接:https://segmentfault.com/a/1190000005886009 缓存穿透:查询一个不存在的数据时,缓存和存储层都不会命中,由于存储层查不到数据则不写入缓存,所以每次查询都会到存储层查询从而缓存失去了其存在的意义. 如何避免: 对查询为空的情况也进行缓存,只不过设置一个较短的缓存时间. 把所有可能存在的key放到一个大的bitmap中,查询时通过该bitmap过滤. 缓存雪崩:发生缓存穿透时或者缓存失效后,Storage层的调用量暴增从而使Storage也挂掉.

Memcached之缓存雪崩,缓存穿透,缓存预热,缓存算法(7)

缓存雪崩 缓存雪崩可能是因为数据未加载到缓存中,或者缓存同一时间大面积的失效,从而导致所有请求都去查数据库,导致数据库CPU和内存负载过高,甚至宕机. 解决思路: 1,采用加锁计数,或者使用合理的队列数量来避免缓存失效时对数据库造成太大的压力.这种办法虽然能缓解数据库的压力,但是同时又降低了系统的吞吐量. 2,分析用户行为,尽量让失效时间点均匀分布.避免缓存雪崩的出现. 3,如果是因为某台缓存服务器宕机,可以考虑做主备,比如:redis主备,但是双缓存涉及到更新事务的问题,update可能读到脏

Memcached之缓存雪崩,缓存穿透,缓存预热,缓存算法

缓存雪崩 缓存雪崩可能是因为数据未加载到缓存中,或者缓存同一时间大面积的失效,从而导致所有请求都去查数据库,导致数据库CPU和内存负载过高,甚至宕机. 解决思路: 1,采用加锁计数,或者使用合理的队列数量来避免缓存失效时对数据库造成太大的压力.这种办法虽然能缓解数据库的压力,但是同时又降低了系统的吞吐量. 2,分析用户行为,尽量让失效时间点均匀分布.避免缓存雪崩的出现. 3,如果是因为某台缓存服务器宕机,可以考虑做主备,比如:Redis主备,但是双缓存涉及到更新事务的问题,update可能读到脏

缓存穿透、缓存并发、缓存失效问题以及解决方案

一.缓存穿透 问题描述:我们在项目中使用缓存通常都是APP先检查缓存中是否存在,如果存在直接返回缓存内容,如果不存在就直接查询数据库然后再缓存查询结果返回.这个时候如果我们查询的某一个数据在缓存中一直不存在,就会造成每一次请求都查询DB,这样缓存就失去了意义,在流量大时,可能DB就挂掉了. 解决方法: 方法1.在封装的缓存SET和GET部分增加个步骤,如果查询一个KEY不存在,就已这个KEY为前缀设定一个标识KEY:以后再查询该KEY的时候,先查询标识KEY,如果标识KEY存在,就返回一个协定好