目录
1、布隆过滤器是什么
布隆过滤器(Bloom Filter)是一种空间效率高、处理速度快的概率型数据结构,用于判断一个元素是否存在于一个集合中。
布隆过滤器由一个位数组(通常是一个大的二进制位数组)和一系列哈希函数组成。
2、布隆过滤器特点
高效地插入和查询,减少内存占用,不保存数据信息,只存储一个标记位0、1。但返回的结果是不确定,有点小瑕疵。
由于存在哈希冲突,一个元素判断结果(返回0或1)存在但元素不一定存在,判断结果不存在则一定不存在(有可能有,无肯定无)。
可以保证的是,如果一个元素不在布隆过滤器中,则此元素一定不存在。
3、布隆过滤器的使用场景
1、解决缓存穿透问题,和Redis结合bitmap使用
2、黑名单校验、识别垃圾邮件
二进制数组构建过程
- 预加载符合条件的记录
- 计算每条记录的哈希值
- 计算哈希值对应的bitmap数组位
- 修改值为1
查找元素的过程
- 计算元素的哈希值
- 计算哈希值对应的biamap数组位
- 找到数组中对应位置的值,0代表不存在,1代表存在
4、代码实战,知行合一
BloomFilterUtil 代码
package com.toonyoo.redis.bloomFilter.util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Component
@Slf4j
public class BloomFilterUtil {
public static final String CUSTOMER_REDIS_KEY = "customer:";
// redis数据白名单key
public static final String WHITE_LIST = "whiteList";
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 布隆过滤器 初始化白名单数据
*/
@PostConstruct
public void init() {
// 以当前数据库表中的一条数据为例
String key = CUSTOMER_REDIS_KEY + "1266688003";
// 1、通过key算哈希值
int value = Math.abs(key.hashCode());
// 2、通过哈希值与2^32取模,算bitmap对应的槽位
long bitmap = (long) (value % Math.pow(2, 32));
// 3、将key对应的槽位存入bitmap
redisTemplate.opsForValue().setBit(WHITE_LIST,bitmap,true);
log.info("布隆过滤器初始化白名单数据成功,{}对应的bitmap槽位:{}",key,bitmap);
}
/**
*
* @param keyItem 布隆过滤器的key即redis中bitmap存放的key == 白名单key
* @param key 数据的key,即通过key算bitmap槽位
* @return
*/
public boolean checkBloomFilter(String keyItem, String key){
// 1、通过key算哈希值
int value = Math.abs(key.hashCode());
// 2、通过哈希值与2^32取模,算bitmap对应的槽位
long bitmap = (long) (value % Math.pow(2, 32));
log.info("检查布Redis数据白名单,{}对应的bitmap槽位:{}",key,bitmap);
// 3、查找key对应的槽位在bitmap中,bitmap中有则可能有,无则一定无
return redisTemplate.opsForValue().getBit(keyItem, bitmap);
}
}
业务层具体实现逻辑
package com.toonyoo.redis.bloomFilter;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.toonyoo.redis.bloomFilter.util.BloomFilterUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import static com.toonyoo.redis.bloomFilter.util.BloomFilterUtil.WHITE_LIST;
@Service
@Slf4j
public class CustomerService extends ServiceImpl<CustomerMapper, Customer> {
@Autowired
private CustomerMapper customerMapper;
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private BloomFilterUtil bloomFilterUtil;
public static final String CUSTOMER_REDIS_KEY = "customer:";
/**
* 业务场景-模拟添加用户并回写进Redis
*
* @param customer
*/
public void addCustomer(Customer customer) {
if (customer != null) {
// 向mysql中写入数据
int i = customerMapper.insert(customer);
// 若写入mysql成功
if (i > 0) {
// 缓存redis key
String key = CUSTOMER_REDIS_KEY + customer.getId();
// 将mysql中的数据重新掏出来
Customer c = customerMapper.selectById(customer.getId());
// 序列化
String jsonString = JSON.toJSONString(c);
// 回写进redis
redisTemplate.opsForValue().set(key, jsonString);
/**
* 此处不能将key存进布隆过滤器,因为存在缓存中的数据会过期,但bitmap不会过期。
* 若缓存大批量过期,先打到布隆过滤器,它认为是白名单,然后就会导致大批请求打到Redis而磁石Redis中数据已经过期
* 导致大量请求打到mysql。
*/
}
}
}
/**
* 模拟业务场景-读取redis数据
* @param id
*/
public Customer getCustomerById(Integer id) {
Customer customer = null;
// 缓存redis key
String key = CUSTOMER_REDIS_KEY + id;
// 最先查布隆过滤器是否存在此key,若无就直接return,这样可以防止缓存穿透
boolean flag = bloomFilterUtil.checkBloomFilter(WHITE_LIST, key);
// 无则一定无,拒绝访问,直接返回
if (!flag){
log.info("布隆过滤器中不存在此key,拒绝访问");
return null;
}
// 先查redis
String result = redisTemplate.opsForValue().get(key);
// 反序列化
customer = JSON.parseObject(result,Customer.class);
// 若redis不在则去查mysql
if (result == null){
customer = customerMapper.selectById(id);
// 若mysql存在数据,还需回写进redis
if (customer != null){
String jsonString = JSON.toJSONString(customer);
redisTemplate.opsForValue().set(key,jsonString);
}
}
return customer;
}
}