- 缓存穿透
- BloomFilter
- 缓存雪崩
- 缓存击穿
- 布隆过滤器:某样东西一定不存在或者可能存在
1. 缓存穿透 / 雪崩 / 击穿
从事态严重性来讲:穿透 > 雪崩 > 击穿
- 缓存穿透:数据库中数据不存在,缓存也不存在。每次都大量请求数据库,打穿数据库
- 解决方法
- 1. 缓存预热:缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统,避免用户请求的时候,再去加载相关的数据
- 2. 布隆过滤器:见下
- 解决方法
- 缓存雪崩:原有缓存失效(过期),新缓存未到期间;或者redis挂了。大量请求打向数据库
- 解决方法
- 1. 关键点就在于让key错开时间失效:设置key过期时间的时候加上一个随机数
- 2. 给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新缓存
- 缓存标记:记录缓存数据是否过期,如果过期会触发通知另外的线程在后台去更新实际key的缓存
- 缓存数据:它的过期时间比缓存标记的时间延长1倍,例:标记缓存时间30分钟,数据缓存设置为60分钟。 这样,当缓存标记key过期后,实际缓存还能把旧数据返回给调用端,直到另外的线程在后台更新完成后,才会返回新缓存
- 解决方法
- 缓存击穿:缓存中某个热点key扛着大量的并发请求,一旦它过期,失效瞬间,大量请求打向数据库
- 解决方法
- 1. 延长过期时间,或者永不过期(可以选择与缓存雪崩,解决方法2,添加缓存标记,使得看上去永不过期)
- 解决方法
- 宕机:会引起缓存雪崩
- 解决方法:集群搭建保证高可用
2. 布隆过滤器 BloomFilter
作用:一种巧妙的概率型数据结构,告诉你某样东西一定不存在或者可能存在
- 思路:加在redis的上游,访问redis前先用布隆过滤器,过滤大部分不存在key的请求访问
- 原理:设置三个hash函数{x,y,z},设置一个长度为18的bit array[] 用于hash映射,假设有两个key:str1, str2,
- str1经过x,y,z三个hash函数映射到了2,13,16;str2经过x,y,z三个hash函数映射到了2,5,11
- 每个key访问布隆的时候先看看自己映射的三个index是否都为1
- eg. str1访问布隆,映射到[2,13,16],发现都为1,说明存在——>放行,访问redis
- eg. 假设str3访问布隆,映射到[2,3,4],发现3,4为0,说明不存在——>拦截,减少对db压力
- 可能会误判:
- 传统模式下:不会对布隆过滤器作key删除操作(例如上图中的str1和str2都被映射到了index=2,如果删除str1,则str2也不能访问了)
- 但是名为 Counting Bloom filter 的变种可以用来测试元素计数个数是否绝对小于某个阈值,它支持元素删除
- 工程上为了降低冲突,会设置inverse bloom-filter空间比较大,并多次hash防干扰
- 传统模式下:不会对布隆过滤器作key删除操作(例如上图中的str1和str2都被映射到了index=2,如果删除str1,则str2也不能访问了)
- 实际使用:guava提供的类库
- 自己实现:
-
import java.util.Arrays; import java.util.BitSet; import java.util.concurrent.atomic.AtomicBoolean; public class MyBloomFilter { //你的布隆过滤器容量 private static final int DEFAULT_SIZE = 2 << 28; //bit数组,用来存放结果 private static BitSet bitSet = new BitSet(DEFAULT_SIZE); //后面hash函数会用到,用来生成不同的hash值,可随意设置,别问我为什么这么多8,图个吉利 private static final int[] hashInts = {1, 6, 16, 38, 58, 68}; //add方法,计算出key的hash值,并将对应下标置为true public void add(Object key) { Arrays.stream(hashInts ).forEach(i -> bitSet.set(hash(key, i))); } //判断key是否存在,true不一定说明key存在,但是false一定说明不存在 public boolean isContain(Object key) { boolean result = true; for (int i : hashInts) { //短路与,只要有一个bit位为false,则返回false result = result && bitSet.get(hash(key, i)); } return result; } //hash函数,借鉴了hashmap的扰动算法,强烈建议大家把这个hash算法看懂,这个设计真的牛皮加闪电 private int hash(Object key, int i) { int h; return key == null ? 0 : (i * (DEFAULT_SIZE - 1) & ((h = key.hashCode()) ^ (h >>> 16))); } }
-
Ref:
改进:布谷鸟过滤器:https://zhuanlan.zhihu.com/p/68418134
https://blog.csdn.net/qq_33709582/article/details/108407706
https://www.zhihu.com/search?type=content&q=%E5%B8%83%E9%9A%86%E8%BF%87%E6%BB%A4