bloom过滤器它是指通过定义一定长度的数组,这个数组所有下标存储的初始值为0,然后将要存储的key值通过hash的算法获取到它的hash值,然后把这个计算出来的hash值作为数组的下标,修改该下标中的存储的值为1,表示这个key已经存储到数组中了,再下一次操作中当我们在不知道这个key是否已经存储的情况下,我们只需要通过前面的相同hash算法计算出hash值,然后通过该hash值作为下标去数组中取值,取出的值如果为0则表示不存在,可以进行新增,如果取出的值为1,则表示已经存在,不需要进行新增的操作了,这个就是bloom过滤器的基本原理。
通过上面的bloom过滤器的原理我们也不难看出通过hash算法来计算值肯定会出现不同的key有相同的hash值的情况,我们称为hash碰撞,因此为了减少因hash碰撞造成的误判率,我们可以通过下面2种方式来最大化的减少相同hash值造成的误判率的情况:
1.提高bloom过滤器的数组长度,因此需要更多的内存来提供。
2.提供多个hash算法,多次进行校验,只要有1个hash算法获取到的下标处值为0就说明该key值不存在。
也因此通过上面我们也可以直接了解到bloom过滤器对key值的存在会存在一定的误判,但是对key值不存在是一定会判断出来的。同时使用bloom的另一个好处就是可以节省空间,因为它只需要存储0和1这样的bit值就可以了,不用存储很长的字符串来表示一个key的存在,因此节省了大量的空间。
在Redis中实现了Redisbloom过滤器,它为我们提供了bloom的实现,Redisbloom是一个插件,因此需要在redis4.0.0+以上才可以支持,可以通过下面方式获取过滤器
wget https://github.com/RedisBloom/RedisBloom/archive/v2.0.0.tar.gz
下载完成后解压到指定目录,进入到解压得到的Redisbloom文件夹,通过make编译完成后可以得到一个redisbloom.so文件,然后在redis.conf文件的module部分配置如下的插件配置
#####################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 /usr/local/redis-5.0.5/modlues/RedisBloom-2.0.0/redisbloom.so
按照正常步骤启动Redis后我们通过下面方式验证bloom过滤器
127.0.0.1:6379> BF.ADD bloom mark
1) (integer) 1
127.0.0.1:6379> BF.ADD bloom redis
1) (integer) 1
127.0.0.1:6379> BF.EXISTS bloom mark
(integer) 1
127.0.0.1:6379> BF.EXISTS bloom redis
(integer) 1
127.0.0.1:6379> BF.EXISTS bloom nonexist
(integer) 0
127.0.0.1:6379> BF.EXISTS bloom que?
(integer) 0
127.0.0.1:6379>
127.0.0.1:6379> BF.MADD bloom elem1 elem2 elem3
1) (integer) 1
2) (integer) 1
3) (integer) 1
127.0.0.1:6379> BF.MEXISTS bloom elem1 elem2 elem3
1) (integer) 1
2) (integer) 1
3) (integer) 1
在Java代码中我们可以通过maven的方式引入Redis的bloom客户端
<!-- RedisBloomFilter client -->
<dependency>
<groupId>com.redislabs</groupId>
<artifactId>jrebloom</artifactId>
<version>1.2.0</version>
</dependency>
初始化bloom过滤器(以下步骤可以在https://github.com/RedisBloom/JRedisBloom看到)
import io.rebloom.client.Client
Client client = new Client("localhost", 6378);
在bloom过滤器添加1条记录
client.add("simpleBloom", "Mark");
// Does "Mark" now exist?
client.exists("simpleBloom", "Mark"); // true
client.exists("simpleBloom", "Farnsworth"); // False
添加bloom过滤器多条记录
client.addMulti("simpleBloom", "foo", "bar", "baz", "bat", "bag");
// Check if they exist:
boolean[] rv = client.existsMulti("simpleBloom", "foo", "bar", "baz", "bat", "mark", "nonexist");
定制一个大小为10000,误判率为0.0001的bloom过滤器
client.createFilter("specialBloom", 10000, 0.0001);
client.add("specialBloom", "foo");
初始化一个集群客户端
Set<HostAndPort> jedisClusterNodes = new HashSet<>();
jedisClusterNodes.add(new HostAndPort("localhost", 7000));
ClusterClient cclient = new ClusterClient(jedisClusterNodes);
验证下集群客户端
cclient.add("simpleBloom", "Mark");
// Does "Mark" now exist?
cclient.exists("simpleBloom", "Mark"); // true
cclient.exists("simpleBloom", "Farnsworth"); // False
需要注意的情况是bloom是不支持redis sentinel哨兵集群的,它只支持单机或者cluster分片集群。