Redis:bitmap+布隆过滤器

本文将讲解bitmap的概念、命令、应用场景布隆过滤器的概念和使用bitmap实现布隆过滤器

bitmap

bitmap概念

bitmap,一种用于处理位操作的特殊数据结构,是一个由二进制位组成的字符串(即二进制数组,每一位都只能是0或1),常用于二值统计场景。

bitmap命令

  • SETBIT key offset value:将指定偏移量处的位设置为指定的值(0或1)。
  • GETBIT key offset:获取指定偏移量处的位的值。
  • BITCOUNT key [start end]:统计指定范围内位为1的数量。
  • BITOP operation destkey key [key ...]:对多个Bitmap进行位操作,将结果存储在目标键中,支持的位操作有AND、OR、XOR和NOT。
  • BITPOS key bit [start [end]]:查找指定位值(0或1)在Bitmap中的位置范围。

bitmap应用场景

任何二值统计场景都可以使用bitmap,如:

  • 签到、打卡应用。
  • 用户在线状态统计。

布隆过滤器

布隆过滤器是一种用于判断某个元素是否属于某个集合的特殊数据结构,其具有判断为无则不存在,判断为有则不一定存在的特性。

思想

当有变量被加入集合时,通过N个映射函数将这个变量映射成位图中的N个点, 把它们置为 1(假定有两个变量都通过 3 个映射函数)。

查询某个变量的时候我们只要看看这些点是不是都是 1, 就可以大概率知道集合中有没有它了:

  • 如果这些点,有任何一个为零则被查询变量一定不在。
  • 如果都是 1,则被查询变量很可能存在(因为存在哈希碰撞的情况)。

应用场景

布隆过滤器常用于预防缓存穿透,其存储已存在数据的key,当有新的请求时,先到布隆过滤器中查询是否存在:

  • 如果布隆过滤器中不存在该条数据则直接返回。
  • 如果布隆过滤器中已存在,才去查询缓存redis,如果redis里没查询到则再查询Mysql数据库。

另外也可以根据布隆过滤器数据结构的特点,用于进行黑名单、白名单校验等场景。

缺陷

布隆过滤器有两方面的缺陷,都是由哈希碰撞导致的:

  1. 如果判断为有,可能为无。
  2. 无法删除元素,否则会导致存在的其他元素被删除而判断为无。

代码实现

首先声明依赖:

<dependencies>
    <dependency>
        <groupId>org.freemarker</groupId>
        <artifactId>freemarker</artifactId>
        <version>2.3.31</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.12.5</version>
    </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.3.2</version>
    </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-generator</artifactId>
        <version>3.5.3.2</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <version>8.0.33</version>
    </dependency>
</dependencies>

本次将使用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> 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()));
    }
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
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 ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值