redis布隆过滤器简单使用

布隆过滤器无法直接在redis里使用,需要redis4.0以上的版本才能安装插件使用,

当然也可以直接docker安装带布隆过滤器的redis版本

布隆过滤器使用场景:假如现在有一些资讯推送的需求,我们不能给用户推送重复的资讯,那么就需要解决去重的问题,如果把推送的记录都记录进入数据库然后去判重复,那么数据量将会极大,性能也极低,是一种非常不好的方案,如果去全部缓存起来,内存的消耗也是非常的严重,因此redis的布隆过滤器就可以帮我们很好的解决这个去重的问题,还能节省90%以上的空间,那么你可能又要问那为什么不用HyperLogLog去重呢,占用内存还小才12k,那是因为HyperLogLog 只有pfadd和pfcount这两个常用方法,没办法判定特定的数据存不存在,只能统计数量。但是布隆过滤器可以做到这一点,只是可能会有一点误差,它会把出现过的情况分辨的很清楚,没出现过的有可能出现误判,也就是说,推送过的内容就不会再推送,但是没推送过的可能出现漏推。

这里说一下关于误判率的问题:布隆过滤器可以自己设置一些参数来尽量减小误判率

bf.reserve(key,error_rate,initial_size)这个指令可以设置,initial_size默认是100,error_rate默认为0.01,当我们add的值超过了initial_size之后,误判率会上升,error_rate错误率越低,需要得空间也越大,所以这两个参数得斟酌着设置,initial_size初始设的太小,超过这个值后误判率会上升,但是一开始设的太大又会浪费不必要的空间。

另外bf.reserve(key,error_rate,initial_size)这个指令的执行,如果key已经存在的话会报错,所以第一次在还有没key的时候,初始化一次就好。

为什么说布隆过滤器节省空间是因为,传统的set是设置的字符串进去的,假如设置id长度为20,每个元素本身还需要一个指针被set集合引用,这个指针又会占去4个字节(32位)或者8个字节(64位),那么设置一个记录就需要20多个字节,但是布隆过滤器存的是指纹空间,在错误率为0.1%的情况下,一个元素大约需要15bit的空间也就是不到2个字节,所以空间的节省还是很大的。

空间计算器:https://krisives.github.io/bloom-calculator/

windows redis 布隆 redis布隆过滤器应用场景_java

1000000数据在0.1%的错误率情况下所需的内存才:1755.07kb=1.71mb,你就说省不省空间

以下为示例代码:示例为springboot项目

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>

pom引入这两个依赖

主要结构:

windows redis 布隆 redis布隆过滤器应用场景_redis_02

application.yml  项目配置文件

server:
  port: 8080

spring:
  redis:
    database: 0
    host: localhost
    jedis:
      pool:
        max-active: 8
        max-idle: 8
        max-wait: -1ms
        min-idle: 0
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        max-wait: -1ms
        min-idle: 0
      shutdown-timeout: 100ms
    password: 123456
    port: 6379
    timeout: 5000ms
bloomFilterAdd.lua  布隆过滤器lua添加脚本
local bloomKey = KEYS[1]
local value = KEYS[2]

-- 执行添加方法
local result = redis.call('BF.ADD', bloomKey, value)
return result
bloomFilterExists.lua  布隆过滤器lua判断是否存在脚本
local bloomKey = KEYS[1]
local value = KEYS[2]

-- 执行检测是否存在方法
local result = redis.call('BF.EXISTS', bloomKey, value)
return result
bloomFilterReserve.lua  布隆过滤器lua初始化脚本
local bloomKey = KEYS[1]
local error_rate =  tonumber(KEYS[2])
local initial_size = tonumber(KEYS[3])

-- 执行初始化
local result = redis.call('BF.RESERVE', bloomKey, error_rate,initial_size)
return result
RedisStudyApplicationTests  测试文件
package com.redisstudy;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisStudyApplicationTests {

    @Resource
    private RedisTemplate redisTemplate;

    @Test
    public void testBloomFilter() {
        /*这里只是为了模拟,真实情况想办法只做一次初始化操作*/
        if(!reserve("myTest1","0.001","10000"))
        {
            System.out.println("已存在,请直接开始添加");
        }
        else
        {
            System.out.println("初始化成功");
        }
        add("myTest1","user1");
        exists("myTest1","user1");
        exists("myTest1","user2");
    }

    /**
     * 初始化
     * @param key
     * @param error_rate
     * @param initial_size
     * @return
     */
    public boolean reserve(String key,String error_rate,String initial_size){
        DefaultRedisScript<Boolean> bloomAdd = new DefaultRedisScript<>();
        ClassPathResource addPath = new ClassPathResource("bloom/bloomFilterReserve.lua");
        bloomAdd.setScriptSource(new ResourceScriptSource(addPath));
        bloomAdd.setResultType(Boolean.class);
        List<Object> addList= new ArrayList<>();
        addList.add(key);
        addList.add(error_rate);
        addList.add(initial_size);
        try {
            return (Boolean) redisTemplate.execute(bloomAdd,addList);
        }catch (Exception e)
        {
            return false;
        }
    }

    /**
     * 添加
     * @param key
     * @param value
     */
    public void add(String key,String value){
        DefaultRedisScript<Boolean> bloomAdd = new DefaultRedisScript<>();
        ClassPathResource addPath = new ClassPathResource("bloom/bloomFilterAdd.lua");
        bloomAdd.setScriptSource(new ResourceScriptSource(addPath));
        bloomAdd.setResultType(Boolean.class);
        List<Object> addList= new ArrayList<>();
        addList.add(key);
        addList.add(value);
        Boolean addResult = (Boolean) redisTemplate.execute(bloomAdd,addList);
        System.out.println("添加结果:"+addResult);
    }

    /**
     * 判断
     * @param key
     * @param value
     */
    public void exists(String key,String value){
        DefaultRedisScript<Boolean> bloomExists = new DefaultRedisScript<>();
        ClassPathResource existsPath = new ClassPathResource("bloom/bloomFilterExists.lua");
        bloomExists.setScriptSource(new ResourceScriptSource(existsPath));
        bloomExists.setResultType(Boolean.class);
        List<Object> existsList= new ArrayList<>();
        existsList.add(key);
        existsList.add(value);
        Boolean existsResult1 = (Boolean) redisTemplate.execute(bloomExists,existsList);
        System.out.println("查询结果:"+existsResult1);
    }

}
运行结果: 没毛病


第一次运行:

windows redis 布隆 redis布隆过滤器应用场景_布隆过滤器_03

第二次运行:

windows redis 布隆 redis布隆过滤器应用场景_redis_04