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:
- 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).
- 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:
- Execute o mesmo cálculo de hash novamente para o elemento especificado;
- 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:
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
- 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.
- 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:
- Uma matriz de bits de tamanho apropriado mantém os dados
- Várias funções hash diferentes
- Método de adição de elementos à matriz (filtro Bloom)
- 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.
BF.ADD
: Adicione o elemento ao filtro Bloom, se o filtro ainda não existir, crie o filtro. FormatoBF.ADD {key} {item}
.BF.MADD
: Adicione um ou mais elementos ao "filtro Bloom" e crie um filtro que ainda não existe. Este comando operaBF.ADD
da mesma maneira, exceto que permite várias entradas e retorna vários valores. FormatoBF.MADD {key} {item} [item ...]
.- **
BF.EXISTS
**: Determina se o elemento existe no filtro Bloom. FormatoBF.EXISTS {key} {item}
. BF.MEXISTS
: Determinar se existe um ou mais elementos no formato de filtro Bloom:BF.MEXISTS {key} {item} [item ...]
.
Além disso, o BF.RESERVE
comando 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:
- chave: o nome do filtro Bloom
- 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.
- 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