我们通常使用 缓存 + 过期时间的策略来帮助我们加速接口的访问速度,减少了后端负载,同时保证功能的更新。

1、缓存穿透

缓存系统,按照KEY去查询VALUE,当KEY对应的VALUE一定不存在的时候并对KEY并发请求量很大的时候,就会对后端造成很大的压力。

(查询一个必然不存在的数据。比如文章表,查询一个不存在的id,每次都会访问DB,如果有人恶意破坏,很可能直接对DB造成影响。)

由于缓存不命中,每次都要查询持久层。从而失去缓存的意义。

1.1 使用互斥锁排队

业界比价普遍的一种做法,即根据key获取value值为空时,锁上,从数据库中load数据后再释放锁。若其它线程获取锁失败,则等待一段时间后重试。这里要注意,分布式环境中要使用分布式锁,单机的话用普通的锁(synchronized、Lock)就够了。

public String getWithLock(String key, Jedis jedis, String lockKey,String uniqueId, long expireTime) {// 通过key获取valueString value = redisService.get(key);if (StringUtil.isEmpty(value)) {// 分布式锁,详细可以参考https://blog.csdn.net/fanrenxiang/article/details/79803037// 封装的tryDistributedLock包括setnx和expire两个功能,在低版本的redis中不支持try {boolean locked = redisService.tryDistributedLock(jedis,lockKey, uniqueId, expireTime);if (locked) {value = userService.getById(key);redisService.set(key, value);redisService.del(lockKey);return value;} else {// 其它线程进来了没获取到锁便等待50ms后重试Thread.sleep(50);getWithLock(key, jedis, lockKey, uniqueId, expireTime);}} catch (Exception e) {log.error("getWithLock exception=" + e);return value;} finally {redisService.releaseDistributedLock(jedis, lockKey, uniqueId);}}return value;}

这样做思路比较清晰,也从一定程度上减轻数据库压力,但是锁机制使得逻辑的复杂度增加,吞吐量也降低了,有点治标不治本。

1.2 布隆过滤器

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

<dependencies> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>23.0</version> </dependency>
</dependencies> public class BloomFilterTest {private static final int capacity = 1000000;private static final int key = 999998;private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), capacity);static {for (int i = 0; i < capacity; i++) {bloomFilter.put(i);}}public static void main(String[] args) {/* 返回计算机最精确的时间,单位微妙 */long start = System.nanoTime();if (bloomFilter.mightContain(key)) {System.out.println("成功过滤到" + key);}long end = System.nanoTime();System.out.println("布隆过滤器消耗时间:" + (end - start));int sum = 0;for (int i = capacity + 20000; i < capacity + 30000; i++) {if (bloomFilter.mightContain(i)) {sum = sum + 1;}}System.out.println("错判率为:" + sum);}}成功过滤到999998
布隆过滤器消耗时间:215518
错判率为:318
复

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

2、缓存雪崩问题

缓存在同一时间内大量键过期(失效),接着来的一大波请求瞬间都落在了数据库中导致连接异常。

解决方案:

  1. 在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
  2. 可以通过缓存reload机制,预先去更新缓存,再即将发生大并发访问前手动触发加载缓存
  3. 不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀
  4. 做二级缓存,或者双缓存策略。A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期。
public String getByKey(String keyA, String keyB) {String value = redisService.get(keyA);if (StringUtil.isEmpty(value)) {value = redisService.get(keyB);String newValue = getFromDbById();redisService.set(keyA, newValue, 31, TimeUnit.DAYS);redisService.set(keyB, newValue);}return value;}

3、热点key

(1) 这个key是一个热点key(例如一个重要的新闻,一个热门的八卦新闻等等),所以这种key访问量可能非常大。

(2) 缓存的构建是需要一定时间的。(可能是一个复杂计算,例如复杂的sql、多次IO、多个依赖(各种接口)等等)

于是就会出现一个致命问题:在缓存失效的瞬间,有大量线程来构建缓存(见下图),造成后端负载加大,甚至可能会让系统崩溃 。

解决方法:
  1. 使用互斥锁(mutex key):这种解决方案思路比较简单,就是只让一个线程构建缓存,其他线程等待构建缓存的线程执行完,重新从缓存获取数据就可以了

  2. "提前"使用互斥锁(mutex key):在value内部设置1个超时值(timeout1), timeout1比实际的memcache timeout(timeout2)小。当从cache读取到timeout1发现它已经过期时候,马上延长timeout1并重新设置到cache。然后再从数据库加载数据并设置到cache中。

  3. “永远不过期”:

这里的“永远不过期”包含两层意思:

(1) 从redis上看,确实没有设置过期时间,这就保证了,不会出现热点key过期问题,也就是“物理”不过期。(2) 从功能上看,如果不过期,那不就成静态的了吗?所以我们把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建,也就是“逻辑”过期
  1. 资源保护:可以做资源的隔离保护主线程池,如果把这个应用到缓存的构建也未尝不可。

4、缓存和数据库间数据一致性问题

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

5、拓展

http://www.mobabel.net/总结redis热点key发现及常见解决方案/

Redis缓存雪崩、缓存穿透、热点Key相关推荐

  1. Redis缓存雪崩缓存击穿缓存穿透

    Redis缓存雪崩&缓存击穿&缓存穿透 一 缓存更新策略 二 缓存雪崩 三 缓存击穿 四 缓存穿透 一 缓存更新策略 目前redis缓存更新存在3种主流策略,分别是:内存淘汰.超时剔除 ...

  2. Redis追命连环问,你能回答到第几问?(上)Redis简介,数据类型及缓存雪崩缓存击穿缓存穿透

    Redis常见面试题连环问,你能回答到第几问?(上) Redis常见面试题连环问,你能回答到第几问?(中) Redis常见面试题连环问,你能回答到第几问?(下) Redis是后端工程师必备的一项技能, ...

  3. 什么是redis缓存穿透, 缓存雪崩, 缓存击穿

    什么是redis? redis是一个非关系型数据库,相对于其他数据库而言,它的查询速度极快,且能承受的瞬时并发量非常的高.所以常常被用来存放网站的缓存,以减少主要数据库(如mysql)的服务器压力. ...

  4. 顶级“Redis学习笔记”,缓存雪崩+击穿+穿透+集群+分布式锁,NB了

    如果你是一位后端工程师,面试时八成会被问到 Redis,特别是那些大型互联网公司,不仅要求面试者能简单使用 Redis,还要深入理解其底层实现原理,具备解决常见问题的能力.可以说,熟练使用 Redis ...

  5. redis缓存雪崩、穿透、击穿概念、布隆过滤器小结及解决办法

    判存业务 redis缓存雪崩.穿透.击穿概念及解决办法 什么是 概念: 1.缓存雪崩 对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意 ...

  6. 小白也能看懂的缓存雪崩、穿透、击穿

    作为后端开发,我想缓存是大家再熟悉不过的东西了. 我会介绍出现缓存雪崩.穿透和击穿的业务背景.解决方案和对业务可靠性处理.事先说明,最佳解决方案一定需要结合实际业务调整,不同业务的处理不完全相同 其实 ...

  7. 缓存穿透,缓存雪崩,缓存击穿,缓存一致性解决方案分析

    首先我们通过明确其中的概念,再来分析解决方案,这里的缓存我们以redis为例,当然其他的缓存技术,出现的问题以及解决思路大致是一样的 缓存穿透:是指查询一个一定不存在的数据,通常我们都会先查缓存,缓存 ...

  8. 第十八章_Redis缓存预热+缓存雪崩+缓存击穿+缓存穿透

    缓存预热 缓存预热就是系统启动前,提前将相关的缓存数据直接加载到缓存系统.避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据. 可以通过@PostConstr ...

  9. Redis-- 缓存预热+缓存雪崩+缓存击穿+缓存穿透

    Redis-- 缓存预热+缓存雪崩+缓存击穿+缓存穿透**加粗样式** 一 面试题引入 二 缓存预热 三 缓存雪崩 3.1 问题现象 3.2 预防+解决 四 缓存穿透 4.1 定义 4.2 解决方案 ...

  10. 十三、缓存雪崩+缓存击穿+缓存穿透

    十三.缓存雪崩+缓存击穿+缓存穿透 十三.缓存雪崩+缓存击穿+缓存穿透 十三.缓存雪崩+缓存击穿+缓存穿透 1.缓存雪崩 1.什么情况会发生雪崩 2.雪崩解决方案 2.缓存穿透 1.是什么 2.解决方 ...

最新文章

  1. ajax提交不能进入后台_Ajax跨域问题
  2. Zero Quantity Maximization
  3. unity自发光透明shader
  4. 使MySQL 支持繁体字
  5. python基础笔记_python基础学习笔记
  6. Bootstrap3 插件的版本号
  7. Perl_获得字符串长度_length($var)
  8. Mat 创建图像的理解
  9. Android基于高德地图poi的仿微信获取位置
  10. 网易云音乐android变臃肿,网易云音乐,你变成了我最讨厌的模样
  11. robo3t 1.3.1 安装教程
  12. python降序排序_python中如何降序排列
  13. 群晖服务器共享文件忘记密码,群晖synology NAS ds 1815+忘记google authenticator二次验证密码...
  14. 模拟手柄控制器点击没有反应的问题
  15. Python第三方库turtle画小人发射爱心
  16. BZOJ1066 蜥蜴
  17. 基于Html5 的canvas容器实现定制印章(圆形、椭圆、方形)
  18. 企立方-拼多多采集注意的点有哪些
  19. HTML5画布行星图像映射
  20. JSON 对象(object)

热门文章

  1. css 字体加粗_HTML基础属性与CSS基础
  2. 需要多快的速度,才能在抽走桌布之后保持桌面物体不掉?
  3. 从时速100公里行驶的车上向后发射时速100公里的棒球,会发生什么?
  4. 90后一代人还能通过攒钱改变现状吗?
  5. 年底求职难?起薪28万的数据岗位,人才缺口达150万,不限专业学历……
  6. 震惊整个世界的新发现,科学界的大骗局
  7. java输入数据插入if_java编程,从键盘录入10个整数数据,将每次录入的数据按从小到大的顺序插入到数组中。...
  8. 因为我把JMM原理讲解了一遍,这给足了我涨薪的底气!
  9. phpfind mysql怎么用_MySQL 的 find_in_set 函数使用方法
  10. vue html引入图片,vue引入图片的几种方式