缓存相关问题及解决方案
缓存雪崩
问题: 指设置缓存时大面积的数据采用相同的过期时间,导致这一面积的缓存在某一时刻集体失效,所有请求落在数据库上,数据库压力过大。
解决方案: 在设置过期时间时,在一定的范围内使用随机值,尽可能降低过期时间的重复率。
缓存击穿
问题: 对于某个可能被超高并发访问的热点数据,在某个时刻突然失效,那对于这个数据的查询都落在数据库上,数据库压力过大。
解决方案:
- 对于热点数据的过期时间够长。
- 使用互斥锁,并发时只让一个请求去查询数据库,并保存在缓存里,其它请求再从缓存中查询。
缓存穿透
问题: 指查询一个一定不存在的数据,缓存未命中,去查询数据库,但是数据库也没有该记录,返回为 null,不写入缓存里。之后每次查询这个不存在的数据都要穿透到数据库查询。高并发下数据库压力过大。
解决方案:
- 将该不存在的数据返回的 null 结果缓存,加上短暂的过期时间。
- 布隆过滤器。
特点:
- 元素判断为存在的时候,其实不一定存在;
- 元素判断为不存在的时候,一定不存在。
缺点:以上特性导致,想要从布隆过滤器中删除一个元素的时候,可能会误删元素。
优点:在空间和时间上的优势都很明显
Java 实现(随意版)
package com.lvshui5u.datastructure.redis;
import java.util.BitSet;
/**
* @author: lvshui5u
* @date: 2021/8/20 11:24
* @describe:
*/
public class BloomFilter {
/**
* 默认大小为 2^30,这个大小应该是有讲究的,但是我讲究不明白
*/
private static final int DEFAULT_SIZE = 1 << 30;
/**
* 布隆过滤器主体
*/
private static final BitSet SET = new BitSet(DEFAULT_SIZE);
/**
* 算法种子,六个种子代表六个 hash 算法
*/
private static final int[] SEEDS = {3,5,7,11,13,31};
/**
* 六个种子代表六个 hash 算法
*/
private static final HashFunction[] FUNCTIONS = new HashFunction[SEEDS.length];
/**
* hash 方法初始化
*/
static {
for (int i = 0; i < SEEDS.length; i++) {
FUNCTIONS[i] = new HashFunction(DEFAULT_SIZE, SEEDS[i]);
}
}
/**
* 将某个值加入到 BloomFilter 里,其实添加了 6 个位置
* @param value
*/
public void add(String value){
if(value != null){
for (HashFunction function : FUNCTIONS) {
int hash = function.hash(value);
SET.set(hash);
}
}
}
/**
* 是否包含
* @param value
* @return
*/
public boolean contains(String value){
boolean res = true;
if(value != null){
for (HashFunction function : FUNCTIONS){
int hash = function.hash(value);
boolean b = SET.get(hash);
// 有一个位置对不上 就不存在
res &= b;
}
}
return res;
}
public int size(){
return SET.size();
}
public int length(){
return SET.length();
}
static class HashFunction{
private final int size;
private final int seed;
public HashFunction(int size, int seed){
this.size = size;
this.seed = seed;
}
public int hash(String value){
int hash = 0;
char[] chars = value.toCharArray();
for (char ch : chars) {
hash = hash * seed + ch;
}
return (size-1) & hash;
}
}
}
实验
package com.lvshui5u.datastructure.redis;
/**
* @author: lvshui5u
* @date: 2021/8/20 14:59
* @describe:
*/
public class BloomFilterTest {
public static void main(String[] args) {
BloomFilter bloomFilter = new BloomFilter();
for (int i = 0; i < 100000; i++) {
bloomFilter.add("helloWorld-" + i);
}
System.out.println(bloomFilter.contains("helloWorld-1"));
System.out.println(bloomFilter.contains("helloWorld-9"));
System.out.println(bloomFilter.contains("helloWorld-10"));
System.out.println(bloomFilter.contains("helloWorld-99"));
System.out.println(bloomFilter.contains("helloWorld-100"));
System.out.println(bloomFilter.contains("helloWorld-999"));
System.out.println(bloomFilter.contains("helloWorld-1000"));
System.out.println(bloomFilter.contains("helloWorld-9999"));
System.out.println(bloomFilter.contains("helloWorld-10000"));
System.out.println(bloomFilter.contains("helloWorld-99999"));
System.out.println(bloomFilter.contains("helloWorld-100000"));
}
}
结果
true
true
true
true
true
true
true
true
true
true
false