缓存击穿:一个热点Key(数据库存在该数据),有大并发集中对其进行访问,突然间这个Key失效了,导致大并发全部打在数据库上,导致数据库压力剧增,这种现象就叫做缓存击穿。
缓存雪崩 && 缓存击穿
思考:判断两个元素y1和y2。
- y1:y1通过三个哈希函数的映射有一个位置为0,因此布隆过滤器会判断y1不在集合中。
- y2:y2通过三个哈希函数的映射所有位置均为1,因此布隆过滤器会判断y2在集合中。但实际上,y2可能属于这个集合,也可能不属于,因为每个位置都可能与其他元素共用(即发生Hash冲突)。
引入依赖
<dependency>
<groupId>com.google.guavagroupId>
<artifactId>guavaartifactId>
<version>25.0-jreversion>
dependency>
实现示例
public class bloomFilterTest {
public static void main(String[] args) {
// 创建布隆过滤器对象:创建了一个最多存放1500个字符串的布隆过滤器,并且误判率为0.01
BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.integerFunnel(),1500,0.01);
// 将元素添加进布隆过滤器
String data = "data01";
bloomFilter.put(data);
// 判断指定元素是否存在
System.out.println(filter.mightContain(data));
}
}
概述:为了解决分布式场景的问题,集团的分布式缓存中间件Tair(Tair 2.0 RDB)也实现了BloomFilter。
实现示例
<dependency>
<groupId>com.taobao.rdbgroupId>
<artifactId>rdb-client2artifactId>
<version>2.5.0-hotkeyversion>
dependency>
RdbSmartApi rdbSmartApi = RdbSmartFactory.getClientManager("xxx");
rdbSmartApi.setPassWord("xxx");
rdbSmartApi.init();
//Bloom过滤器的名字
String filterName = "myBloomFilter";
//预计要插入的数据大小
Long size = 10000000L;
//误判率,0.00001代表错误率为0.001%
double errorRate = 0.00001;
//创建一个BloomFilter
String result = rdbSmartApi.sync().bfreserve(filterName.getBytes(),size,errorRate);
//创建成功则返回OK
if("OK".equals(result)){
String data = "data01";
//向BloomFilter中插入一条数据
boolean success = rdbSmartApi.sync().bfadd(filterName.getBytes(),data.getBytes());
if(success){
//exist为是否存在
boolean exist = rdbSmartApi.sync().bfexists(filterName.getBytes(),data.getBytes());
}
}
- Redis Modules的介绍:https://redis.io/modules
- RedisBloom的介绍:https://github.com/RedisBloom/RedisBloom
git clone https://github.com/RedisBloom/RedisBloom.git
cd RedisBloom
make #编译 会生成一个rebloom.so文件
redis-server --loadmodule /path/to/rebloom.so #运行redis时加载布隆过滤器模块
redis-cli # 启动连接容器中的 redis 客户端验证
docker pull redislabs/rebloom:latest # 拉取镜像
docker run -p 6379:6379 --name redis-redisbloom redislabs/rebloom:latest #运行容器
docker exec -it redis-redisbloom bash
redis-cli
bf.add:添加元素到布隆过滤器
bf.exists:判断元素是否在布隆过滤器
bf.madd:添加多个元素到布隆过滤器
bf.mexists:判断多个元素是否在布隆过滤器
127.0.0.1:6379> bf.add user Tom
(integer) 1
127.0.0.1:6379> bf.exists user Tom
(integer) 1
127.0.0.1:6379> bf.exists user John
(integer) 0
127.0.0.1:6379> bf.madd user Barry Jerry Mars
1) (integer) 1
2) (integer) 1
3) (integer) 1
127.0.0.1:6379> bf.mexists user Barry Linda
1) (integer) 1
2) (integer) 0
<dependency>
<groupId>org.redissongroupId>
<artifactId>redissonartifactId>
<version>3.15.1version>
dependency>
public class RedissonBloomFilterDemo {
public static void main(String[] args) {
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
// 布隆过滤器的名称
RBloomFilter<String> bloomFilter = redisson.getBloomFilter("user");
// 初始化布隆过滤器,预计统计元素数量为55000000,期望误判率为0.03
bloomFilter.tryInit(55000000L, 0.03);
bloomFilter.add("Tom");
bloomFilter.add("Jack");
System.out.println(bloomFilter.count()); //2
System.out.println(bloomFilter.contains("Tom")); //true
System.out.println(bloomFilter.contains("Linda")); //false
}
}
<dependency>
<groupId>org.apache.hadoopgroupId>
<artifactId>hadoop-commonartifactId>
<version>2.4.1version>
dependency>
public class CountingBloomFilterDemo {
public static void main(String[] args) {
//预计数据的大小
int dataSize = 100000000;
//hash函数的个数
int hashCount = 3;
//使用的hash函数类型,0代表JenkinsHash,1代表MurmurHash
int hashType = 1;
//初始化一个CountingBloomFilter
CountingBloomFilter filter = new CountingBloomFilter(dataSize,hashCount,hashType);
//要put的数据
String data = "data01";
Key key = new Key(data.getBytes());
//将data put到CountingBloomFilter中
filter.add(key);
//判断data是否存在
boolean exist = filter.membershipTest(key);
//将data从CountingBloomFilter中删除
filter.delete(key);
//判断data是否存在
filter.membershipTest(key);
}
}
说明
- 图(a):将新项x插入到8个桶的哈希表中的示例,其中x可以放置在桶2或6中。
- 图(b):表示待插入x的两个桶都没有空间,随机选择了候选桶6,踢出现有项a,重新放置“a”触发了另一个重置,将现有的项“c”从桶4踢到桶1。
布谷鸟过滤器对布谷鸟哈希的改变
布谷鸟过滤器的数据结构:由一个哈希表组成,哈希表由一个桶数组组成,其中一个桶可以有多个条目,每个条目存储一个指纹。
哈希计算两个候选桶索引的方案
插入
Algorithm 1: Insert(x)
f = fingerprint(x);
i1 = hash(x);
i2 = i1 ⊕ hash(f);
if bucket[i1] or bucket[i2] has an empty entry then
add f to that bucket;
return Done;
// must relocate existing items;
i = randomly pick i1 or i2;
for n = 0; n < MaxNumKicks; n++ do
randomly select an entry e from bucket[i];
swap f and the fingerprint stored in entry e;
i = i ⊕ hash(f);
if bucket[i] has an empty entry then
add f to bucket[i];
return Done;
// Hashtable is considered full;
return Failure;
Algorithm 2: Lookup(x)
f = fingerprint(x);
i1 = hash(x);
i2 = i1 ⊕ hash(f);
if bucket[i1] or bucket[i2] has f then
return True;
return False;
Algorithm 3: Delete(x)
f = fingerprint(x);
i1 = hash(x);
i2 = i1 ⊕ hash(f);
if bucket[i1] or bucket[i2] has f then
remove a copy of f from this bucket;
return True;
return False;
CockooFilter的Java单机版实现
<dependency>
<groupId>com.github.mgunlogsongroupId>
<artifactId>cuckoofilter4jartifactId>
<version>1.0.2version>
dependency>
public class CockooFilterDemo {
public static void main(String[] args) {
//要put的数据
String data = "data01";
//预计数据的大小
long dataSize = 100000000L;
//初始化一个CuckooFilter
CuckooFilter<String> filter = new CuckooFilter.Builder<String> (Funnels.stringFunnel(Charset.defaultCharset()), dataSize).build();
//将data put到CuckooFilter中
filter.put(data);
//判断data是否存在
boolean exist = filter.mightContain(data);
//将data从CuckooFilter中移除
filter.delete(data);
}
}
基于Redis的CockooFilter:Redis的module的形式同样支持CuckooFilter。和BloomFilter一样,Jedis也没有CuckooFilter的客户端api调用实现。下面是redis官网对支持CuckooFilter的操作说明。
基于Tair的CockooFilter:集团Tair RDB2.0支持高级数据结构中,同样包含CockooFilter。
<dependency>
<groupId>com.taobao.rdbgroupId>
<artifactId>rdb-client2artifactId>
<version>2.5.0-hotkeyversion>
dependency>
public class CockooFilterTairDemo {
public static void main(String[] args) {
RdbSmartApi rdbSmartApi = RdbSmartFactory.getClientManager("xxx");
rdbSmartApi.setPassWord("xxx");
rdbSmartApi.init();
//Bloom过滤器的名字
String filterName = "myBloomFilter";
//预计要插入的数据大小
Long size = 10000000L;
//创建一个CuckooFilter
String result = rdbSmartApi.sync().cfreserve(filterName.getBytes(),size);
//创建成果,则返回OK
if("OK".equals(result)){
String data = "permission";
//向BloomFilter中插入一条数据
boolean success = rdbSmartApi.sync().bfadd(filterName.getBytes(),data.getBytes());
if(success){
//exist为是否存在,根据BloomFiler的特点,若exist为false,则代表该data一定不存在
boolean exist = rdbSmartApi.sync().bfexists(filterName.getBytes(),data.getBytes());
//将data从CuckFilter中删除,这个歌功能BloomFilter是没有的
rdbSmartApi.sync().cfdel(filterName.getBytes(),data.getBytes());
}
}
}
}
概述
TairBloom
是云redis企业版(Tair3.0)上自带的一个扩展数据结构,其采用redis module方式实现了一个可动态扩容的布隆过滤器(ScalableBloomFilter
)。ScalableBloomFilter
的思想:新建一个布隆过滤器,将多个布隆过滤器“组装”成一个布隆过滤器使用。
ScalableBloomFilter
模型(下文简称SBF),该SBF一共包含BF0和BF1两层。说明:SBF的插入和查询过程比较简单。但是注意,首先,BF1在直观上要比BF0长很多(m比特位数高);其次,BF1上的散列函数k(哈希函数个数)也要比BF0上的大。
应用场景:非常适合推荐系统场景。文章阅读功能,如果想留住用户,就要尽可能给用户推荐他喜欢类型的文章,但是又不能推荐重复的文章(特别是用户最近刚读过的文章)。为此,将用户读过的文章加入到TairBloom中,并在推荐给用户之前,先去TairBloom中查询一下该用户是否读过该文章,如果读过就不推荐,否则就可以加入推荐列表。