300字范文,内容丰富有趣,生活中的好帮手!
300字范文 > Redis专题-缓存穿透 缓存雪崩 缓存击穿

Redis专题-缓存穿透 缓存雪崩 缓存击穿

时间:2024-03-29 15:49:21

相关推荐

Redis专题-缓存穿透 缓存雪崩 缓存击穿

一.缓存穿透

缓存穿透概念

缓存穿透是指查询一个一定不存在的数据,在数据库没有,自然在缓存中也不会有。导致用户查询的时候,在缓存中找不到对应key的value,每次都要去数据库再查询一遍,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。

缓存穿透解决方案

有很多种方法可以有效地解决缓存穿透问题。

缓存空值

如果一个查询返回的数据为空(不管是数据不存在,还是系统故障)我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过5分钟。通过这个设置的默认值存放到缓存,这样第二次到缓存中获取就有值了,而不会继续访问数据库。

采用布隆过滤器BloomFilter

在缓存之前加一层BloomFilter,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。在查询的时候先去BloomFilter去查询key是否存在,如果不存在就直接返回,存在再去查询缓存,缓存中没有再去查询数据库。对BloomFilter有兴趣的可以看我的另一篇文章教你用BitMap排序、查找和存储大量数据。

二.缓存雪崩

缓存雪崩概念

缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。

缓存雪崩解决方案

设置不同的过期时间,让缓存失效的时间点尽量均匀

在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

考虑用队列或者锁让程序执行在压力范围之内,当然这种方案可能会影响并发量;

热点数据可以考虑不失效。

三.缓存击穿

缓存击穿概念

在平常高并发的系统中,大量的请求同时查询一个key,此时这个key正好失效了,就会导致大量的请求都打到数据库上面去,造成数据库请求量过大,压力剧增,这种现象我们称为缓存击穿。

缓存击穿解决方案

使用互斥锁(mutex key)

业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存,否则,就重试整个get缓存的方法。

SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果。

redis2.6.1之前版本未实现setnx的过期时间,这里给出两种版本代码参考:

//2.6.1之前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); } } }

//2.6.1之后String get(key) {String value = redis.get(key);if (value == null) {//代表缓存值过期//设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load dbif (redis.setnx(key_mutex, 1, 3 * 60) == 1) {//代表设置成功value = db.get(key);redis.set(key, value, expire_secs);redis.del(key_mutex);} else {//这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可sleep(50);get(key); //重试}} else {return value;}}

永远不过期

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

从redis上看,确实没有设置过期时间,这就保证了,不会出现热点key过期问题,也就是“物理”不过期;从功能上看,如果不过期,那不就成静态的了吗?所以我们把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建,也就是“逻辑”过期。

String get(final String key) {V v = redis.get(key); String value = v.getValue(); long timeout = v.getTimeout(); if (v.timeout <= System.currentTimeMillis()) {// 异步更新后台异常执行 threadPool.execute(new Runnable() {public void run() {String keyMutex = "mutex:" + key; if (redis.setnx(keyMutex, "1")) {// 3 min timeout to avoid mutex holder crash redis.expire(keyMutex, 3 * 60); String dbValue = db.get(key); redis.set(key, dbValue); redis.delete(keyMutex); } } }); } return value; }

从实战看,这种方法对于性能非常友好,唯一不足的就是构建缓存时候,其余线程(非构建缓存的线程)可能访问的是老数据,但是对于一般的互联网功能来说这个还是可以忍受。

猜你感兴趣

Redis专题-集群模式

Redis专题-持久化方式

Redis专题-底层数据结构与使用场景

Redis专题-缓存穿透、缓存雪崩、缓存击穿

更多文章请点击:更多…

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。