redis- manually implement a bloom filter

 

Bloom filter 1. Introduction

布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难


重点 : 
  二进制向量       :  二进制的方式存放数据
  随机映射函数     :  多次运算算出位数
  误判率          :  算出有不一定有 , 算出 没有 一定 没有
  删除困难        :  多次hash 导致删除麻烦
  
布隆过滤器不存放数据 , 只存放数据经过运算后的值 
经过m次运算后 把对应bit位设置为1

 

 

Bloom filter implemented embodiment 2.java

分布式布隆过滤器 : 
  redis实现
    1.bit实现
    2.redis插件实现(4.0之后) https://www.codercto.com/a/87070.html
    
本地布隆过滤器 :
  guava实现

 

 

Bloom filter using 3.guava

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>27.1-jre</version>
</dependency>


创建布隆过滤器方法
public static <T> BloomFilter<T> create(
    Funnel<? super T> funnel, int expectedInsertions, double fpp) {
    
funnel             : 添加数据的接口
expectedInsertions : 预计添加多少数据
fpp                : 能接受的误判率
//创建
BloomFilter<String> filter = BloomFilter.create(new Funnel<String>() {
    @Override
    public void funnel(String from, PrimitiveSink into) {
        System.out.println("testadd");
        into.putString(from, Charsets.UTF_8);
    }
}, 50, 0.1);


//添加元素
filter.put("test");
//判断元素是否存在
boolean test = filter.mightContain("test");
//把另一个布隆过滤器的数据加进来
filter.putAll();

 

 

Bloom filter using 4.redis

 

1.redis Bloom filter to create entity classes

import com.google.common.hash.Funnels;
import com.google.common.hash.Hashing;
import com.google.common.primitives.Longs;
import java.nio.charset.Charset;

public class RedisBloomFilter {


    private int numApproxElements;  //预计存放数据个数
    private double fpp;             //误判率
    private int numHashFunctions;   //计算几次hash
    private int bitmapLength;       //布隆过滤器的容量

    /**
     * 构造布隆过滤器。注意:在同一业务场景下,三个参数务必相同
     *
     * @param numApproxElements 预估元素数量
     * @param fpp 可接受的最大误差(假阳性率)
     */
    public RedisBloomFilter(int numApproxElements, double fpp) {
        this.numApproxElements = numApproxElements;
        this.fpp = fpp;

        bitmapLength = (int) (-numApproxElements * Math.log(fpp) / (Math.log(2) * Math.log(2)));
        numHashFunctions = Math.max(1, (int) Math.round((double) bitmapLength / numApproxElements * Math.log(2)));
    }

    /**
     * 取得自动计算的最优哈希函数个数
     */
    public int getNumHashFunctions() {
        return numHashFunctions;
    }

    /**
     * 取得自动计算的最优Bitmap长度
     */
    public int getBitmapLength() {
        return bitmapLength;
    }

    /**
     * 计算一个元素值哈希后映射到Bitmap的哪些bit上
     *
     * @param element 元素值
     * @return bit下标的数组
     */
    public Long[] getBitIndices(String element) {
        Long[] indices = new Long[numHashFunctions];

        byte[] bytes = Hashing.murmur3_128()
            .hashObject(element, Funnels.stringFunnel(Charset.forName("UTF-8")))
            .asBytes();

        long hash1 = Longs.fromBytes(
            bytes[7], bytes[6], bytes[5], bytes[4], bytes[3], bytes[2], bytes[1], bytes[0]
        );
        long hash2 = Longs.fromBytes(
            bytes[15], bytes[14], bytes[13], bytes[12], bytes[11], bytes[10], bytes[9], bytes[8]
        );

        long combinedHash = hash1;
        for (int i = 0; i < numHashFunctions; i++) {
            indices[i] = (combinedHash & Long.MAX_VALUE) % bitmapLength;
            combinedHash += hash2;
        }

        return indices;
    }
}

2. Ideas

使用redis bit操作 创建布隆过滤器 , 保存redis布隆对象在redis中 
//初始化操作  设置一个名字为redisbit 的redis bit 长度为10W
setbit redisbit 100000 0 
//插入操作
使用guava的计算方法 , 获取到计算后的bit位 , 全部设置到redis中 
//判断是否存在
使用guava的计算方法 , 获取到计算后的bit位 , 判断redis中是否均为1

 

 

3.redis Tools

import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

/**
 * @author
 * @since 2019—11-19
 */
@Component
@Slf4j
public class RedisBloomFilterUtil {

    private static RedisTemplate redisTemplate;
    private static SetOperations<String, String> setOperations;
    private static ValueOperations<String, String> valueOperations;

    public static void initBit(String bitKey, int bitmapLength) {
        valueOperations.setBit(bitKey, bitmapLength, false);
    }

    @Autowired
    public void setRedisTemplate(RedisTemplate<String, String> redisTemplate) {
        RedisBloomFilterUtil.redisTemplate = redisTemplate;
        setOperations = redisTemplate.opsForSet();
        valueOperations = redisTemplate.opsForValue();
    }


    /**
     * 初始化 布隆过滤器
     * @param indexs    要初始化的bit位集合
     * @param key       rediskey值
     * @param size      每次操作多少
     */
    public static void setAllBit(Set<Long> indexs, String key, int size) {

        List<Long> indexList = new LinkedList();
        for (Long index : indexs) {
            indexList.add(index);
            if (indexList.size() == size) {
                addBitPip(indexList, key);
                indexList.clear();
            }
        }
        addBitPip(indexList, key);
        indexList.clear();

    }

    //添加元素
    public static void addBitPip(List<Long> list, String keys) {

        List<Object> objects = redisTemplate.executePipelined((RedisCallback<Long>) connection -> {
            for (Long index : list) {
                connection.setBit(keys.getBytes(), index, true);
            }
            return null;
        });
    }

    //判断元素是否存在
    public static boolean bitMembers(Long[] list, byte[] key) {
        List<Boolean> pipelined = redisTemplate.executePipelined((RedisCallback<Boolean>) connection -> {
            for (Long index : list) {
                connection.getBit(key, index);
                
            }
            return null;
        });
        return !pipelined.contains(false);
//key计算次数较少时方案
//        for (Long index : list) {
//            Boolean flag =valueOperations.getBit(key , index);
//            if (Boolean.FALSE.equals(flag))
//                return false;
//        }
//        return true;
        
    }


    /**
     * 批量添加数据到 set 缓存
     */
    public static void setOperationsAdd(String key, Collection<String> collection) {
        if (collection.size() <= 0) {
            return;
        }
        byte[] keyBytes = key.getBytes();
        if (collection.size() > 3000) {
            List<String> list = new LinkedList<>();
            for (String s : collection) {
                list.add(s);
                if (list.size() >= 3000) {
                    redisTemplate.execute((RedisCallback<Long>) redisConnection -> redisConnection.sAdd(keyBytes, getBytes(list)));
                    list.clear();
                }
            }
            redisTemplate.execute((RedisCallback<Long>) redisConnection -> redisConnection.sAdd(keyBytes, getBytes(list)));
        } else {
            redisTemplate.execute((RedisCallback<Long>) redisConnection -> redisConnection.sAdd(keyBytes, getBytes(collection)));
        }

    }

    public static byte[][] getBytes(Collection<String> list) {
        byte[][] values = new byte[list.size()][];
        int i = 0;
        for (String s : list) {
            values[i++] = s.getBytes();
        }
        return values;
    }

    /**
     * 判断set缓存是否存在field
     */
    public static Boolean setOperationsIsMember(String key, String field) {
        Boolean flag = setOperations.isMember(key, field);
        return flag;
    }

}

 

4. Test results

set --- bit 对比 
模拟100W udid基础数据 
 最优 hash个数13
 最优 bitmap长度38340233
 每次bit插入数量 : 10000
 创建udid个数 : 1000000
 插入set 时间 : 2.582 s
 计算bit时间 : 10.97 s
 总计数量(bit计算后) : 11024880
 创建bit 时间 : 1.695 min
 创建idfa(用来计算误判率) : 1000000
 创建idfa 时间 : 2.832 s
 set判断存在 count : 0
 set判断 时间 : 1.903 min
 判断bit 时间 : 4.627 min
 bit判断存在 count : 1582
 误判率 : 0.001582
//1000W udid 内存对比 (bit 191701167 个位)
set : 800M 
bit : 25m
Published 15 original articles · won praise 21 · views 30000 +

Guess you like

Origin blog.csdn.net/q690080900/article/details/104944711
Recommended