目录
面试题(文末获取答案)
现有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数组中。
特点
- 空间效率高:相比于传统的列表或集合,布隆过滤器使用很少的空间就可以表示一个大的集合。
- 查询速度快:无论数据量大小,检查元素是否存在的时间都非常快,时间复杂度接近O(1)。
- 无法删除:布隆过滤器不支持从集合中删除元素,因为删除一个元素会影响到其他元素的判断结果。(因为可能存在哈希碰撞,删除keyA的同时,也可能把其他不想删除的Key给连带删除了)
- 有误判:布隆过滤器可能会将不存在的元素误判为存在(假阳性),但不会将存在的元素误判为不存在。
- 不存储元素本身:布隆过滤器不存储元素本身的信息,只能判断元素是否可能存在。
- 可定制误判率:通过调整布隆过滤器的大小和使用的哈希函数数量,可以在占用空间和误判率之间进行权衡。
- 适用场景:特别适合于那些不需要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亿的网址判断?
黑名单校验,识别垃圾邮件?
白名单校验,识别出合法用户进行后续处理?
答:(以上问题均可用该回答)
- 这些场景可以用布隆过滤器来实现,因为布隆过滤器能快速的识别该key是否存在于该集合,虽然布隆过滤器有可能将不存在的key误判为存在,但这样的小概率误判我们是可以容许的。
- 如果布隆过滤器判断为不存在,那是一定不存在于该集合,我们就可以基于此做出业务逻辑,加入黑名单,给出空值,缓存空值,直接返回即不允许继续查询等等。