分布式布隆过滤器 基于redis

1.bitMap(位图)
bitMap中用一位代表一个值 常见应用在数据去重
bitMap空间复杂度不会随着数据量的增大而增大
bitMap空间复杂度会随要存储的数据元素的最大值线性增大

比如存 ‘4’ 会直接存在第4位 那么64bit类型的数据 最大值为2^64 那么要存到bitMap中 就要找到第2^64次方比特位存这个数 就需要
2^64bit=2EB空间 和天文数字一样不现实。

hashMap也可以字典去重 但是存1个数需要4byte 也就是4*8=32bit 而bitMap中一个数只占一个bit位 所以bitMap更加省空间。

jdk中就有一个实现 bitSet 用过的都知道 bitSet的构造函数传入的容量为int类型 而不是long 原因如上。所以面对海量数据需要大量的空间时bitMap就不合适了

2.布隆过滤器
布隆过滤器是对hash表以及bitMap的一种极致应用
1.首先创建一个bitMap 其中所有元素置为0
2.创建多个hash函数 每存一个数就通过这几个hash函数映射到指定的位置并置为1,比如有8个hash函数就说明 8个bit位代表一个数
3.判断一个数是否存在 就通过这几个hash函数找到指定位置 其中只要有1位为0那么就不存在,都为1那么这个数可能存在。

由于大量数据一般处于分布式环境中所以这里准备的是基于redis的布隆过滤器。(redis中有位图的实现)

1.redis工具类

@Component
public class RedisUtil {
    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 设置bitMap
     *
     * @param key    bitMap key
     * @param offset 设置在多少位
     * @param flag   设置的值
     */
    public void setBit(String key, Long offset, Boolean flag) {
        redisTemplate.opsForValue().setBit(key, offset, flag);
    }

    /**
     * 获取bitMap指定位置的值
     *
     * @param key    bitMap key
     * @param offset 位置
     */
    public Boolean getBit(String key, Long offset) {
        return redisTemplate.opsForValue().getBit(key, offset);
    }
}

2.建立hash函数所需要的seed

public enum CorrectRatio {
    /**
     * 32位代表一个数
     */
    HIGH(new int[]{2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97,
            101, 103, 107, 109, 113, 127, 131});

    private int[] seed;

    CorrectRatio(int[] size) {
        this.seed = size;
    }

    public int[] getSeed() {
        return seed;
    }

    public void setSeed(int[] seed) {
        this.seed = seed;
    }
}

3.布隆过滤器

public class BoolmfilterForRedis {

    @Autowired
    private RedisUtil redisUtil;

    /**
     * BKRD Hash算法seed
     */
    private int[] seed;

    /**
     * hash方法 数组
     */
    private HashFunction[] hashFunction;

    /**
     * 所需bitMap 位数 为2的幂次方
     */
    private Long cap;

    private static final String bitMapPrefix = "bitMapForBoolmfilter";


    /**
     * 初始化容量 redis中位图的容量
     *
     * @param n 输入量
     */
    private void initCap(CorrectRatio correctRatio, Long n) {
        /*
         * 如果correctRatio.getSeed().length 为32 即有32个hash函数对一个数做映射
         * 那么 bitSet中 32位代表一个数 所需要的最大容量为32*n
         */
        this.cap = correctRatio.getSeed().length * n;
    }


    /**
     * 构造 hash函数
     *
     * @param correctRatio
     */
    private void createHashFunctions(CorrectRatio correctRatio) {
        this.seed = correctRatio.getSeed();
        this.hashFunction = new HashFunction[this.seed.length];
        for (int i = 0; i < seed.length; i++) {
            this.hashFunction[i] = new HashFunction(seed[i], this.cap);
        }
    }

    /**
     * 初始化
     *
     * @param correctRatio
     * @param n
     */
    public BoolmfilterForRedis(CorrectRatio correctRatio, Long n) {
        initCap(correctRatio, n);
        createHashFunctions(correctRatio);
    }

    /**
     * 添加
     *
     * @param value
     */
    public void add(String value) {
        for (int i = 0; i < seed.length; i++) {
            redisUtil.setBit(bitMapPrefix, this.hashFunction[i].hash(value), true);
        }
    }

    /**
     * 包含
     *
     * @param value
     * @return
     */
    public Boolean contain(String value) {
        boolean flag = true;
        for (int i = 0; i < seed.length; i++) {
            flag = flag && redisUtil.getBit(bitMapPrefix, this.hashFunction[i].hash(value));
            if (!flag) {
                return false;
            }
        }
        return flag;
    }


    /**
     * hash算法
     */
    private class HashFunction {
        private Integer seed;

        private Long cap;

        public HashFunction(Integer seed, Long cap) {
            this.seed = seed;
            this.cap = cap;
        }

        public Long hash(String value) {
            Integer result = 0;
            for (int i = 0; i < value.length(); i++) {
                result = result * this.seed + value.charAt(i);
            }
            //求余数 防止redis中bitMap无限膨胀  类似循环队列 可以防止系统OOM
            return result & (cap - 1);
        }
    }
}

4.由于初始化不能通过无参构造函数 所以还要配置

@Configuration
public class BoolmfilterConfig {
    @Value("${boolmfilter.inputnum.pow}")
    private Integer pow;

    @Bean
    public BoolmfilterForRedis getBoolmfilter(){
        //输入数据量cap=输入数据量 * hash个数  
        // hash个数就是seed数 所以要求seed个数为2的幂次方 输入量也要是2的幂次方 详情参考hashMap容量为什么为2的幂次方
        BoolmfilterForRedis boolmfilter=new BoolmfilterForRedis(CorrectRatio.HIGH,(long)1<<pow);
        return boolmfilter;
    }
}

5.测试
测试前准备1000uuid存入

@RunWith(SpringRunner.class)
@SpringBootTest
public class GatewayApplicationTests {
    @Autowired
    private BoolmfilterForRedis boolmfilter;
    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    public void contextLoads() {
        //1.先存入1000个uuid
        for (int i=0;i<1000;i++) {
            redisTemplate.opsForList().rightPush("xx", UUID.randomUUID().toString());
        }
    }

}

2.将数据填入boolmfilter

  List<String> xx = redisTemplate.opsForList().range("bitMapForBoolmfilter", 0L, 100L);
        xx.forEach(s -> {
            boolmfilter.add(s);
        });

3.测试通过率 由于uuid基本不重复 所以这里生成的uuid通过率在不误判的情况下应为0

int count = 0;
        for (int i = 0; i < 10000; i++) {
            boolean contains = boolmfilter.contain(UUID.randomUUID().toString());
            if (contains == true) {
                count++;
            }
        }
        System.out.println(count);

结果:
在这里插入图片描述
所以这边10000次测试都不通过 误判率比较低低

找一条已经存入布隆过滤器的uuid测试
在这里插入图片描述

   Boolean contain = boolmfilter.contain("6cce2977-1bc2-4e19-9e9a-2485ff6b7950");
        System.out.println(contain);

测试结果:
在这里插入图片描述
判断正确

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值