布隆過濾器
本質上布隆過濾器是一種數據結構,可以用來告訴你 “某樣東西一定不存在或者可能存在”。
如果我們平時常用的List,set,map ,tree 來實現同樣的效果,set和map都是採用map的數據結構,時間複雜度是O1級別。但是map 需要保存所有存在的數據,當數據量非常大的時候,消耗的內存是非常大的。布隆過濾器可以極大減小這種內存消耗,但同樣會造成部分不存在的數據,誤判爲存在。可以通過底層維護的bit數組的長度,來調整數據的誤判率。
布隆過濾器是一個 bit 向量或者說 bit 數組:
當我們輸入 “線性代數” ,通過計算3個不同的hash計算公式,將值映射到1,3,5這個幾個數組上面去,當我們輸入 “線性代數”這 個值,映射到1,3,5上面的時候,發現這幾個位置都被映射過了,就可以判斷 線性代數 代數可能存在。
當我們再輸入 “高等數學” ,通過計算3個不同的hash計算公式,將值映射到1,8,9這個幾個數組上面去,這個時候 1,3,5,8,9這幾個位置都有被映射過了。
如果我們判斷 “概率論” 是否存在的時候,將值進行三種hash運算映射到1,2,3 。發現2沒有被映射過,所以可以判斷 “概率論” 不存在。 但是也有可能當輸入“線性代數” 到映射到1,5,8這三個位置都被映射過了,此時發生誤判,認爲 “線性代數” 可能存在。
利用布隆過濾器減少磁盤 IO 或者網絡請求,因爲一旦一個值必定不存在的話,我們可以不用進行後續昂貴的查詢請求。一般運用場景防止緩存穿透,對垃圾郵件、短信的過濾,推薦非重複消息。
布隆過濾器中的google過濾器相對於redis布隆過濾器有以下的缺點
基於JVM內存的一種布隆過濾器
重啓即失效
本地內存無法用在分佈式場景
不支持大數據量存儲
redis布隆 也有相對於google過濾器,需要依賴redis,性能稍差的缺點。
redis布隆過濾器的安裝
其中添加過濾器依賴於redis單機部署redis集羣(4.0.14版本)
1.下載redisbloom插件(redis官網下載即可)
https://github.com/RedisLabsModules/redisbloom/
這裏使用v1.1.1版本
//新建目錄
mkdir redisbloom
cd redisbloom
//下載redis 插件
wget https://github.com/RedisLabsModules/rebloom/archive/v1.1.1.tar.gz
//解壓文件
tar -zxvf v1.1.1.tar.gz
//進入解壓後的目錄
cd RedisBloom-1.1.1/
//編譯
make
//進入集羣目錄,添加redis布隆過濾器的插件
cd /usr/local/cluster/7001/redis-4.0.14
vi redis.conf
cd /usr/local/cluster/7002/redis-4.0.14
vi redis.conf
.......
在redis中找到
################################## MODULES #####################################
# Load modules at startup. If the server is not able to load modules
# it will abort. It is possible to use multiple loadmodule directives.
#
# loadmodule /path/to/my_module.so
# loadmodule /path/to/other_module.so
#添加 布隆過濾插件
loadmodule /usr/local/cluster/redisbloom/RedisBloom-1.1.1/rebloom.so
重啓redis
./stop.sh
./start.sh
單機版直接啓動
./redis-server ../redis.conf
//進入到redis 7000節點
./redis-cli -p 7000 -c
//添加過濾器值
192.168.25.128:7001> bf.add calvinBloom 111
(integer) 1
192.168.25.128:7001> bf.add calvinBloom 222
(integer) 1
192.168.25.128:7001> bf.add calvinBloom 333
(integer) 1
192.168.25.128:7001> bf.exists calvinBloom 111
(integer) 1
192.168.25.128:7001> bf.exists calvinBloom 222
(integer) 1
192.168.25.128:7001> bf.exists calvinBloom 333
三、布隆過濾器的使用
1、引入依賴
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>com.redislabs</groupId>
<artifactId>jrebloom</artifactId>
<version>1.2.0</version>
</dependency>
2、加入redis.properties配置文件
#客戶端超時時間單位是毫秒 默認是2000
redis.timeout=10000
#最大空閒數
redis.maxIdle=300
#連接池的最大數據庫連接數。設爲0表示無限制,如果是jedis 2.4以後用redis.maxTotal
#redis.maxActive=600
#控制一個pool可分配多少個jedis實例,用來替換上面的redis.maxActive,如果是jedis 2.4以後用該屬性
redis.maxTotal=2000
#最大建立連接等待時間。如果超過此時間將接到異常。設爲-1表示無限制。
redis.maxWaitMillis=1000
redis.nodes=192.168.25.128:7000,192.168.25.128:7001,192.168.25.128:7002,192.168.25.128:7003,192.168.25.128:7004,192.168.25.128:7005,192.168.25.128:7006,192.168.25.128:7007
3、引入redis的配置文件
@Configuration
@PropertySource("classpath:conf/redis.properties")
public class RedisConfig {
@Value("${redis.maxIdle}")
private Integer maxIdle;
@Value("${redis.timeout}")
private Integer timeout;
@Value("${redis.maxTotal}")
private Integer maxTotal;
@Value("${redis.maxWaitMillis}")
private Integer maxWaitMillis;
@Value("${redis.nodes}")
private String clusterNodes;
/**
* jedis的正常創建
* @return
*/
@Bean
public JedisCluster getJedisCluster(){
String[] cNodes = clusterNodes.split(",");
HashSet<HostAndPort> nodes = new HashSet<>();
//分割集羣節點
for (String node : cNodes) {
String[] hp = node.split(":");
nodes.add(new HostAndPort(hp[0], Integer.parseInt(hp[1])));
}
JedisPoolConfig jedisPoolConfig=new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(maxIdle);
jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
jedisPoolConfig.setMaxTotal(maxTotal);
//創建集羣對象
JedisCluster jedisCluster = new JedisCluster(nodes, timeout, jedisPoolConfig);
return jedisCluster;
}
/**
* bloom過濾器的創建
* @return
*/
@Bean
public ClusterClient initClusterClient() {
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
// 最大連接數
config.setMaxTotal(300);
// 池中保留的最大空閒連接數
config.setMaxIdle(100);
// 池中保留的最小空閒連接數
config.setMinIdle(100);
// 最大等待時間
config.setMaxWaitMillis(5 * 1000);
config.setTestWhileIdle(true);
config.setTestOnBorrow(true);
String[] cNodes = clusterNodes.split(",");
HashSet<HostAndPort> nodes = new HashSet<>();
//分割集羣節點
for (String node : cNodes) {
String[] hp = node.split(":");
nodes.add(new HostAndPort(hp[0], Integer.parseInt(hp[1])));
}
ClusterClient clusterClient = new ClusterClient(nodes, 300 * 1000, 300 * 1000, 10, config);// 創建REDIS集羣
return clusterClient;
}
}
4、創建BloomJedisService 接口
public interface BloomJedisService {
public boolean createFilter(final String name, final long initCapacity, final double errorRate);
public boolean[] addMulti(final String name, final byte[]... values);
public boolean[] addMulti(final String name, final String... values);
public boolean add(final String name, final String value);
public boolean add(final String name, final byte[] value);
public boolean exists(final String name, final String value);
public boolean exists(final String name, final byte[] value);
public boolean delete(final String name) ;
public boolean[] existsMulti(final String name, final byte[] value);
public boolean[] existsMulti(final String name, final String... values);
public long expire(final String key, final int seconds);
public long expireAt(final String key, final long unixTime);
public long ttl(final String key) ;
/**
* key是否存在
*
* @param key
* @return
*/
public boolean exists(final String key);
}
5、創建BloomJedisServiceImpl 的具體實現
@Service
public class BloomJedisServiceImpl implements BloomJedisService {
public static String prefix = "1_BF_";
@Autowired
private ClusterClient clusterClient;
@Override
public boolean createFilter(final String name, final long initCapacity, final double errorRate) {
return clusterClient.createFilter(prefix + name, initCapacity, errorRate);
}
@Override
public boolean[] addMulti(final String name, final byte[]... values) {
return clusterClient.addMulti(prefix + name, values);
}
@Override
public boolean[] addMulti(final String name, final String... values) {
return clusterClient.addMulti(prefix + name, values);
}
@Override
public boolean add(final String name, final String value) {
return clusterClient.add(prefix + name, value);
}
@Override
public boolean add(final String name, final byte[] value) {
return clusterClient.add(prefix + name, value);
}
@Override
public boolean exists(final String name, final String value) {
return !clusterClient.add(prefix + name, value);
}
@Override
public boolean exists(final String name, final byte[] value) {
return clusterClient.add(prefix + name, value);
}
@Override
public boolean delete(final String name) {
return clusterClient.delete(prefix + name);
}
@Override
public boolean[] existsMulti(final String name, final byte[] value) {
return clusterClient.existsMulti(prefix + name, value);
}
@Override
public boolean[] existsMulti(final String name, final String... values) {
return clusterClient.existsMulti(prefix + name, values);
}
@Override
public long expire(final String key, final int seconds) {
return clusterClient.expire(prefix + key, seconds);
}
@Override
public long expireAt(final String key, final long unixTime) {
return clusterClient.expireAt(prefix + key, unixTime);
}
@Override
public long ttl(final String key) {
return clusterClient.ttl(prefix + key);
}
/**
* key是否存在
*
* @param key
* @return
*/
@Override
public boolean exists(final String key) {
return clusterClient.exists(prefix + key);
}
}
6、測試功能
private static final String testFilter = "test_filter2";
@Test
public void testBloom(){
//創建過濾器(,這裏爲了測試比較明顯,將概率設置大一點)
bloomJedisService.createFilter(testFilter, 50, 0.01);
for (int i = 0; i < 1000; i++) {
bloomJedisService.add(testFilter, ""+i);
}
int j=0;
for (int i = 1001; i < 2000; i++) {
boolean exists = bloomJedisService.exists(testFilter, "" + i);
if(exists){
System.out.println("有誤判"+(++j));
}
}
}
測試結果 1000/19=0.02 ,基本能接受
有誤判1
有誤判2
有誤判3
有誤判4
有誤判5
有誤判6
有誤判7
有誤判8
有誤判9
有誤判10
有誤判11
有誤判12
有誤判13
有誤判14
有誤判15
有誤判16
有誤判17
有誤判18
有誤判19