服务器环境部署:Redis布隆过滤器使用

本文详细介绍了如何在Redis集群中安装和使用布隆过滤器插件,包括下载、编译、配置及测试过程,并提供了Java客户端的示例代码。

老早就想在项目中用起来这个优秀的东西。只是成熟的项目又有很多私有客户部署,redis版本可能存在差异,为避免不必要的版本兼容或迁移,就没有大幅度的在成熟项目上使用。现新项目刚好有相关使用需求,所以理所当然的要用起来,享受算法带来的便利

概述

布隆过滤器是一种算法,一种建立在位图 + hash基础之上的算法。
redis的布隆过滤器只是他的一种实现而已。

布隆过滤器的详细介绍还请去redis官网或者查阅其他资料了解,这里只记录如何安装Redis的布隆过滤器插件

准备

  • redis服务或redis集群。本篇用redis集群来叙述,毕竟现在基本都集群走起
  • redis服务至少4.x版本及以上(4.x版本及以下装起来会很麻烦,需要使用lua脚本,不建议使用,redis现在最稳定的最新版是5.x版本了,4.x版本还不用感觉跟不上时代科技的进步了吧)

检查redis版本:redis-server -v

  • 布隆过滤器插件

安装部署

下载与解压

下载地址(redis官网下载即可):https://github.com/RedisLabsModules/redisbloom/

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
成功后可看到目录下有个.so文件

如下执行流程

[root@sparka base]# cd RedisBloom-1.1.1/
[root@sparka RedisBloom-1.1.1]# ls
contrib Dockerfile docs LICENSE Makefile mkdocs.yml ramp.yml README.md src tests
[root@sparka RedisBloom-1.1.1]# make
cc -Wall -Wno-unused-function -g -ggdb -O2 -fPIC -std=gnu99 -D_GNU_SOURCE -I/data/br/base/RedisBloom-1.1.1 -I/data/br/base/RedisBloom-1.1.1/contrib -c -o /data/br/base/RedisBloom-1.1.1/src/rebloom.o /data/br/base/RedisBloom-1.1.1/src/rebloom.c
cc -Wall -Wno-unused-function -g -ggdb -O2 -fPIC -std=gnu99 -D_GNU_SOURCE -I/data/br/base/RedisBloom-1.1.1 -I/data/br/base/RedisBloom-1.1.1/contrib -c -o /data/br/base/RedisBloom-1.1.1/contrib/MurmurHash2.o /data/br/base/RedisBloom-1.1.1/contrib/MurmurHash2.c
cc -Wall -Wno-unused-function -g -ggdb -O2 -fPIC -std=gnu99 -D_GNU_SOURCE -I/data/br/base/RedisBloom-1.1.1 -I/data/br/base/RedisBloom-1.1.1/contrib -c -o /data/br/base/RedisBloom-1.1.1/src/sb.o /data/br/base/RedisBloom-1.1.1/src/sb.c
cc -Wall -Wno-unused-function -g -ggdb -O2 -fPIC -std=gnu99 -D_GNU_SOURCE -I/data/br/base/RedisBloom-1.1.1 -I/data/br/base/RedisBloom-1.1.1/contrib -c -o /data/br/base/RedisBloom-1.1.1/src/cf.o /data/br/base/RedisBloom-1.1.1/src/cf.c
In file included from /data/br/base/RedisBloom-1.1.1/src/cf.c:6:
/data/br/base/RedisBloom-1.1.1/src/cuckoo.c: In function ‘CuckooFilter_Count’:
/data/br/base/RedisBloom-1.1.1/src/cuckoo.c:157: warning: passing argument 1 of ‘filterCount’ from incompatible pointer type
/data/br/base/RedisBloom-1.1.1/src/cuckoo.c:139: note: expected ‘const uint8_t (*)[2]’ but argument is of type ‘uint8_t (*)[2]’
ld /data/br/base/RedisBloom-1.1.1/src/rebloom.o /data/br/base/RedisBloom-1.1.1/contrib/MurmurHash2.o /data/br/base/RedisBloom-1.1.1/src/sb.o /data/br/base/RedisBloom-1.1.1/src/cf.o -o /data/br/base/RedisBloom-1.1.1/rebloom.so -shared -Bsymbolic -Bsymbolic-functions -lm -lc
[root@sparka RedisBloom-1.1.1]# ls
contrib Dockerfile docs LICENSE Makefile mkdocs.yml ramp.yml README.md rebloom.so src tests

Redis引入该模块

  • (1)在redis.conf配置文件里加入如下引入配置

loadmodule /data/br/base/RedisBloom-1.1.1/rebloom.so

  • (2)redis集群每个配置文件都需要加入这一行
  • (3)添加完配置后重启redis

测试Redis是否成功引入模块

redis布隆过滤器涉及的几个命令如:bf.add、bf.exists等,更多请自行查阅官网资料

192.168.1.103:7001> bf.add mym jd
(integer) 1
192.168.1.103:7001> bf.add mym meituan
(integer) 1
192.168.1.103:7001> bf.add mym meituan
(integer) 0
192.168.1.103:7001> bf.exists mym meituan
(integer) 1
192.168.1.103:7001> bf.exists mym apple
(integer) 0
192.168.1.103:7001> 

java客户端测试(redis集群)

以下代码为真实工程使用,故有点冗长,但是测试起来没毛病。非集群模式更简单了,我这就不贴了,ClusterClient换成Client了而已。
若从低版本jedis升级到3.x版本的依赖,以下代码仍然可以使用,个人就是从低版本升级到高版本,遇到了一些坑。

        <!-- 布隆过滤器依赖:redis-server 4.x以上才支持 -->
        <dependency>
            <groupId>com.redislabs</groupId>
            <artifactId>jrebloom</artifactId>
            <version>1.2.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.1.0</version>
        </dependency>

工具类

public class JedisUtils {
   
    public static String REDIS_CLUSTER = "";

    public static String REDIS_VERSION = "";

    public static String REDIS_PASSWORD = "";

    public static String PREFIX_BF = "1_BF_";

    private static final Lock lock = new ReentrantLock();


    /**
     * redis-server需 4.x版本及以上
     * @return
     */
    public static JedisBloomFilterProxy getBloomFilterRedisProxy() {
        return new JedisBloomFilterProxy(REDIS_CLUSTER, PREFIX_BF, REDIS_PASSWORD);
    }


    /**
     * 概述:布隆过滤器 redis的proxy
     *
     * @author maoyuanming
     * @date 2019-10-28
     * @version v1.0.0
     */
    public static class JedisBloomFilterProxy{

        private static ClusterClient clusterClient = null;

        private String prefix = "";

        public JedisBloomFilterProxy(String cluster, String prefix, String password) {
            if (clusterClient == null) {
                clusterClient = getBloomFilterClusterClient(cluster, password);
            }
            this.prefix = prefix;
        }

        public boolean createFilter(final String name, final long initCapacity, final double errorRate){
            return clusterClient.createFilter(prefix + name, initCapacity, errorRate);
        }

        public boolean[] addMulti(final String name, final byte[]... values){
            return clusterClient.addMulti(prefix + name, values);
        }

        public boolean[] addMulti(final String name, final String... values){
            return clusterClient.addMulti(prefix + name, values);
        }

        public boolean add(final String name, final String value){
            return clusterClient.add(prefix + name, value);
        }

        public boolean add(final String name, final byte[] value) {
            return clusterClient.add(prefix + name, value);
        }

        public boolean exists(final String name, final String value){
            return clusterClient.add(prefix + name, value);
        }

        public boolean exists(final String name, final byte[] value){
            return clusterClient.add(prefix + name, value);
        }

        public boolean delete(final String name){
            return clusterClient.delete(prefix + name);
        }

        public boolean[] existsMulti(final String name, final byte[] value){
            return clusterClient.existsMulti(prefix + name, value);
        }

        public boolean[] existsMulti(final String name, final String... values){
            return clusterClient.existsMulti(prefix + name, values);
        }

        public long expire(final String key, final int seconds){
            return clusterClient.expire(prefix + key, seconds);
        }

        public long expireAt(final String key, final long unixTime){
            return clusterClient.expireAt(prefix + key, unixTime);
        }

        public long ttl(final String key){
            return clusterClient.ttl(prefix + key);
        }

        /**
         * key是否存在
         * @param key
         * @return
         */
        public boolean exists(final String key){
            return clusterClient.exists(prefix + key);
        }

        /**
         * 获取布隆过滤器的redis cluster client
         * @return
         */
        private ClusterClient getBloomFilterClusterClient(String cluster, String password) {
            if (clusterClient == null) {
                initClusterClient(cluster, password);
            }
            return clusterClient;
        }

        /**
         * 初始化
         * @param clusterNode
         * @param password
         * @return
         */
        private ClusterClient initClusterClient(String clusterNode, String password) {
            if (clusterClient == null) {
                log.debug("Create bloom filter client connection pool");
                lock.lock();
                try {
                    if (clusterClient == null) {
                        GenericObjectPoolConfig config = new GenericObjectPoolConfig();
                        config.setMaxTotal(300);    // 最大连接数
                        config.setMaxIdle(100);        // 池中保留的最大空闲连接数
                        config.setMinIdle(100);        // 池中保留的最小空闲连接数
                        config.setMaxWaitMillis(5 * 1000); // 最大等待时间
                        config.setTestWhileIdle(true);
                        config.setTestOnBorrow(true);
                        clusterClient = new ClusterClient(getHostAndPort(clusterNode), 300 * 1000, 300 * 1000, 10, password, config);// 创建REDIS集群
                    }
                } finally {
                    lock.unlock();
                }
            } else {
                log.debug("Use bloom filter client connection pool");
            }
            return clusterClient;
        }

        /**
         * 调用此方法会直接与redis服务端断连。非本身服务关闭时避免调用
         */
        public void close(){
            clusterClient.close();
        }
    }
    
    /**
     * 概述:处理集群连接地址
     *
     * @param clusterNode
     */
    public static Set<HostAndPort> getHostAndPort(String clusterNode) {
        Set<HostAndPort> setList = new HashSet<HostAndPort>();
        if (clusterNode != null) {
            String[] hostArr = ProxyUtils.getSplit(clusterNode, ",");
            if (hostArr == null) {
                return setList;
            }
            String[] hostAdd = null;
            for (int i = 0; i < hostArr.length; i++) {
                hostAdd = ProxyUtils.getSplit(hostArr[i], ":");
                if (hostAdd == null || hostAdd.length != 2) {
                    continue;
                }
                String[] hosts = ProxyUtils.getSplit(hostAdd[0], "/");
                String[] ports = ProxyUtils.getSplit(hostAdd[1], "/");
                String host = hosts[0];
                if (hosts.length == 2) {
                    host = hosts[1];
                    IPMaps.setIPMap(hosts[0], hosts[1]);
                } else {
                    IPMaps.setIPMap(hosts[0], hosts[0]);
                }
                setList.add(new HostAndPort(host, Integer.valueOf(ports[0])));
            }
        }
        return setList;
    }

}

兼容jedis-2.9.0等老版本的工具

package redis.clients.jedis;

import java.util.HashMap;
import java.util.Map;

/**
 * 概述:兼容封装的JedisProxy使用低版本的Jedis的使用。此类在工程内虽未使用,但引用的JedisProxy有使用,在运行时将会被调用
 *
 * 一些封装类有使用,为避免在运行时报错,包名、类名、实现须保持与低版本Jedis中IPMaps一致,勿移动
 * @author maoyuanming
 * @date 2019-10-28
 * @version v1.0.0
 */
public class IPMaps {
    private static Map<String, String> map = new HashMap();

    public IPMaps() {
    }

    public static void setIPMap(String oldIP, String newIP) {
        map.put(oldIP, newIP);
    }

    static String getNewIp(String oldIP) {
        String newIP = (String)map.get(oldIP);
        if (newIP == null || "".equals(newIP)) {
            newIP = oldIP;
        }

        return newIP;
    }
}


测试类


import com.bonree.winsdk.common.common.JedisUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.util.Arrays;

/**
 * 概述:redis 布隆过滤器测试类
 *
 * @author maoyuanming
 * @date 2019-10-28
 * @version v1.0.0
 */
public class RedisBloomFilterTest {

    JedisUtils.JedisBloomFilterProxy filterClient = null;

    String testFilter = "test_filter";

    @Before
    public void setUp() throws Exception {
        JedisUtils.REDIS_CLUSTER = "192.168.1.103:7000,192.168.1.103:7001,192.168.1.103:7002," +
                "192.168.1.103:7003,192.168.1.103:7004,192.168.1.103:7005";
        JedisUtils.REDIS_PASSWORD = "123456";
        filterClient = JedisUtils.getBloomFilterRedisProxy();
    }

    @Test
    public void testBloomFilterCluster(){
        if(!filterClient.exists(testFilter)){
            filterClient.createFilter(testFilter, 100, 0.0000002);
        }
        String testExist = "mym";
        filterClient.add(testFilter, "mym");
        filterClient.add(testFilter, "mmm");
        filterClient.add(testFilter, "myy");
        filterClient.addMulti(testFilter, "bonree", "br", "shenzhen");
        System.out.println("之前添加过再此添加是否添加成功:"+filterClient.add(testFilter, "mmm"));
        boolean mym = filterClient.exists(testFilter, testExist);
        System.out.println(testExist + " 是否存在:" + mym);

        boolean[] abcs = filterClient.existsMulti(testFilter, "abc","br", testExist);
        System.out.println("result:" + Arrays.toString(abcs));
    }

    @After
    public void after(){
        if(filterClient != null){
            try {
                filterClient.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

跑测试方法结果

之前添加过再此添加是否添加成功:false
mym 是否存在:false
result:[false, true, true]
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值