RedisBloom使用

安装RedisBloom模块,从git获取对应的原码,make生成.so文件,挂载.so文件,启动redis

powershell 复制代码
docker run --name test-redis -v /iothub/test-redis/data:/data -v /iothub/test-redis/modules:/modules -p 6378:6379 -d redis:4.0.10 redis-server --requirepass jimi@123 --appendonly yes --loadmodule /modules/redismodule.so
java 复制代码
package com.jimi.rtvos.helper;

import com.jimi.rtvos.consts.Constants;
import com.khan.utils.JacksonUtils;
import com.khan.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import redis.clients.jedis.JedisPool;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;

/**
 * Description:
 * <p>
 * Redis布隆过滤器
 * </p>
 * <p>Redis 布隆过滤器需要 先安装RedisBloom模块</p>
 * <p>启动服务,需要先创建成功对应的Bloom过滤器,防止使用功能异常</p>
 *
 * @Author: leo.xiong
 * @CreateDate: 2025/8/7 13:52
 * @Email: xionglang@xxxx.com
 * @Since:
 */
@Component
public class RedisBloomHelper {
    private static final Logger LOGGER = LoggerFactory.getLogger(RedisBloomHelper.class);
    private static final String OK = "OK";
    private static final Long SUCCESS = 1L;
    @Resource
    protected JedisPool jedisPool;

    /**
     * 初始化布隆过滤器,初始化失败,服务启动失败,需要处理异常
     */
    @PostConstruct
    public void initBloom() {
        try {
            boolean creationResult = create(Constants.DEVICE_IMEI_BLOOM_FILTER, "0.00000001", "5000000", 2);
            if (!creationResult) {
                throw new RuntimeException("Failed to initialize bloom filter.");
            }
            LOGGER.info("Successfully initialized bloom filter: {}", Constants.DEVICE_IMEI_BLOOM_FILTER);
        } catch (Exception e) {
            LOGGER.error("Error occurred during initialization of bloom filter: ", e);
            throw new RuntimeException("Initialization failed due to an error.", e);
        }
    }

    /**
     * 创建一个新的布隆过滤器
     *
     * @param filterName        布隆过滤器名称
     * @param falsePositiveRate 误报率,小于1,值越大,误报率越高,千万分之一误报率0.00000001
     * @param capacity          容量,容量如果小于写入数据,并且不自增,误报率会加大
     * @param autoCapacity      容量是否自增长,自增长的值,如果是1,就是不会自增长,可以设置为2
     */
    private boolean create(String filterName, String falsePositiveRate, String capacity, Integer autoCapacity) {
        if (StringUtils.isEmpty(filterName) || StringUtils.isEmpty(falsePositiveRate) || StringUtils.isEmpty(capacity)) {
            LOGGER.error("The base configuration for creating a bloom filter is empty filterName:{} falsePositiveRate:{} capacity:{}", filterName, falsePositiveRate, capacity);
            return false;
        }
        try {
            if (isBloomFilterExists(filterName)) {
                // 布隆过滤器已存在,无需再次创建
                return true;
            }
            String result;
            if (autoCapacity == null || autoCapacity <= 1) {
                result = jedisPool.getResource().eval(
                        "return redis.call('BF.RESERVE', KEYS[1], ARGV[1], ARGV[2])",
                        1, filterName, falsePositiveRate, capacity).toString();
            } else {
                result = jedisPool.getResource().eval(
                        "return redis.call('BF.RESERVE', KEYS[1], ARGV[1], ARGV[2], 'EXPANSION', ARGV[3])",
                        1, filterName, falsePositiveRate, capacity, autoCapacity.toString()).toString();
            }
            LOGGER.info("Create bloom result:{} filterName:{} falsePositiveRate:{} capacity:{} autoCapacity:{}", result, filterName, falsePositiveRate, capacity, autoCapacity);
            return OK.equals(result);
        } catch (Exception e) {
            LOGGER.error("Error during bloom filter creation for filter name: {}", filterName, e);
            return false;
        }
    }

    private boolean isBloomFilterExists(String filterName) {
        Object value = null;
        try {
            value = jedisPool.getResource().eval(
                    "return redis.call('BF.INFO', KEYS[1])",
                    1,
                    filterName
            );
            // 如果返回值为空或者是一个空的哈希表,说明布隆过滤器不存在
            if (value == null) {
                return false;
            }
            LOGGER.info("Bloom filter already exists value:{}", JacksonUtils.toJson(value));
            // 返回值不为空且不是空哈希表,说明布隆过滤器存在
            return true;
        } catch (Exception e) {
            // 处理其他可能的异常
            LOGGER.info("checking bloom filter not exist");
            return false;
        }
    }

    /**
     * 向布隆过滤器中添加元素
     *
     * @param filterName
     * @param value
     */
    public boolean set(String filterName, String value) {
        Long result = execute("BF.ADD", filterName, value);
        //一般1是成功,0是已存在
        LOGGER.info("Write bloom filter result:{} filterName:{} value:{}", result, filterName, value);
        return result != null;
    }

    /**
     * 批量写入
     *
     * @param filterName
     * @param imeiList
     * @return
     */
    public List<Long> batchSet(String filterName, List<String> imeiList) {
        StringBuilder luaScript = new StringBuilder();
        luaScript.append("local result = {}\n");
        luaScript.append("for i = 1, #ARGV do\n");
        luaScript.append("    table.insert(result, redis.call('BF.ADD', KEYS[1], ARGV[i]))\n");
        luaScript.append("end\n");
        luaScript.append("return result\n");
        List<Long> resultList = (List<Long>) jedisPool.getResource().eval(luaScript.toString(), Arrays.asList(filterName), imeiList);
        LOGGER.info("Batch write bloom filter result:{} filterName:{} value:{}", resultList, filterName, imeiList);
        return resultList;
    }

    /**
     * 检查元素是否可能存在于布隆过滤器中
     *
     * @return
     */
    public boolean exist(String filterName, String value) {
        Long result = execute("BF.EXISTS", filterName, value);
        //一般1是成功,0是已存在
        LOGGER.info("exist bloom filter result:{} filterName:{} value:{}", result, filterName, value);
        return SUCCESS.equals(result);
    }

    private Long execute(String command, String filterName, String value) {
        if (StringUtils.isEmpty(filterName) || StringUtils.isEmpty(value)) {
            LOGGER.error("Bloom filter parameter is empty filterName:{} value:{}", filterName, value);
            return null;
        }
        return (Long) jedisPool.getResource().eval(
                "return redis.call('" + command + "', KEYS[1], ARGV[1])",
                1, filterName, value
        );
    }
}
相关推荐
振鹏Dong1 小时前
微服务架构及常见微服务技术栈
java·后端
丶小鱼丶1 小时前
二叉树算法之【中序遍历】
java·算法
摇滚侠2 小时前
Oracle 关闭 impdp任务
java
苇柠3 小时前
Spring框架基础(1)
java·后端·spring
yics.3 小时前
数据结构——栈和队列
java·数据结构
架构师沉默3 小时前
我用一个 Postgres 实现一整套后端架构!
java·spring boot·程序人生·架构·tdd
xiucai_cs3 小时前
布隆过滤器原理与Spring Boot实战
java·spring boot·后端·布隆过滤器
向阳花自开4 小时前
Spring Boot 常用注解速查表
java·spring boot·后端
huan_19934 小时前
通过docker构建一个java镜像
java·docker