Redis7_15 高阶篇 第六章 使用Redis的Bitmap实现布隆过滤器

 目录

面试题(文末获取答案)

布隆过滤器

是什么

怎么用

为何引入

原理

​编辑

特点

实战

需求

实现

初始化一确定的白名单

实体类 entities

服务层

控制层

演示

启动项目

查询白名单用户

控制台打印结果

查询返回结果

查询非白名单用户

控制台打印结果

面试题(带答案)


面试题(文末获取答案)

现有50亿个电话号码,现有10万个电话号码。如何要快速准确的判断这些电话号码是否已经存在?

你是如何解决项目中的缓存击穿问题的?

判断是否存在,布隆过滤器了解过吗?

安全连接网址,全球数10亿的网址判断。

黑名单校验,识别垃圾邮件。

白名单校验,识别出合法用户进行后续处理。

布隆过滤器

是什么

布隆过滤器(Bloom Filter)是一种空间效率很高的数据结构,用于快速判断一个元素是否在一个集合中。

本质是一个很长的二进制向量和一系列随机映射函数。

怎么用

  • 由于布隆过滤器依靠bit数组实现,我们可以结合redis的bitmap 和 java自带的hash函数来配合实现布隆过滤器        (本章详解)
  • 也可以使用Google的开源java库 Guava,Guava库中有对Bloom过滤器的实现,可以直接使用。还能为其设置误判率等。(下一章详解)

为何引入

为了迅速的判断某元素是否存在于一个大的集合中。 

原理

添加key时,由几个不同的无偏hash函数(无偏表示分布均匀)计算出几个哈希值,取模,对应到bit数组上的各处,为其赋值为1。

同理,判断某key是否存在于该bit数组中时,也经过几个哈希函数后确定对应bit位上是否均为1,不都为1则判定一定不存在于bit数组中,都为一则可能存在于该bit数组中。

特点

  1. 空间效率高:相比于传统的列表或集合,布隆过滤器使用很少的空间就可以表示一个大的集合。
  2. 查询速度快:无论数据量大小,检查元素是否存在的时间都非常快,时间复杂度接近O(1)
  3. 无法删除布隆过滤器不支持从集合中删除元素,因为删除一个元素会影响到其他元素的判断结果。(因为可能存在哈希碰撞,删除keyA的同时,也可能把其他不想删除的Key给连带删除了)
  4. 有误判:布隆过滤器可能会将不存在的元素误判为存在(假阳性),但不会将存在的元素误判为不存在。
  5. 不存储元素本身:布隆过滤器不存储元素本身的信息,只能判断元素是否可能存在。
  6. 可定制误判率:通过调整布隆过滤器的大小和使用的哈希函数数量,可以在占用空间和误判率之间进行权衡。
  7. 适用场景:特别适合于那些不需要100%准确性,但对空间和时间效率有高要求的场景。

最重要的特点

  • 布隆过滤器判断为存在,则不一定存在。
  • 布隆过滤器判断为不存在,则一定不存在。
  • (有是可能有,无是一定无。)

实战

需求

模拟查询用户的场景,给定一初始白名单,实现布隆过滤器,过滤掉不存在白名单中的用户id的请求。

实现

初始化一确定的白名单

注意:这里我们只采用了一个hash函数,实际可以增加,没有难度,这里为方便只使用了一个哈希函数.

package com.atguigu.redis7.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**
 * Description:布隆过滤器白名单初始化工具类,一开始就设置一部分数据为白名单所有,
 * 白名单业务默认规定:布隆过滤器有,redis也有。
 * Author: fy
 * Date: 2024/4/20 21:11
 */
@Component
@Slf4j
public class BloomFilterInit {

    @Autowired
    private RedisTemplate redisTemplate;

    /*
        布隆过滤器初始化加载工具
        初始化一些白名单的数据。
     */
    @PostConstruct
    public void init() {
        //1.白名单数据的key
        String key = "customer:12";
        //2.计算哈希,避免负数,取绝对值
        long hashValue = Math.abs(key.hashCode());
        //3.获取hash值对应的bit位
        long bitIndex = (long) (hashValue % (Math.pow(2, 32)));
        log.info("key:{},hashValue:{},bitIndex:{}", key, hashValue, bitIndex);
        //2024-04-20 21:37:56.713 [main] INFO  com.atguigu.redis7.filter.BloomFilterInit- keycustomer:12,hashValue:1772098755,bitIndex:1772098755
        //4.设置redis中的bitmap对应位为1
        redisTemplate.opsForValue().setBit("whitelistCustomer", bitIndex, true);
    }

}

实体类 entities

package com.atguigu.redis7.entities;

import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;

@Table(name = "t_customer")
public class Customer {
    @Id
    @GeneratedValue(generator = "JDBC")
    private Integer id;

    private String cname;

    private Integer age;

    private String phone;

    private Byte sex;

    private Date birth;

    /**
     * @return id
     */
    public Integer getId() {
        return id;
    }

    /**
     * @param id
     */
    public void setId(Integer id) {
        this.id = id;
    }

    /**
     * @return cname
     */
    public String getCname() {
        return cname;
    }

    /**
     * @param cname
     */
    public void setCname(String cname) {
        this.cname = cname;
    }

    /**
     * @return age
     */
    public Integer getAge() {
        return age;
    }

    /**
     * @param age
     */
    public void setAge(Integer age) {
        this.age = age;
    }

    /**
     * @return phone
     */
    public String getPhone() {
        return phone;
    }

    /**
     * @param phone
     */
    public void setPhone(String phone) {
        this.phone = phone;
    }

    /**
     * @return sex
     */
    public Byte getSex() {
        return sex;
    }

    /**
     * @param sex
     */
    public void setSex(Byte sex) {
        this.sex = sex;
    }

    /**
     * @return birth
     */
    public Date getBirth() {
        return birth;
    }

    /**
     * @param birth
     */
    public void setBirth(Date birth) {
        this.birth = birth;
    }
}

服务层

package com.atguigu.redis7.service;
import com.atguigu.redis7.entities.Customer;
import com.atguigu.redis7.mapper.CustomerMapper;
import com.atguigu.redis7.utils.CheckUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;

@Service
@Slf4j
public class CustomerService {
    public static final String CACHE_KEY_CUSTOMER = "customer:";

    @Resource
    private CustomerMapper customerMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private CheckUtils checkUtils;

    /**
     * 添加客户
     *
     * @param customer 客户
     */
    public void addCustomer(Customer customer) {
        int i = customerMapper.insertSelective(customer);

        //如果插入数据成功,把该数据缓存到redis一份
        if (i > 0) {
            //到数据库里面,重新捞出新数据出来,做缓存
            customer = customerMapper.selectByPrimaryKey(customer.getId());
            //缓存key
            String key = CACHE_KEY_CUSTOMER + customer.getId();
            //往mysql里面插入成功随后再从mysql查询出来,再插入redis
            redisTemplate.opsForValue().set(key, customer);
        }
    }

    /**
     * 按 ID 查找客户
     *
     * @param customerId 客户 ID
     * @return {@code Customer}
     */
    public Customer findCustomerById(Integer customerId) {
        Customer customer = null;
        //缓存key的名称
        String key = CACHE_KEY_CUSTOMER + customerId;
        //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;
    }

    /**
     * 查找时,先去布隆过滤器看看是不是在白名单,如果发现返回了0,直接挡住,不查redis和数据库
     * (也可以做个黑名单,看看请求的内容是否在里面,不在就通过)
     * BloomFilter → redis → mysql
     * 白名单:whitelistCustomer
     * @param customerId
     * @return
     */
    public Customer findCustomerByIdWithBloomFilter (Integer customerId)
    {
        Customer customer = null;

        //缓存key的名称
        String key = CACHE_KEY_CUSTOMER + customerId;

        //布隆过滤器check,无是绝对无,有是可能有
        // 查不到,直接返回null,不许你去redis和mysql 以免过多的缓存击穿
        //===============================================
        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;
    }
}

 

控制层

package com.atguigu.redis7.controller;

import com.atguigu.redis7.entities.Customer;
import com.atguigu.redis7.service.CustomerService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Random;
import java.util.Date;
import java.util.concurrent.ExecutionException;


@Api(tags = "客户Customer接口+布隆过滤器讲解")
@RestController
@Slf4j
public class CustomerController {
    @Resource
    private CustomerService 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);
        }
    }

    /**
     * 按 ID 查找客户
     *
     * @param id 同上
     * @return {@code Customer}
     */
    @ApiOperation("单个用户查询,按customerid查用户信息")
    @RequestMapping(value = "/customer/{id}", method = RequestMethod.GET)
    public Customer findCustomerById(@PathVariable int id) {
        return customerSerivce.findCustomerById(id);
    }

    /**
     * 使用 Bloom 过滤器按 ID 查找客户
     *
     * @param id 同上
     * @return {@code Customer}
     * @throws ExecutionException   执行异常
     * @throws InterruptedException 中断异常
     */
    @ApiOperation("BloomFilter案例讲解")
    @RequestMapping(value = "/customerbloomfilter/{id}", method = RequestMethod.GET)
    public Customer findCustomerByIdWithBloomFilter(@PathVariable int id) {
        return customerSerivce.findCustomerByIdWithBloomFilter(id);
    }
}
 

演示

启动项目

查询白名单用户

控制台打印结果

2024-06-24 11:11:56.610 [http-nio-7777-exec-3] INFO  com.atguigu.redis7.utils.CheckUtils- ----->key:customer:12    对应坑位index:1772098755    是否存在:true

查询返回结果

查询非白名单用户

控制台打印结果

2024-06-24 11:12:48.345 [http-nio-7777-exec-5] INFO  com.atguigu.redis7.utils.CheckUtils- ----->key:customer:13    对应坑位index:1772098754    是否存在:false
2024-06-24 11:12:48.347 [http-nio-7777-exec-5] INFO  com.atguigu.redis7.service.CustomerService- 白名单无此顾客信息:customer:13

查询返回结果

面试题(带答案)

现有50亿个电话号码,现有10万个电话号码。如何要快速准确的判断这些电话号码是否已经存在?

你是如何解决项目中的缓存击穿问题的?

判断是否存在,布隆过滤器了解过吗?

安全连接网址,全球数10亿的网址判断?

黑名单校验,识别垃圾邮件?

白名单校验,识别出合法用户进行后续处理?

答:(以上问题均可用该回答)

  1. 这些场景可以用布隆过滤器来实现,因为布隆过滤器能快速的识别该key是否存在于该集合,虽然布隆过滤器有可能将不存在的key误判为存在,但这样的小概率误判我们是可以容许的。
  2. 如果布隆过滤器判断为不存在,那是一定不存在于该集合,我们就可以基于此做出业务逻辑,加入黑名单,给出空值,缓存空值,直接返回即不允许继续查询等等。
  • 12
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
Redis提供的Bitmap可以作为布隆过滤器所需要的位数组的基础。布隆过滤器是一种数据结构,用于判断一个元素是否属于一个集合,具有效的添加和查询操作。布隆过滤器使用一系列的哈希函数将元素映射到位数组中的多个位置,通过检查这些位置是否被置为1来判断元素是否存在。RedisBitmap就是一种位数组,可以将布隆过滤器的位数组存储在其中。Bitmap提供了位操作的功能,可以用来设置和查询位的状态。通过使用Bitmap,我们可以方便地实现布隆过滤器的添加和查询操作。因此,RedisBitmap正好适用于实现布隆过滤器的功能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [借助Redis Bitmap实现简单的布隆过滤器](https://blog.csdn.net/huangchonghai/article/details/120340977)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [玩转Redis-Redis布隆过滤器使用及原理](https://blog.csdn.net/u010887744/article/details/108700911)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孤尘Java

感谢认可!感谢您的打赏!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值