1.什么是缓存穿透?
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
2.为什么要用缓存?
1.提升应用程序性能,
2.降低数据库成本
3.减少后端负载
4.可预测的性能
5.消除数据库热点
6.提高读取吞吐量(IOPS)
3.缓存的设计
Insert:新增数据至数据库
update:删除缓存中对应的数据,修改数据库的数据
delete:删除缓存中对应数据,少出数据库的数据
query: 查询缓存中数据,存在数据机返回查询的数据,
缓存中不存在数据则查询数据库,数据库存在数据则返回数据库中的数据,并同步更新到缓存
缓存和数据库都不存在数据,返回null。
4.缓存穿透问题
1.如果查询的数据在缓存和数据库中都不存在,会带来额外的IO操作和开销
2.如果该用户操作是一个偏执狂,无限如此操作怎么办?key value -->null
3.如果此时有鳄鱼攻击者已经发现你的系统漏洞,频繁用不存在数据的key进行请求怎么办?
5.如何咋大数据集合中判断某个元素不存在?
1.容器的选择
1)空间占用小
2)能支持快速查找
2.算法选择
1).确定性(计算多次的结果一致)
2).允许任意的输入且保证固定的长度的输出
利用哈希算法
3.哈希函数的问题分析
这样会产生一种误判,即:Andy其实并没有你在位图中,但是通过hash计算时,返回了存在的结果,这时可以增加位图长度,以及多次哈希计算来解决
6. Boom Filter (不存在漏报,存在误报)
主要是要计算出,m和k的值,以及hash算法
public class TestBoomFilter {
//预期存储的数据量
private int n;
//误判率
private double p; //0.0003
// bit[] length
private int m;
// hash函数个数
private int k;
// bit[]
private BitSet bitMap;
/**
* 构造器
* @param n 存储的目标数据
* @param p 误判率
*/
public TestBoomFilter(int n, double p) {
this.n = n;
this.p = p;
}
/**
* 添加元素
*
* @param element
*/
public void addElement(String element) {
//lazy策略
if (bitMap == null) {
//init操作
init();
}
int[] posArr = getIndexes(element);
for (int temPos : posArr) {
bitMap.set(temPos,true);
}
}
/**
* 判断element元素是否存在在与集合中.
* @param element 判断的元素
* @return
*/
public boolean isExist(String element) {
int[] posArr = getIndexes(element);
boolean flag=true;
for (int temPos : posArr) {
flag= flag && bitMap.get(temPos);
}
return flag;
}
/**
* 获取K个函数计算element的值的下标数组
*
* @param element
* @return
*/
private int[] getIndexes(String element) {
int[] retArr = new int[this.k];
for (int i = 0; i < this.k; i++) {
retArr[i] = HashUtil.MD5Hash(element + i) % this.m;
}
return retArr;
}
/**
* 初始化过程.
*/
private synchronized void init() {
if (this.m == 0) {
//计算m的值..
this.m = (int) ((-n * Math.log(this.p)) / (Math.log(2) * Math.log(2)));
}
if (this.k == 0) {
this.k = Math.max(1, (int) Math.round(this.m / this.n * Math.log(2)));
}
if (bitMap == null) {
bitMap = new BitSet(this.m);
}
System.out.println("this.m : " + this.m);
System.out.println("this.k : " + this.k);
}
/**
* 测试自写的boomfilter
*/
private static void testMyBoomFilter(){
AndyBoomFilter boomFilter = new TestBoomFilter(1000000,0.0003);
for (int i = 0; i <1000000; i++) {
boomFilter.addElement("abc"+i);
}
int count = 0;
for (int i = 0; i <2000000; i++) {
if(boomFilter.isExist("abc"+i)) {
count++;
}
}
System.out.println("count:"+count);
}
/**
* 测试guava的boomfilter
*/
private static void testGuavaBoomFilter(){
BloomFilter<String> bommfilter = BloomFilter.create(Funnels.stringFunnel(Charset.forName("utf-8")), 1000000, 0.0003);
for (int i = 0; i <1000000; i++) {
bommfilter.put("abc"+i);
}
int count = 0;
for (int i = 0; i <2000000; i++) {
if(bommfilter.mightContain("abc"+i)) {
count++;
}
}
System.out.println("count:"+count);
}
public static void main(String[] args) {
testMyBoomFilter();
// testGuavaBoomFilter();
}
}
hash算法
public static int MD5Hash(String key) {
MessageDigest md5 = null;
try {
md5 = MessageDigest.getInstance("md5");
byte[] bytes = key.getBytes();
md5.update(bytes);
BigInteger bi = new BigInteger(md5.digest());
return Math.abs(bi.intValue());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return -1;
}
结果:
7.源码分析
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>21.0</version>
</dependency>
<dependency>
<groupId>com.baqend</groupId>
<artifactId>bloom-filter</artifactId>
<version>1.0.7</version>
</dependency>
计算m和k
@VisibleForTesting
static int optimalNumOfHashFunctions(long n, long m) {
return Math.max(1, (int)Math.round((double)m / (double)n * Math.log(2.0D)));
}
@VisibleForTesting
static long optimalNumOfBits(long n, double p) {
if (p == 0.0D) {
p = 4.9E-324D;
}
return (long)((double)(-n) * Math.log(p) / (Math.log(2.0D) * Math.log(2.0D)));
}