本文将讲解bitmap的概念、命令、应用场景,布隆过滤器的概念和使用bitmap实现布隆过滤器。
bitmap,一种用于处理位操作的特殊数据结构,是一个由二进制位组成的字符串(即二进制数组,每一位都只能是0或1),常用于二值统计场景。
任何二值统计场景都可以使用bitmap,如:
布隆过滤器是一种用于判断某个元素是否属于某个集合的特殊数据结构,其具有判断为无则不存在,判断为有则不一定存在的特性。
当有变量被加入集合时,通过N个映射函数将这个变量映射成位图中的N个点, 把它们置为 1(假定有两个变量都通过 3 个映射函数)。
查询某个变量的时候我们只要看看这些点是不是都是 1, 就可以大概率知道集合中有没有它了:
布隆过滤器常用于预防缓存穿透,其存储已存在数据的key,当有新的请求时,先到布隆过滤器中查询是否存在:
另外也可以根据布隆过滤器数据结构的特点,用于进行黑名单、白名单校验等场景。
布隆过滤器有两方面的缺陷,都是由哈希碰撞导致的:
首先声明依赖:
org.freemarker
freemarker
2.3.31
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-jdbc
org.springframework.boot
spring-boot-starter-data-redis
com.fasterxml.jackson.core
jackson-databind
2.12.5
com.baomidou
mybatis-plus-boot-starter
3.5.3.2
com.baomidou
mybatis-plus-generator
3.5.3.2
org.projectlombok
lombok
com.mysql
mysql-connector-j
8.0.33
本次将使用mybatis-plus-generator反向生成service,以便快速开始bloomfilter内容的展示。
在预估bitmap大小时,可以使用公式m = (-n * ln(p)) / (ln(2)^2),m为位图的大小,n是要存储的对象数量,p是期望的假阳性率。
如1000000数据下,1%假阳性需要9,585,058bit,即≈2^23,需要对hash结果mod2^23来控制位图大小。
BloomUtils.class:
@Component
public class BloomUtils {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
IEmployeesService service;
public static final String KEY_NAME = "WhitelistEmployees";
public void init() {
//白名单客户预加载到布隆过滤器
List employees = service.getBaseMapper().selectList(new QueryWrapper<>());
employees.forEach(employees1 -> {
try {
addToWhitelist(String.valueOf(employees1.getEmployeeId()));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
});
}
//根据m = (-n * ln(p)) / (ln(2)^2)公式(m为位图的大小,n是要存储的对象数量,p是期望的假阳性率)。
// 1000000数据下,1%假阳性需要9,585,058bit,即≈2^23,需要对hash结果mod2^23来控制位图大小。
public void addToWhitelist(String value) throws NoSuchAlgorithmException {
long md5Hash = (long) (hashToPositiveInt(value, "MD5") % Math.pow(2, 23));
long sha1Hash = (long) (hashToPositiveInt(value, "SHA-1") % Math.pow(2, 23));
long sha256Hash = (long) (hashToPositiveInt(value, "SHA-256") % Math.pow(2, 23));
stringRedisTemplate.opsForValue().setBit(KEY_NAME, md5Hash, true);
stringRedisTemplate.opsForValue().setBit(KEY_NAME, sha1Hash, true);
stringRedisTemplate.opsForValue().setBit(KEY_NAME, sha256Hash, true);
}
public boolean checkInWhitelist(String value) throws NoSuchAlgorithmException {
long md5Hash = (long) (hashToPositiveInt(value, "MD5") % Math.pow(2, 23));
long sha1Hash = (long) (hashToPositiveInt(value, "SHA-1") % Math.pow(2, 23));
long sha256Hash = (long) (hashToPositiveInt(value, "SHA-256") % Math.pow(2, 23));
return Boolean.TRUE.equals(stringRedisTemplate.opsForValue().getBit(KEY_NAME, md5Hash)) &&
Boolean.TRUE.equals(stringRedisTemplate.opsForValue().getBit(KEY_NAME, sha1Hash)) &&
Boolean.TRUE.equals(stringRedisTemplate.opsForValue().getBit(KEY_NAME, sha256Hash));
}
public static int hashToPositiveInt(String input, String algorithm) throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance(algorithm);
byte[] hashBytes = digest.digest(input.getBytes(StandardCharsets.UTF_8));
// 使用BigInteger将字节数组表示的哈希值转换为正整数
BigInteger bigIntegerHash = new BigInteger(1, hashBytes);
// 获取正整数表示的哈希值
return bigIntegerHash.intValue();
}
}
然后即可在Controller中直接调用:
@RestController
@RequestMapping("/employees")
public class EmployeesController {
@Autowired
IEmployeesService service;
@Autowired
StringRedisTemplate stringRedisTemplate;
@Autowired
BloomUtils bloomUtils;
@GetMapping("/init")
public void initRedisWhitelist() {
bloomUtils.init();
}
@GetMapping("/check")
public boolean checkWhitelist(@RequestParam String employeesID) throws NoSuchAlgorithmException {
return bloomUtils.checkInWhitelist(employeesID);
}
@GetMapping("/find")
public Employees findByEmployeeById(@RequestParam String employeesID) throws NoSuchAlgorithmException, JsonProcessingException {
if (bloomUtils.checkInWhitelist(employeesID)) {//先过布隆过滤器
String Semployees = stringRedisTemplate.opsForValue().get(employeesID);
Employees employees = null;
if (Semployees == null) {
synchronized (this) {
Semployees = stringRedisTemplate.opsForValue().get(employeesID);// 双检查,检查缓存中是否有数据
if (Semployees == null) {
employees = service.getById(employeesID);// 从数据库获取数据
stringRedisTemplate.opsForValue().set(employeesID, new ObjectMapper().writeValueAsString(employees));// 将数据写入缓存
}
}
return employees;
} else {
return new ObjectMapper().readValue(Semployees, Employees.class);
}
} else {//布隆过滤器不通过则直接返回null
return null;
}
}
@PostMapping("/add")
public void addEmployees(@RequestBody Employees employees) throws JsonProcessingException, NoSuchAlgorithmException {
service.save(employees);
Employees employees1 = service.getById(employees.getEmployeeId());
stringRedisTemplate.opsForValue().set(String.valueOf(employees1.getEmployeeId()),new ObjectMapper().writeValueAsString(employees1));
bloomUtils.addToWhitelist(String.valueOf(employees1.getEmployeeId()));
}
}