Introdução do filtro bloom

Diagrama esquemático do filtro bloom
Cada elemento na matriz de bits ocupa apenas 1 bit e cada elemento pode ser apenas 0 ou 1. Dessa maneira, a solicitação de uma matriz de bits de elementos de 100 w ocupa apenas 1000000Bit / 8 = 125000 Byte = 125000/1024 kb ≈ 122kb.

Resumo: Uma pessoa chamada Bloom propôs uma estrutura de dados para recuperar se os elementos estão em uma determinada coleção grande.Esta estrutura de dados é eficiente e tem bom desempenho, mas a desvantagem é que ela possui uma certa taxa de reconhecimento de erros e dificuldade de exclusão . E, em teoria, quanto mais elementos forem adicionados à coleção, maior a probabilidade de falsos positivos.

2. Introdução do princípio do filtro Bloom

Quando um elemento é adicionado ao filtro Bloom, são executadas as seguintes operações:

  1. Use a função hash no filtro Bloom para calcular o valor do elemento para obter o valor hash (existem várias funções hash para obter vários valores hash).
  2. De acordo com o valor de hash obtido, o valor do subscrito correspondente é definido como 1 na matriz de bits.

Quando precisarmos determinar se existe um elemento no filtro Bloom, faremos o seguinte:

  1. Execute o mesmo cálculo de hash novamente para o elemento especificado;
  2. Depois de obter o valor, determine se cada elemento na matriz de bits é 1, se o valor for 1, significa que o valor está no filtro Bloom, se houver um valor diferente de 1, significa que o elemento não está no filtro Bloom .

Veja um exemplo simples:

Cálculo de hash do filtro Bloom

Conforme mostrado na figura, quando o armazenamento da string é adicionado ao filtro Bloom, a string é gerada primeiro por várias funções de hash com diferentes valores de hash e, em seguida, os elementos da tabela a seguir na matriz de bits correspondente são definidos como 1 (Quando a matriz de bits é inicializada, todas as posições são 0). Ao armazenar a mesma sequência pela segunda vez, porque a posição correspondente anterior foi definida como 1, é fácil saber que esse valor já existe (a desduplicação é muito conveniente).

Se precisarmos determinar se uma string está no filtro Bloom, precisamos executar o mesmo cálculo de hash na string especificada novamente e, após obter o valor, determinar se cada elemento na matriz de bits é 1 se Ambos são 1, significa que esse valor está no filtro Bloom, se houver um valor diferente de 1, significa que o elemento não está no filtro Bloom.

Seqüências diferentes podem ter a mesma posição de hash. Nesse caso, podemos aumentar adequadamente o tamanho da matriz de bits ou ajustar nossa função de hash.

Em resumo, podemos concluir que o filtro Bloom diz que existe um elemento e que uma pequena probabilidade será julgada incorretamente. O filtro Bloom diz que um elemento não está presente; o elemento deve estar ausente.

3. Cenários de uso do filtro Bloom

  1. Julgue se os dados fornecidos existem: por exemplo, julgue se um número está em um conjunto de números que contém um grande número de números (o número é grande, mais de 500 milhões!), Impede a penetração do cache (determine se os dados solicitados são eficazes para evitar ignorar diretamente o banco de dados de solicitação de cache) Etc., filtragem de spam da caixa de correio, função de lista negra, etc.
  2. Desduplicação: por exemplo, quando um determinado URL é rastreado, o URL que foi rastreado é deduplicado.

4. Implementar manualmente o filtro Bloom por meio de programação Java

Já mencionamos o princípio do filtro Bloom acima e, depois de conhecer o princípio do filtro Bloom, você pode implementá-lo manualmente.

Se você deseja implementar um manualmente, é necessário:

  1. Uma matriz de bits de tamanho apropriado mantém os dados
  2. Várias funções hash diferentes
  3. Método de adição de elementos à matriz (filtro Bloom)
  4. O método para determinar se um determinado elemento existe na matriz de bits (filtro Bloom).

Aqui está um código que eu acho bastante bom (consulte a melhoria de código existente na Internet, aplicável a todos os tipos 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)));
        }

    }
}

Teste:

        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));

Resultado:

false
false
true
true

Teste:

        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));

Resultado:

false
false
true
true

5. Use o filtro Bloom que acompanha o Goava de código aberto do Google

O objetivo da minha própria implementação é principalmente fazer-me entender o princípio do filtro Bloom.A implementação do filtro Bloom no Guava é mais autoritativa, portanto, em projetos reais, não precisamos implementar manualmente um filtro Bloom.

Primeiro, precisamos introduzir as dependências do Guava no projeto:

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

O uso real é o seguinte:

Criamos um filtro Bloom que pode armazenar até 1500 números inteiros e podemos tolerar a probabilidade de erro de julgamento (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));

Em nosso exemplo, quando o mightContain()método retorna true , podemos ter 99% de certeza de que o elemento está no filtro e, quando o filtro retornar false , podemos ter 100% de certeza de que o elemento não existe no filtro.

A implementação do filtro Bloom fornecida pelo Guava ainda é muito boa (você pode ver a implementação do código-fonte para obter mais detalhes), mas há uma falha importante de que ele só pode ser usado em uma única máquina (além disso, a expansão da capacidade não é fácil), Atualmente, a Internet é geralmente distribuída. Para resolver esse problema, precisamos usar o filtro Bloom no Redis.

6. Filtro Bloom em Redis

6.1 Introdução

Após o Redis v4.0, existe uma função Módulo (módulo / plug-in), o Redis Modules permite que o Redis use módulos externos para estender suas funções. O filtro Bloom é o módulo. Para detalhes, você pode conferir a introdução oficial do Redis aos Módulos Redis: https://redis.io/modules.

Além disso, o site oficial recomendou um RedisBloom como um módulo do filtro Redis Bloom, endereço: https://github.com/RedisBloom/RedisBloom. Outros incluem:

  • redis-lua-scaling-bloom-filter (implementado pelo script lua): https://github.com/erikdubbelboer/redis-lua-scaling-bloom-filter
  • pyreBloom (filtro rápido de bloom Redis em Python): https://github.com/seomoz/pyreBloom
  • ...

O RedisBloom fornece suporte ao cliente em vários idiomas, incluindo: Python, Java, JavaScript e PHP.

6.2 Instalar com o Docker

Se precisarmos experimentar o filtro Bloom no Redis, é muito simples, basta usar o Docker! Pesquisamos diretamente o docker redis bloomfilter no Google e encontramos a resposta que buscávamos após excluir o primeiro resultado da pesquisa do anúncio (é uma maneira que geralmente resolvo o problema, o compartilho), o endereço específico: https: // hub.docker.com/r/redislabs/rebloom/ (a introdução é muito detalhada).

As operações específicas são as seguintes:

➜ ~ 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 comuns

Nota: chave: o nome do filtro Bloom, item: o elemento adicionado.

  1. BF.ADD: Adicione o elemento ao filtro Bloom, se o filtro ainda não existir, crie o filtro. FormatoBF.ADD {key} {item} .
  2. BF.MADD: Adicione um ou mais elementos ao "filtro Bloom" e crie um filtro que ainda não existe. Este comando opera BF.ADDda mesma maneira, exceto que permite várias entradas e retorna vários valores. FormatoBF.MADD {key} {item} [item ...] .
  3. ** BF.EXISTS**: Determina se o elemento existe no filtro Bloom. FormatoBF.EXISTS {key} {item} .
  4. BF.MEXISTS: Determinar se existe um ou mais elementos no formato de filtro Bloom: BF.MEXISTS {key} {item} [item ...].

Além disso, o BF.RESERVEcomando precisa ser introduzido separadamente:

O formato deste comando é o seguinte:

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

A seguir, é apresentado brevemente o significado específico de cada parâmetro:

  1. chave: o nome do filtro Bloom
  2. error_rate: a probabilidade esperada de falsos positivos. Este deve ser um valor decimal entre 0 e 1. Por exemplo, para uma taxa esperada de falso alarme de 0,1% (1 em 1000), error_rate deve ser definido como 0,001. Quanto mais próximo esse número estiver de zero, maior o consumo de memória de cada item e maior o uso da CPU de cada operação.
  3. capacidade: a capacidade do filtro. Quando o número real de elementos armazenados exceder esse valor, o desempenho começará a diminuir. A degradação real dependerá da extensão em que o limite for excedido. À medida que o número de elementos de filtro aumenta exponencialmente, o desempenho diminui linearmente.

Parâmetros opcionais:

  • expansão: se um novo subfiltro for criado, seu tamanho será multiplicado pelo tamanho do filtro atual expansion. O valor de expansão padrão é 2. Isso significa que cada subfiltro subsequente será duas vezes o 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 artigos originais · elogiado 4 · visitas 2194

Acho que você gosta

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