Introducción de filtro de floración

Diagrama esquemático del filtro de floración
Cada elemento en la matriz de bits ocupa solo 1 bit, y cada elemento solo puede ser 0 o 1. De esta manera, la solicitud de una matriz de bits de 100w elementos solo ocupa 1000000Bit / 8 = 125000 Byte = 125000/1024 kb ≈ 122kb.

Resumen: una persona llamada Bloom propuso una estructura de datos para recuperar si los elementos están en una colección grande dada. Esta estructura de datos es eficiente y tiene un buen rendimiento, pero la desventaja es que tiene una cierta tasa de reconocimiento de errores y dificultad de eliminación . Y, en teoría, cuantos más elementos se agreguen a la colección, mayor será la probabilidad de falsos positivos.

2. Introducción del principio del filtro Bloom

Cuando se agrega un elemento al filtro Bloom, se realizan las siguientes operaciones:

  1. Use la función hash en el filtro Bloom para calcular el valor del elemento para obtener el valor hash (hay varias funciones hash para obtener varios valores hash).
  2. Según el valor hash obtenido, el valor del subíndice correspondiente se establece en 1 en la matriz de bits.

Cuando necesitemos determinar si existe un elemento en el filtro Bloom, haremos lo siguiente:

  1. Realice el mismo cálculo de hash nuevamente para el elemento dado;
  2. Después de obtener el valor, determine si cada elemento en la matriz de bits es 1, si el valor es 1, significa que el valor está en el filtro Bloom, si hay un valor distinto de 1, significa que el elemento no está en el filtro Bloom .

Tome un ejemplo simple:

Cálculo de hash de filtro Bloom

Como se muestra en la figura, cuando el almacenamiento de cadenas se agrega al filtro Bloom, la cadena se genera primero mediante múltiples funciones hash con diferentes valores hash, y luego los elementos de la siguiente tabla en la matriz de bits correspondiente se establecen en 1 (Cuando se inicializa la matriz de bits, todas las posiciones son 0). Al almacenar la misma cadena por segunda vez, debido a que la posición correspondiente anterior se ha establecido en 1, es fácil saber que este valor ya existe (la deduplicación es muy conveniente).

Si necesitamos determinar si una cadena está en el filtro Bloom, solo necesitamos realizar el mismo cálculo de hash en la cadena dada nuevamente, y después de obtener el valor, determinar si cada elemento en la matriz de bits es 1 si el valor Ambos son 1, significa que este valor está en el filtro Bloom, si hay un valor distinto de 1, significa que el elemento no está en el filtro Bloom.

Las diferentes cadenas pueden tener la misma posición de hash. En este caso, podemos aumentar adecuadamente el tamaño de la matriz de bits o ajustar nuestra función de hash.

En resumen, podemos concluir que el filtro Bloom dice que existe un elemento, y una pequeña probabilidad juzgará mal. El filtro Bloom dice que un elemento no está presente, entonces el elemento debe estar ausente.

3. Escenarios de uso del filtro Bloom

  1. Juzgue si los datos dados existen: por ejemplo, juzgue si un número está en un conjunto de números que contiene un gran número de números (¡el conjunto de números es grande, más de 500 millones!), Prevenga la penetración de caché (determine si los datos solicitados son efectivos para evitar eludir directamente la base de datos de solicitud de caché) Etc., filtrado de spam del buzón, función de lista negra, etc.
  2. Deduplicación: por ejemplo, cuando se rastrea una URL determinada, se deduplica la URL que se ha rastreado.

4. Implemente manualmente el filtro Bloom a través de la programación Java

Ya hemos hablado sobre el principio del filtro Bloom. Después de conocer el principio del filtro Bloom, puede implementar uno manualmente.

Si desea implementar uno manualmente, debe:

  1. Una matriz de bits de tamaño adecuado contiene los datos.
  2. Varias funciones hash diferentes
  3. Método de agregar elementos a la matriz (filtro Bloom)
  4. El método para determinar si un elemento dado existe en la matriz de bits (filtro Bloom).

Aquí hay un código que creo que es bastante bueno (consulte la mejora del código existente en Internet, aplicable a todo tipo de objetos):

import java.util.BitSet;

public class MyBloomFilter {

    /**
     * 位数组的大小
     */
    private static final int DEFAULT_SIZE = 2 << 24;
    /**
     * 通过这个数组可以创建 6 个不同的哈希函数
     */
    private static final int[] SEEDS = new int[]{3, 13, 46, 71, 91, 134};

    /**
     * 位数组。数组中的元素只能是 0 或者 1
     */
    private BitSet bits = new BitSet(DEFAULT_SIZE);

    /**
     * 存放包含 hash 函数的类的数组
     */
    private SimpleHash[] func = new SimpleHash[SEEDS.length];

    /**
     * 初始化多个包含 hash 函数的类的数组,每个类中的 hash 函数都不一样
     */
    public MyBloomFilter() {
        // 初始化多个不同的 Hash 函数
        for (int i = 0; i < SEEDS.length; i++) {
            func[i] = new SimpleHash(DEFAULT_SIZE, SEEDS[i]);
        }
    }

    /**
     * 添加元素到位数组
     */
    public void add(Object value) {
        for (SimpleHash f : func) {
            bits.set(f.hash(value), true);
        }
    }

    /**
     * 判断指定元素是否存在于位数组
     */
    public boolean contains(Object value) {
        boolean ret = true;
        for (SimpleHash f : func) {
            ret = ret && bits.get(f.hash(value));
        }
        return ret;
    }

    /**
     * 静态内部类。用于 hash 操作!
     */
    public static class SimpleHash {

        private int cap;
        private int seed;

        public SimpleHash(int cap, int seed) {
            this.cap = cap;
            this.seed = seed;
        }

        /**
         * 计算 hash 值
         */
        public int hash(Object value) {
            int h;
            return (value == null) ? 0 : Math.abs(seed * (cap - 1) & ((h = value.hashCode()) ^ (h >>> 16)));
        }

    }
}

Prueba:

        String value1 = "https://javaguide.cn/";
        String value2 = "https://github.com/Snailclimb";
        MyBloomFilter filter = new MyBloomFilter();
        System.out.println(filter.contains(value1));
        System.out.println(filter.contains(value2));
        filter.add(value1);
        filter.add(value2);
        System.out.println(filter.contains(value1));
        System.out.println(filter.contains(value2));

Salida:

false
false
true
true

Prueba:

        Integer value1 = 13423;
        Integer value2 = 22131;
        MyBloomFilter filter = new MyBloomFilter();
        System.out.println(filter.contains(value1));
        System.out.println(filter.contains(value2));
        filter.add(value1);
        filter.add(value2);
        System.out.println(filter.contains(value1));
        System.out.println(filter.contains(value2));

Salida:

false
false
true
true

5. Utilice el filtro Bloom que viene con la guayaba de código abierto de Google

El propósito de mi propia implementación es principalmente hacerme entender el principio del filtro Bloom. La implementación del filtro Bloom en Guava es más autorizada, por lo que en proyectos reales no necesitamos implementar manualmente un filtro Bloom.

Primero necesitamos introducir las dependencias de Guava en el proyecto:

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

El uso real es el siguiente:

Hemos creado un filtro Bloom que puede almacenar hasta 1500 enteros, y podemos tolerar la probabilidad de error de juicio (0.01)

        // 创建布隆过滤器对象
        BloomFilter<Integer> filter = BloomFilter.create(
                Funnels.integerFunnel(),
                1500,
                0.01);
        // 判断指定元素是否存在
        System.out.println(filter.mightContain(1));
        System.out.println(filter.mightContain(2));
        // 将元素添加进布隆过滤器
        filter.put(1);
        filter.put(2);
        System.out.println(filter.mightContain(1));
        System.out.println(filter.mightContain(2));

En nuestro ejemplo, cuando el mightContain()método devuelve verdadero , podemos estar 99% seguros de que el elemento está en el filtro, y cuando el filtro devuelve falso , podemos estar 100% seguros de que el elemento no existe en el filtro.

La implementación del filtro Bloom proporcionado por Guava sigue siendo muy buena (puede ver su implementación de código fuente para obtener más detalles), pero tiene una falla importante que solo puede usarse en una sola máquina (además, la expansión de capacidad no es fácil), Hoy en día, Internet está generalmente distribuido. Para resolver este problema, necesitamos usar el filtro Bloom en Redis.

6. Filtro de floración en Redis

6.1 Introducción

Después de Redis v4.0, hay una función de Módulo (módulo / plug-in), los Módulos de Redis le permiten a Redis usar módulos externos para ampliar sus funciones. El filtro Bloom es el Módulo. Para más detalles, puede consultar la introducción oficial de Redis a los módulos de Redis: https://redis.io/modules.

Además, el sitio web oficial recomendó un RedisBloom como módulo del filtro Redis Bloom, dirección: https://github.com/RedisBloom/RedisBloom. Otros incluyen:

  • redis-lua-scaling-bloom-filter (implementado por el script lua): https://github.com/erikdubbelboer/redis-lua-scaling-bloom-filter
  • pyreBloom (filtro rápido de floración Redis en Python): https://github.com/seomoz/pyreBloom
  • ...

RedisBloom proporciona soporte al cliente en varios idiomas, incluidos: Python, Java, JavaScript y PHP.

6.2 Instalar con Docker

Si necesitamos experimentar el filtro Bloom en Redis es muy simple, ¡solo use Docker! Buscamos directamente en Docker Redis Bloomfilter en Google y encontramos la respuesta que queríamos después de excluir el primer resultado de búsqueda del anuncio (esta es una forma en que generalmente resuelvo el problema, lo comparto), la dirección específica: https: // hub.docker.com/r/redislabs/rebloom/ (la introducción es muy detallada).

Las operaciones específicas son las siguientes:

➜ ~ docker run -p 6379:6379 --name redis-redisbloom redislabs/rebloom:latest
➜ ~ docker exec -it redis-redisbloom bash
root@21396d02c252:/data# redis-cli
127.0.0.1:6379> 

6.3 Lista de comandos comunes

Nota: clave: el nombre del filtro Bloom, elemento: el elemento agregado.

  1. BF.ADD: Agregue el elemento al filtro Bloom, si el filtro aún no existe, cree el filtro. Formato:BF.ADD {key} {item} .
  2. BF.MADD: Agregue uno o más elementos al "filtro Bloom" y cree un filtro que aún no existe. Este comando funciona de BF.ADDla misma manera, excepto que permite múltiples entradas y devuelve múltiples valores. Formato:BF.MADD {key} {item} [item ...] .
  3. ** BF.EXISTS**: Determine si el elemento existe en el filtro Bloom. Formato:BF.EXISTS {key} {item} .
  4. BF.MEXISTS: Determinar si hay uno o más elementos en el formato filtro Bloom: BF.MEXISTS {key} {item} [item ...].

Además, el BF.RESERVEcomando debe ser introducido por separado:

El formato de este comando es el siguiente:

BF.RESERVE {key} {error_rate} {capacity} [EXPANSION expansion]

A continuación se presenta brevemente el significado específico de cada parámetro:

  1. clave: el nombre del filtro Bloom
  2. error_rate: la probabilidad esperada de falsos positivos. Este debería ser un valor decimal entre 0 y 1. Por ejemplo, para una tasa esperada de falsa alarma de 0.1% (1 en 1000), la tasa de error debe establecerse en 0.001. Cuanto más cercano esté este número a cero, mayor será el consumo de memoria de cada elemento y mayor será el uso de CPU de cada operación.
  3. capacidad: la capacidad del filtro. Cuando el número real de elementos almacenados excede este valor, el rendimiento comenzará a disminuir. La degradación real dependerá de la medida en que se exceda el límite. A medida que el número de elementos de filtro aumenta exponencialmente, el rendimiento disminuirá linealmente.

Parámetros opcionales:

  • expansión: si se crea un nuevo subfiltro, su tamaño se multiplicará por el tamaño del filtro actual expansion. El valor de expansión predeterminado es 2. Esto significa que cada subfiltro posterior será el doble del subfiltro anterior.

6.4 Uso real

127.0.0.1:6379> BF.ADD myFilter java
(integer) 1
127.0.0.1:6379> BF.ADD myFilter javaguide
(integer) 1
127.0.0.1:6379> BF.EXISTS myFilter java
(integer) 1
127.0.0.1:6379> BF.EXISTS myFilter javaguide
(integer) 1
127.0.0.1:6379> BF.EXISTS myFilter github
(integer) 0
Publicado 29 artículos originales · elogiado 4 · visitas 2194

Supongo que te gusta

Origin blog.csdn.net/smile001isme/article/details/105501113
Recomendado
Clasificación