【布隆过滤器】
简介
bitmap 实现布隆过滤器
@Api(tags = "客户Customer接口+布隆过滤器讲解")
@RestController
@Slf4j
public class CustomerController
{
@Resource private CustomerSerivce customerSerivce;
@ApiOperation("数据库初始化2条Customer记录插入")
@RequestMapping(value = "/customer/add",method = RequestMethod.POST)
public void addCustomer()
{
for (int i = 0; i < 2; i++) {
Customer customer = new Customer();
customer.setCname("customer"+i);
customer.setAge(new Random().nextInt(30)+1);
customer.setPhone("1381111XXXX");
customer.setSex((byte) new Random().nextInt(2));
customer.setBirth(Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant()));
customerSerivce.addCustomer(customer);
}
}
@ApiOperation("单个customer查询操作,按照customerid查询")
@RequestMapping(value = "/customer/{id}",method = RequestMethod.GET)
public Customer findCustomerById(@PathVariable Integer id)
{
return customerSerivce.findCustomerById(id);
}
@ApiOperation("BloomFilter,按照customerid查询")
@RequestMapping(value = "/customerbloomfilter/{id}",method = RequestMethod.GET)
public Customer findCustomerByIdWithBloomFilter(@PathVariable int id)
{
return customerSerivce.findCustomerByIdWithBloomFilter(id);
}
}
@Service
@Slf4j
public class CustomerSerivce {
public static final String CACHA_KEY_CUSTOMER = "customer:";
@Resource
private CustomerMapper customerMapper;
@Resource
private RedisTemplate redisTemplate;
@Resource
private CheckUtils checkUtils;
/**
* 写操作
*
* @param customer
*/
public void addCustomer(Customer customer) {
int i = customerMapper.insertSelective(customer);
if (i > 0) {
//mysql插入成功,需要重新查询一次将数据捞出来,写进redis
Customer result = customerMapper.selectByPrimaryKey(customer.getId());
//redis缓存key
String key = CACHA_KEY_CUSTOMER + customer.getId();
//捞出来的数据写进redis
redisTemplate.opsForValue().set(key, result);
}
}
public Customer findCustomerById(Integer customreId) {
Customer customer = null;
//缓存redis的key名称
String key = CACHA_KEY_CUSTOMER + customreId;
//1 先去redis查询
customer = (Customer) redisTemplate.opsForValue().get(key);
//2 redis有直接返回,没有再进去查询mysql
if (customer == null) {
// 3 再去查询我们的mysql
customer = customerMapper.selectByPrimaryKey(customreId);
// 3.1 mysql有,redis无
if (customer != null) {
//3.2 把mysq查询出来的数据回写redis,保持一致性
redisTemplate.opsForValue().set(key, customer);
}
}
return customer;
}
/**
* BloomFilter → redis → mysql
* 白名单:whitelistCustomer
*
* @param customerId
* @return
*/
public Customer findCustomerByIdWithBloomFilter(Integer customerId) {
Customer customer = null;
//缓存key的名称
String key = CACHA_KEY_CUSTOMER + customerId;
//布隆过滤器check,无是绝对无,有是可能有
//===============================================
if (!checkUtils.checkWithBloomFilter("whitelistCustomer", key)) {
log.info("白名单无此顾客,不可以访问: " + key);
return null;
}
//===============================================
//1 查询redis
customer = (Customer) redisTemplate.opsForValue().get(key);
//redis无,进一步查询mysql
if (customer == null) {
//2 从mysql查出来customer
customer = customerMapper.selectByPrimaryKey(customerId);
// mysql有,redis无
if (customer != null) {
//3 把mysql捞到的数据写入redis,方便下次查询能redis命中。
redisTemplate.opsForValue().set(key, customer);
}
}
return customer;
}
}
@Component
@Slf4j
public class CheckUtils {
@Resource
private RedisTemplate redisTemplate;
public boolean checkWithBloomFilter(String checkItem, String key) {
int hashValue = Math.abs(key.hashCode());
long index = (long) (hashValue % Math.pow(2, 32));
boolean existOK = redisTemplate.opsForValue().getBit(checkItem, index);
log.info("--->key:" + key + " 对应坑位下标index: " + index + " 是否存在:" + existOK);
return existOK;
}
}
/**
* 布隆过滤器白名单初始化工具类,一开始就设置一部分数据为白名单所有,
* 白名单业务默认规定:布隆过滤器有,redis是极大可能有。
* 白名单:whitelistCustomer
*/
@Component
@Slf4j
public class BloomFilterInit
{
@Resource
private RedisTemplate redisTemplate;
@PostConstruct//初始化白名单数据,暂时注释省的后台打印
public void init()
{
//1 白名单客户加载到布隆过滤器
String key = "customer:12";
//2 计算hashValue,由于存在计算出来负数的可能,我们取绝对值
int hashValue = Math.abs(key.hashCode());
//3 通过hashValue和2的32次方后取余,获得对应的下标坑位
long index = (long)(hashValue % Math.pow(2,32));
log.info(key+" 对应的坑位index:{}",index);
//4 设置redis里面的bitmap对应类型白名单:whitelistCustomer的坑位,将该值设置为1
redisTemplate.opsForValue().setBit("whitelistCustomer",index,true);
}
}
google布隆过滤器
<!--guava Google 开源的 Guava 中自带的布隆过滤器-->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>23.0</version>
</dependency>
public class GuavaBloomFilterService {
//1 定义一个常量
public static final int _1W = 10000;
//2 定义我们guava布隆过滤器,初始容量
public static final int SIZE = 100 * _1W;
//3 误判率,它越小误判的个数也就越少(思考,是否可以是无限小??没有误判岂不是更好)
public static double fpp = 0.01;//0.01 0.000000000000001
//4 创建guava布隆过滤器
private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), SIZE, fpp);
public void guavaBloomFilter() {
//1 先让bloomFilter加入100W白名单数据
for (int i = 1; i <= SIZE; i++) {
bloomFilter.put(i);
}
//2 故意取10W个不在合法范围内的数据,来进行误判率的演示
ArrayList<Integer> list = new ArrayList<>(10 * _1W);
//3 验证
for (int i = SIZE + 1; i <= SIZE + (10 * _1W); i++) {
if (bloomFilter.mightContain(i)) {
log.info("被误判了:{}", i);
list.add(i);
}
}
log.info("误判总数量:{}", list.size());
}
}