redis工具包开发——redis布隆过滤器实现

布隆过滤器实现其实核心也是模仿guava的布隆过滤器实现,只是操作字节数组的载体改为redis的bitmap结构,在该工具包的布隆过滤器开发里面涉及到比较核心的点有

1.guava里的布隆过滤器理解

2.如何接入spring

3.redis 布隆过滤器实现

先看第一部分

布隆过滤器他能够判断某个对象肯定不存在或者可能存在,核心的思想是,对一个对象进行不同的hash函数处理所得到的值标志到相对应的字节数组中,比如key1 通过3个hash函数解析后,对应到下标为1,3,5,此时把该字节数组的相应的标志置为1,当判断一个新的key是否存在时,通过同样的计算去相应的下标查看是否都为1,如果是的话,就可能存在,为什么可能存在呢?当存入的key越来越多,字节数组里面的位为1也越来越多,布隆过滤器是有一定的误判率的。

了解好基本的概念后,这里贴一下布隆过滤器的一些公式

其中m为布隆过滤器的bitsize,n为预计插入的长度,p为误差率,k为hash函数的个数,guava里面是如何实现的呢?

主要有两个类

BloomFilter 

该类的四个属性

其中bits是自定义的一个类似于bitset的类,操作通过cas完成,然后numHashFunctions是hash函数的个数,funnel类则是用于把对象转换成java基本的数据类型,stragtegy则是hash策略

先看创建的过程,首先是前置校验,然后到计算字节长度,hash函数个数

static <T> BloomFilter<T> create(
      Funnel<? super T> funnel, long expectedInsertions, double fpp, Strategy strategy) {
    checkNotNull(funnel);
    checkArgument(
        expectedInsertions >= 0, "Expected insertions (%s) must be >= 0", expectedInsertions);
    checkArgument(fpp > 0.0, "False positive probability (%s) must be > 0.0", fpp);
    checkArgument(fpp < 1.0, "False positive probability (%s) must be < 1.0", fpp);
    checkNotNull(strategy);

    if (expectedInsertions == 0) {
      expectedInsertions = 1;
    }
    
    long numBits = optimalNumOfBits(expectedInsertions, fpp);
    int numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, numBits);
    try {
      return new BloomFilter<T>(new LockFreeBitArray(numBits), numHashFunctions, funnel, strategy);
    } catch (IllegalArgumentException e) {
      throw new IllegalArgumentException("Could not create BloomFilter of " + numBits + " bits", e);
    }
  }

计算字节数组长度,其实就是上面贴的公式的第一个条实现,入参为预计插入长度和错误率

static long optimalNumOfBits(long n, double p) {
    if (p == 0) {
      p = Double.MIN_VALUE;
    }
    return (long) (-n * Math.log(p) / (Math.log(2) * Math.log(2)));
  }

计算hash函数个数

 static int optimalNumOfHashFunctions(long n, long m) {
    // (m / n) * log(2), but avoid truncation due to division!
    return Math.max(1, (int) Math.round((double) m / n * Math.log(2)));
  }

然后到创建自己的一个字节数组底层是AtomicLongArray,通过cas操作,LongAddable用来统计数量,默认实现为LongAddr,出错后采用继承AtomicLong的自定义类来实现,funnel则是起到一些转换的作用,strategy是put和get的一些方法实现,对于64位和32位有一些细微的差别,这里以64位来演示

put方法通过通过funnel把对象转为对应的字节数组,然后取低八字节作为hash值1,高八字节作为hash值2,通过两者复合的hash做一定的与运算后对字节数组长度取模并设置相应的位

mightContain方法也是同样的方法取出相应的位数并判断


  MURMUR128_MITZ_64() {
    @Override
    public <T> boolean put(
        T object, Funnel<? super T> funnel, int numHashFunctions, LockFreeBitArray bits) {
      long bitSize = bits.bitSize();
      byte[] bytes = Hashing.murmur3_128().hashObject(object, funnel).getBytesInternal();
      long hash1 = lowerEight(bytes);
      long hash2 = upperEight(bytes);

      boolean bitsChanged = false;
      long combinedHash = hash1;
      for (int i = 0; i < numHashFunctions; i++) {
        // Make the combined hash positive and indexable
        bitsChanged |= bits.set((combinedHash & Long.MAX_VALUE) % bitSize);
        combinedHash += hash2;
      }
      return bitsChanged;
    }

    @Override
    public <T> boolean mightContain(
        T object, Funnel<? super T> funnel, int numHashFunctions, LockFreeBitArray bits) {
      long bitSize = bits.bitSize();
      byte[] bytes = Hashing.murmur3_128().hashObject(object, funnel).getBytesInternal();
      long hash1 = lowerEight(bytes);
      long hash2 = upperEight(bytes);

      long combinedHash = hash1;
      for (int i = 0; i < numHashFunctions; i++) {
        // Make the combined hash positive and indexable
        if (!bits.get((combinedHash & Long.MAX_VALUE) % bitSize)) {
          return false;
        }
        combinedHash += hash2;
      }
      return true;
    }

    private /* static */ long lowerEight(byte[] bytes) {
      return Longs.fromBytes(
          bytes[7], bytes[6], bytes[5], bytes[4], bytes[3], bytes[2], bytes[1], bytes[0]);
    }

    private /* static */ long upperEight(byte[] bytes) {
      return Longs.fromBytes(
          bytes[15], bytes[14], bytes[13], bytes[12], bytes[11], bytes[10], bytes[9], bytes[8]);
    }
  };

而这里主要把bitArray这个换成操作redis的bitArray,主要的命令有bitset,bitget,bitcount,具体的实现将在文章下面统一介绍讲解

如何接入spring?主要是用到了@Import注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({RedisBloomFilterRegistar.class, RedisLimiterRegistar.class})
public @interface EnableRedisAux {
    String[] bloomFilterPath() default "";
    boolean enableLimit() default false;
    boolean transaction() default false;

}

spring会解析并加载@Import里的类,然后看一下布隆过滤器的注册类

public class RedisBloomFilterRegistar implements ImportBeanDefinitionRegistrar {
    public static Map<String, Map<String, BloomFilterProperty>> bloomFilterFieldMap;
    public static boolean transaction;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        Map<String, Object> attributes = importingClassMetadata
                .getAnnotationAttributes(EnableRedisAux.class.getCanonicalName());
        transaction = (boolean) attributes.get("transaction");
        String[] scanPaths = (String[]) attributes.get(BloomFilterConsts.SCAPATH);
        if (!scanPaths[0].trim().equals("")) {
            bloomFilterFieldMap = new HashMap<>();
            ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(
                    false);
            scanner.addIncludeFilter(new AnnotationTypeFilter(BloomFilterPrefix.class));
            for (String basePath : scanPaths) {
                scanner.findCandidateComponents(basePath).forEach(e -> {
                    Class<?> clazz = null;
                    try {
                        clazz = Class.forName(e.getBeanClassName());
                    } catch (ClassNotFoundException e1) {
                        e1.printStackTrace();
                    }
                    if (clazz.isAnnotationPresent(BloomFilterPrefix.class)) {
                        Map<String, BloomFilterProperty> map = new HashMap();
                        String prefix = clazz.getAnnotation(BloomFilterPrefix.class).prefix();
                        ReflectionUtils.doWithFields(clazz, field -> {
                            field.setAccessible(Boolean.TRUE);
                            if (field.isAnnotationPresent(BloomFilterProperty.class)) {
                                String key = field.getName();
                                String keyName = CommonUtil.getKeyName(prefix, key);
                                map.put(keyName, field.getAnnotation(BloomFilterProperty.class));
                            }
                        });
                        bloomFilterFieldMap.put(prefix, map);
                    }
                });
            }
            bloomFilterFieldMap = Collections.unmodifiableMap(bloomFilterFieldMap);
        } else {
            System.err.println("=============redisbloomfilter未设置扫描路径,不支持lambda调用=============");
        }

        //指定扫描自己写的符合默认扫描注解的组件
        ClassPathBeanDefinitionScanner scanConfigure =
                new ClassPathBeanDefinitionScanner(registry, true);
        scanConfigure.scan(BloomFilterConsts.PATH);
    }

}

这个注册类第一个部分逻辑是存储一下支持lambda调用的类的属性信息

扫描配置的路径下有BloomFilterPrefix注解的类,然后把有BloomFilterProperty注解的属性信息存入一个map里面,该配置类的信息为<键前缀,<属性名,BloomfilterProperty信息>

解析lambda时,GetBloomFilterField这个类负责从lambda表达式里面获取对应的字段名称,再从上述的map里面获取到对应的信息封装成一个对象用于接下来的调用

public class GetBloomFilterField {

    private static Map<Class, SerializedLambda> map = new ConcurrentHashMap();
    private static Map<String, BloomFilterInfo> bloomFilterInfoMap = new ConcurrentHashMap();

    public static <T> BloomFilterInfo resolveFieldName(SFunction<T> sFunction) {
        SerializedLambda lambda = map.get(sFunction.getClass());
        if (lambda == null) {
            try {
                Method writeReplace = sFunction.getClass().getDeclaredMethod(BloomFilterConstants.LAMBDAMETHODNAME);
                writeReplace.setAccessible(true);
                lambda = (SerializedLambda) writeReplace.invoke(sFunction);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
            map.put(sFunction.getClass(), lambda);
        }
        String fieldName = getFieldName(lambda);
        String capturingClass = lambda.getImplClass();
        String infoKey = CommonUtil.getKeyName(fieldName, capturingClass);
        BloomFilterInfo res = bloomFilterInfoMap.get(infoKey);
        if (res == null) {
            capturingClass = capturingClass.replace("/", ".");
            Class<?> aClass = null;
            try {
                aClass = Class.forName(capturingClass);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            if (aClass.isAnnotationPresent(BloomFilterPrefix.class)) {
                BloomFilterPrefix annotation = aClass.getAnnotation(BloomFilterPrefix.class);
                if (RedisBloomFilterRegistar.bloomFilterFieldMap != null) {
                    Map<String, BloomFilterProperty> map = RedisBloomFilterRegistar.bloomFilterFieldMap.get(annotation.prefix());
                    BloomFilterProperty field = map.get(CommonUtil.getKeyName(annotation.prefix(), fieldName));
                    res = new BloomFilterInfo(annotation.prefix().trim().equals("") ? aClass.getCanonicalName() : annotation.prefix(),
                            field.key().trim().equals("") ? fieldName : field.key(),
                            field.exceptionInsert(),
                            field.fpp(),
                            field.timeout(),
                            field.timeUnit(),

                    field.local()
                    );
                }
            }
            if (res != null) {
                bloomFilterInfoMap.put(infoKey, res);
            }

        }
        return res;

    }

    public static class BloomFilterInfo {
        private final String keyPrefix;
        private final String keyName;
        private final long exceptionInsert;
        private final double fpp;
        private final long timeout;
        private final TimeUnit timeUnit;
        private final boolean local;

        public BloomFilterInfo(String keyPrefix, String keyName, Long exceptionInsert, double fpp, Long timeout, TimeUnit timeUnit, boolean local) {
            this.keyPrefix = keyPrefix;
            this.keyName = keyName;
            this.exceptionInsert = exceptionInsert;
            this.fpp = fpp;
            this.timeout = timeout;
            this.timeUnit = timeUnit;
            this.local=local;

        }

        public String getKeyPrefix() {
            return keyPrefix;
        }

        public String getKeyName() {
            return keyName;
        }

        public long getExceptionInsert() {
            return exceptionInsert;
        }

        public double getFpp() {
            return fpp;
        }

        public long getTimeout() {
            return timeout;
        }

        public TimeUnit getTimeUnit() {
            return timeUnit;
        }

        public boolean isLocal(){return local;}
    }


    private static String getFieldName(SerializedLambda lambda) {
        String getMethodName = lambda.getImplMethodName();
        if (getMethodName.startsWith(BloomFilterConstants.GET)) {
            getMethodName = getMethodName.substring(3);
        } else if (getMethodName.startsWith(BloomFilterConstants.IS)) {
            getMethodName = getMethodName.substring(2);
        } else {
            throw new RedisAuxException("没有相应的属性方法");
        }
        // 小写第一个字母
        return Character.isLowerCase(getMethodName.charAt(0)) ? getMethodName : Character.toLowerCase(getMethodName.charAt(0)) + getMethodName.substring(1);
    }
}

第二部分,扫描本应用的某些路径,这样就把对应的资源加载

主要加载有两个类

第一个是内部的redisTemplate,这里就不多说

第二个是布隆过滤器的配置类,@ConditionalOnClass,@AutoConfigureAfter都是一些前置工作,规定该配置类在什么样的情况下才加载,然后就加载相应的类

@Configuration
@ConditionalOnClass(RedisBloomFilter.class)
@AutoConfigureAfter(RedisAutoConfiguration.class)
@SuppressWarnings("unchecked")
class RedisBloomFilterAutoConfiguration {

    @Autowired
    @Qualifier(BloomFilterConstants.INNERTEMPLATE)
    private RedisTemplate redisTemplate;



    @Bean
    @ConditionalOnMissingBean(RedisBloomFilter.class)
    public RedisBloomFilter redisBloomFilter() {
        Properties properties = System.getProperties();
        String property = properties.getProperty("sun.arch.data.model");
        Strategy strategy = RedisBloomFilterStrategies.getStrategy(property);
        if (strategy == null) {
            strategy = RedisBloomFilterStrategies.MURMUR128_MITZ_32.getStrategy();
        }
        Map<Class, RedisBloomFilterItem> map = new HashMap(FunnelEnum.values().length);
        for (FunnelEnum funnelEnum : FunnelEnum.values()) {
            RedisBloomFilterItem item = RedisBloomFilterItem.create(funnelEnum.getFunnel(), strategy, redisBitArrayFactory());
            checkTask().addListener(item);
            map.put(funnelEnum.getCode(), item);
        }
        return new RedisBloomFilter(map);
    }

    @Bean(name = "resetBitScript")
    public DefaultRedisScript resetBitScript() {
        DefaultRedisScript<Void> script = new DefaultRedisScript<Void>();
        script.setScriptText(resetBitScriptStr());
        return script;
    }

    @Bean(name = "setBitScript")
    public DefaultRedisScript setBitScript() {
        DefaultRedisScript script = new DefaultRedisScript();
        script.setScriptText(setBitScripStr());
        return script;
    }

    @Bean(name = "getBitScript")
    public DefaultRedisScript getBitScript() {
        DefaultRedisScript<List> script = new DefaultRedisScript();
        script.setScriptText(getBitScriptStr());
        script.setResultType(List.class);
        return script;
    }


    @Bean
    public BitArrayOperator redisBitArrayFactory() {

        return new BitArrayOperator(
                setBitScript(),
                getBitScript(),
                resetBitScript(),
                redisTemplate,
                checkTask()
        );
    }

    @Bean
    public CheckTask checkTask() {
        return new CheckTask();
    }


    private String setBitScripStr() {
        StringBuilder builder = new StringBuilder();
        builder.append("local kL = table.getn(KEYS)\n")
                .append("local aL = table.getn(ARGV)\n")
                .append("for i = 1, kL\n")
                .append("do\n")
                .append("    for k = 1, aL\n")
                .append("    do redis.call('setbit', KEYS[i], tonumber(ARGV[k]), 1)\n")
                .append("    end\nend");
        return builder.toString();
    }

    private String getBitScriptStr() {
        StringBuilder builder = new StringBuilder();
        builder.append("local array = {}\n").append("local aL = table.getn(ARGV) - 1\n")
                .append("local kL = table.getn(KEYS)\n").append("local bitL = ARGV[1]\n")
                .append("local elementSize = aL / bitL\n").append("for index = 1, elementSize\n")
                .append("do local notAdd = true\n").append("    for i = (index - 1) * bitL + 2, index * bitL + 1\n")
                .append("    do\n").append("        local k = 1\n").append("        while (notAdd and k <= kL)\n")
                .append("        do\n").append("            if redis.call('getbit', KEYS[k], ARGV[i]) == 0 then\n")
                .append("                array[index] = 0\n").append("                notAdd = false\n")
                .append("            else\n").append("                k = k + 1\n")
                .append("            end\n").append("        end\n").append("    end\n")
                .append("    if notAdd then array[index] = 1 end\n").append("end\nreturn array");
        return builder.toString();
    }

    private String resetBitScriptStr() {
        StringBuilder builder = new StringBuilder();
        builder.append("local key = KEYS[1]\n")
                .append("local start, last = 0, tonumber(ARGV[1])\n")
                .append("local b = '\\0'\n")
                .append("local mstart, mlast = start % 8, (last + 1) % 8\n")
                .append("if mlast > 0 then\n")
                .append("    local t = math.max(last - mlast + 1, start)\n")
                .append("    for i = t, last do\n")
                .append("        redis.call('SETBIT', key, i, b)\n    end\n    last = t\nend\n")
                .append("if mstart > 0 then\n")
                .append("    local t = math.min(start - mstart + 7, last)\n")
                .append("    for i = start, t do\n")
                .append("        redis.call('SETBIT', key, i, b)\n    end\n    start = t + 1\nend\n")
                .append("local rs, re = start / 8, (last + 1) / 8\nlocal rl = re - rs\nif rl > 0 then\n")
                .append("    redis.call('SETRANGE', key, rs, string.rep(b, rl))\nend\n");
        return builder.toString();
    }


}

这就是和spring接入的方式,主要是通过@Import和自定义扫描路径加载对应的配置类把相关的类给spring托管

3.redis 布隆过滤器实现

目前写的功能有

1.添加、批量添加

2.查询、批量查询

3.重置

4.键过期

这里的设计是这样的

对外提供一个RedisBloomFilter,但里面有一个map,该map存放class和对应的操作类,操作类通过strategy类来确定对应的hash值,然后通过这些hash值操作字节数组。

RedisBloomFilter类

public class RedisBloomFilter {
    private final Map<Class, RedisBloomFilterItem> bloomFilterMap;


    public RedisBloomFilter(Map<Class, RedisBloomFilterItem> bloomFilterMap) {
        this.bloomFilterMap = bloomFilterMap;
    }

    /**
     * 通过解析lambda获取信息
     *
     * @param sFunction
     * @param member
     * @param <T>
     */
    public <T, R> void add(SFunction<T> sFunction, R member) {
        GetBloomFilterField.BloomFilterInfo bloomFilterInfo = check(sFunction);
        add(bloomFilterInfo.getKeyPrefix(),
                bloomFilterInfo.getKeyName(),
                bloomFilterInfo.getExceptionInsert(),
                bloomFilterInfo.getFpp(),
                bloomFilterInfo.getTimeout(),
                bloomFilterInfo.getTimeUnit(),
                bloomFilterInfo.isLocal(),
                member);
    }

    public <R> void add(AddCondition addCondition, R member) {
        InnerInfo condition = addCondition.build();
        addCondition.clear();
        add(condition.getKeyPrefix(),
                condition.getKeyName(),
                condition.getExceptionInsert(),
                condition.getFpp(),
                condition.getTimeout(),
                condition.getTimeUnit(),
                condition.isLocal(),
                member
        );
    }

    private <R> void add(String keyPrefix, String key, long exceptedInsertions, double fpp, long timeout, TimeUnit timeUnit, boolean local,R member) {
        Class clzz = member.getClass();
        Object res = member;
        RedisBloomFilterItem filter = bloomFilterMap.get(clzz);
        String keyName = checkKey(keyPrefix, key);
        if (filter == null) {
            filter = bloomFilterMap.get(Byte.class);
        }
        filter.put(keyName, res, exceptedInsertions, fpp, timeout, timeUnit,local);
    }

    public <T, R> void addAll(SFunction<T> sFunction, List<R> members) {
        GetBloomFilterField.BloomFilterInfo bloomFilterInfo = check(sFunction);
        addAll(bloomFilterInfo.getKeyPrefix(),
                bloomFilterInfo.getKeyName(),
                bloomFilterInfo.getExceptionInsert(),
                bloomFilterInfo.getFpp(),
                bloomFilterInfo.getTimeout(),
                bloomFilterInfo.getTimeUnit(),
                bloomFilterInfo.isLocal(),
                members);
    }

    public <R> void addAll(AddCondition addCondition, List<R> members) {
        InnerInfo innerInfo = addCondition.build();
        addCondition.clear();
        this.addAll(
                innerInfo.getKeyPrefix(),
                innerInfo.getKeyName(),
                innerInfo.getExceptionInsert(),
                innerInfo.getFpp(),
                innerInfo.getTimeout(),
                innerInfo.getTimeUnit(),
                innerInfo.isLocal(),
                members
        );
    }

    private <R> void addAll(String keyPrefix, String key, Long exceptedInsertions, Double fpp, long timeout, TimeUnit timeUnit, boolean local, List<R> members) {
        if (members.isEmpty()) {
            throw new RedisAuxException("参数有误!");
        }
        String keyName = checkKey(keyPrefix, key);
        Class clzz = members.get(0).getClass();
        RedisBloomFilterItem filter = bloomFilterMap.get(clzz);
        List<Object> resList = new ArrayList(members.size());
        for (R member : members) {
            resList.add(member);
        }
        if (filter == null) {
            filter = bloomFilterMap.get(Byte.class);
        }
        filter.putAll(keyName, exceptedInsertions, fpp, resList, timeout, timeUnit,local);
    }

    public <R> boolean mightContain(BaseCondition queryCondition, R member) {
        InnerInfo build = queryCondition.build();
        return mightContain(build.getKeyPrefix(), build.getKeyName(), member);
    }

    public <T, R> boolean mightContain(SFunction<T> sFunction, R member) {
        GetBloomFilterField.BloomFilterInfo bloomFilterInfo = check(sFunction);
        return mightContain(bloomFilterInfo.getKeyPrefix(), bloomFilterInfo.getKeyName(), member);
    }

    private <R> boolean mightContain(String keyPrefix, String key, R member) {
        String keyName = checkKey(keyPrefix, key);
        Class clzz = member.getClass();
        RedisBloomFilterItem filter = bloomFilterMap.get(clzz);
        if (filter == null) {
            filter = bloomFilterMap.get(Byte.class);
        }
        return filter.mightContain(keyName, member);
    }


    public <T, R> List<Boolean> mightContains(SFunction<T> sFunction, List<R> members) {
        GetBloomFilterField.BloomFilterInfo bloomFilterInfo = check(sFunction);
        return mightContains(bloomFilterInfo.getKeyPrefix(), bloomFilterInfo.getKeyName(), members);
    }

    public <R> List<Boolean> mightContains(BaseCondition queryCondition, List<R> members) {
        InnerInfo build = queryCondition.build();
        return mightContains(build.getKeyPrefix(), build.getKeyName(), members);
    }

    private <R> List<Boolean> mightContains(String keyPrefix, String key, List<R> members) {
        if (members.isEmpty()) {
            return null;
        }
        String keyName = checkKey(keyPrefix, key);
        Class clzz = members.get(0).getClass();
        RedisBloomFilterItem filter = bloomFilterMap.get(clzz);
        List<Object> resList = new ArrayList(members.size());
        for (R member : members) {
            resList.add(member);
        }
        if (filter == null) {
            filter = bloomFilterMap.get(Byte.class);
        }
        return filter.mightContains(keyName, resList);
    }


    public <T> void remove(SFunction<T> sFunction) {
        GetBloomFilterField.BloomFilterInfo bloomFilterInfo = check(sFunction);
        remove(bloomFilterInfo.getKeyPrefix(), bloomFilterInfo.getKeyName());

    }

    public void remove(BaseCondition deleteCondition) {
        InnerInfo build = deleteCondition.build();
        remove(build.getKeyPrefix(), build.getKeyName());
    }


    private void remove(String keyPrefix, String key) {
        String keyname = checkKey(keyPrefix, key);
        for (RedisBloomFilterItem filter : bloomFilterMap.values()) {
            filter.remove(keyname);
        }
    }

    /**
     * 这里因为不同类对应不同的item存放,所以
     *
     * @param keys
     * @param keyPrefix
     */
    public void removeAll(Collection<String> keys, String keyPrefix) {

        List<String> keyList = new LinkedList();
        for (String key : keys) {
            keyList.add(checkKey(keyPrefix, key));
        }
        for (RedisBloomFilterItem filter : bloomFilterMap.values()) {
            filter.removeAll(keyList);
        }
    }

    public void removeAll(Collection<String> keys) {
        removeAll(keys, null);
    }


    public <T> void reset(SFunction<T> sFunction) {
        GetBloomFilterField.BloomFilterInfo bloomFilterInfo = GetBloomFilterField.resolveFieldName(sFunction);
        reset(bloomFilterInfo.getKeyPrefix(), bloomFilterInfo.getKeyName());
    }

    public void reset(BaseCondition resetCondition) {
        InnerInfo build = resetCondition.build();
        reset(build.getKeyPrefix(), build.getKeyName());
    }


    private void reset(String keyPrefix, String keyName) {
        keyName = checkKey(keyPrefix, keyName);
        for (RedisBloomFilterItem filter : bloomFilterMap.values()) {
            filter.reset(keyName);
        }
    }

    public void expire(ExpireCondition expireCondition) {
        InnerInfo condition = expireCondition.build();
        expireCondition.clear();
        expire(condition.getKeyPrefix(), condition.getKeyName(), condition.getTimeout(), condition.getTimeUnit(),condition.isLocal());
    }

    private void expire(String keyPrefix, String keyName, long timeout, TimeUnit timeUnit,boolean local) {
        keyName = checkKey(keyPrefix, keyName);
        for (RedisBloomFilterItem filter : bloomFilterMap.values()) {
            filter.expire(keyName, timeout, timeUnit,local);
        }
    }

    private GetBloomFilterField.BloomFilterInfo check(SFunction sFunction) {
        GetBloomFilterField.BloomFilterInfo bloomFilterInfo = GetBloomFilterField.resolveFieldName(sFunction);
        if (bloomFilterInfo == null) {
            throw new RedisAuxException("请检查注解配置是否正确!");
        }
        return bloomFilterInfo;
    }

    private String checkKey(String prefix, String key) {
        return StringUtils.isEmpty(prefix) ? key : CommonUtil.getKeyName(prefix, key);
    }

    @PreDestroy
    protected void destory() {
        for (RedisBloomFilterItem value : this.bloomFilterMap.values()) {
            value.clear();
        }
        this.bloomFilterMap.clear();
    }

    private boolean containKey(String keyPrefix,String keyName){
        String key = checkKey(keyPrefix, keyName);
        for (RedisBloomFilterItem filter : bloomFilterMap.values()) {
           if(filter.containKey(key)){
               return true;
           };
        }
        return false;
    }
    public boolean containKey(BaseCondition condition){
        return this.containKey(condition.keyPrefix,condition.keyName);
    }
    public <T> boolean containKey(SFunction<T> sFunction){
        GetBloomFilterField.BloomFilterInfo bloomFilterInfo = GetBloomFilterField.resolveFieldName(sFunction);
        return this.containKey(bloomFilterInfo.getKeyPrefix(),bloomFilterInfo.getKeyName());
    }
}

具体项,每个class对应一个操作类,这层缓存了hash个数,字节操作类,然后stragey类计算对应的hash并操作这个字节操作类

 @Override
    public boolean set(long[] index) {
        Object[] value = Arrays.stream(index).boxed().toArray();
        redisTemplate.execute(setBitScript, keyList, value);
        return Boolean.TRUE;
    }

这里把keyList直接作为参数,value列表则是对应的set的位置下标,然后执行lua脚本,通过该脚本执行

local kL=table.getn(KEYS)
local aL=table.getn(ARGV)
for i = 1, kL
do
    for k = 1,aL
    do redis.call('setbit', KEYS[i], tonumber(ARGV[k]), 1)
    end
end

设置单个的大致思路就是如此,然后批量设置其实也差不多,只要把对应的位数算出来设置就好了

然后到判断:

单个判断,这里只要判断取出来位置,是否都是1即可

 @Override
    public boolean get(long[] index) {
        List<Long> res = getBitScriptExecute(index, index.length);
        boolean exists = res.get(0).equals(BloomFilterConstants.TRUE);
        return exists;
    }

这里的size是单个元素被hash后的长度,放在value数组里面里面,然后可以复用keyList对象

private List<Long> getBitScriptExecute(long[] index,int size) {
        Object[] value = new Long[index.length+1];
        value[0]=Long.valueOf(size);
        for (int i = 1; i < value.length; i++) {
            value[i] = Long.valueOf(index[i-1]);
        }
        List res = (List) redisTemplate.execute(getBitScript, keyList, value);
        return res;
    }

取的脚本,这个比较核心,这里的做法是先整理成一个大一维数组,然后先取出每个元素的下标位置,再判断这些范围里面,本身和副本上面的位置是否有0,有就代表该元素不存在

--ARGV[1]代表单个待判断元素对应的字节长度,
-- 索引 1-length 代表键,0为不存在,1为存在
local array = {}
local aL = table.getn(ARGV)-1
local kL = table.getn(KEYS)
local bitL = ARGV[1]
--把单个元素所对应的下标取出来
local elementSize = aL / bitL
--第一个元素2...ARGV[1]+1,第二个ARGV[1]+2...2ARGV[1]+1
for index = 1, elementSize
    --判断单个元素的情况
do local notAdd = true
    for i = (index - 1) * bitL + 2, index * bitL+1
        --判断对应的键下标是否都为true
    do
        local k = 1
        while (notAdd and k <= kL)
        do
            if redis.call('getbit', KEYS[k], ARGV[i]) == 0 then
                array[index] = 0
                notAdd = false
            else
                k = k + 1
            end
        end
    end
    if notAdd then array[index] = 1 end
end
return array

批量取

   public List<Boolean> getBatch(List index) {
        //index.size*keyList.size
        readWriteLock.readLock().lock();
        long[] array = getArrayFromList(index);
        List<Long> list = getBitScriptExecute(array,((long[]) index.get(0)).length);
        List<Boolean> res = new ArrayList(index.size());
        for (Long temp : list) {
            res.add(Boolean.valueOf(temp.equals(BloomFilterConsts.TRUE)));
        }
        readWriteLock.readLock().unlock();
        return res;
    }

然后到重置功能,重置主要是lua脚本逻辑实现,脚本如下


local key = KEYS[1]
local start, last = 0, tonumber(ARGV[1])

local b = '\0'
-- 把多余的头和尾用setbit处理
local mstart, mlast = start % 8, (last + 1) % 8
--如果尾部有多出来的字节,采用单个字符处理并且把尾指针改变
if mlast > 0 then
    --获取尾指针的地址
    local t = math.max(last - mlast + 1, start)
    for i = t,last do
        redis.call('SETBIT', key, i, b)
    end
    last = t
end

-- 处理开头多余出来的部分
if mstart > 0 then
    local t = math.min(start - mstart + 7, last)
    for i = start, t do
        redis.call('SETBIT', key, i, b)
    end
    start = t + 1
end

-- 设置剩余范围
local rs, re = start / 8, (last + 1) / 8
local rl = re - rs
if rl > 0 then
    --string.rep拼接功能
    redis.call('SETRANGE', key, rs, string.rep(b, rl))
end

最后到一个键过期的功能,主要是判断是否设置了过期时间,如果不为-1,则在添加键后配置过期时间,然后用一个线程去定时处理过期键,这个定时在没有任务处理时,会每5s检查一次是否需要处理,如果有的话,就把当前的处理好后,睡眠直到下次键过期(通过最小堆来把快要过期的键放在顶端,然后睡眠相应的时间),这里的做处理是指由于上面的一些缓存的存在,当键过期时,对应的缓存也要删除,所以这里用了监听者模式来通知对应的类做对应的清理工作

public class CheckTask extends Thread implements KeyExpirePublisher, InitializingBean {
    private List<KeyExpireListener> listeners = new ArrayList<>();
    private PriorityBlockingQueue<WatiForDeleteKey> priorityQueue;
    private volatile Boolean run = true;
    private ThreadPoolExecutor executors;

    public CheckTask() {
        super("checkTask");
        this.priorityQueue = new PriorityBlockingQueue<>();
        executors = new ThreadPoolExecutor(4, 4, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1024), new ThreadPoolExecutor.CallerRunsPolicy());
    }

    @Override
    public void run() {
        while (run) {
            //当队列为空时,每5秒检测一次
            if (priorityQueue.isEmpty()) {
                try {
                    TimeUnit.SECONDS.sleep(CHECK_TASK_PER_SECOND);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else {
                //查看存活时间最小的那个节点
                WatiForDeleteKey peek = priorityQueue.peek();
                long dispearTime = 0;
                long now = 0;
                //针对键的存活时间较为密切的情况,一直弹出直到存活时间大于当前时间
                while (peek != null && (dispearTime = (peek.getStartTime() + peek.getExistTime())) <= (now = System.currentTimeMillis())) {
                    WatiForDeleteKey poll = priorityQueue.poll();
                    notifyListener(poll.getKey());
                    peek = priorityQueue.peek();
                }
                //等待下一次过期的时间
                try {
                    if (dispearTime > now) {
                        TimeUnit.MILLISECONDS.sleep(dispearTime - now);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }
    }

    public  void addExpireKey(WatiForDeleteKey watiForDeleteKey) {
        priorityQueue.offer(watiForDeleteKey);
    }

    @Override
    public void addListener(KeyExpireListener listener) {
        listeners.add(listener);
    }

    @Override
    public void removeListener(KeyExpireListener listener) {
        listeners.remove(listener);
    }

    @Override
    public void notifyListener(String key) {
//通过线程池提交删除任务
        for (KeyExpireListener listener : listeners) {
            executors.submit(() -> listener.removeKey(key));
        }

    }

    @Override
    public void afterPropertiesSet() throws Exception {
        this.start();
    }

    @PreDestroy
    public void stopRun() {
        this.run = Boolean.FALSE;
        executors.shutdown();
    }
}

过期键信息

public class WatiForDeleteKey implements Comparable {
    private String key;
    private long existTime;
    private long startTime;

    public String getKey() {
        return key;
    }

    public long getExistTime() {
        return existTime;
    }

    public long getStartTime() {
        return startTime;
    }

    public WatiForDeleteKey(String key, long existTime, long startTime) {
        this.key = key;
        this.existTime = existTime;
        this.startTime = startTime;
    }

    @Override
    public int compareTo(Object o) {
        if (o instanceof WatiForDeleteKey) {
            long now = System.currentTimeMillis();
            //剩下的存活时间
            WatiForDeleteKey ot = (WatiForDeleteKey) o;
            long other = ot.existTime - (now - ot.startTime);

            long self = existTime - (now - startTime);
            //小顶堆,在上面是最小的,存活时间最少在上面
            return self<other?-1:(self==other)?0:1;
        }
        throw new RedisAuxException("WaitForDeleteKey无法比对");
    }

    @Override
    public String toString() {
        return key+":"+((startTime+existTime)-System.currentTimeMillis());
    }
}

上面的bloomfilterItem类就是监听者类,通过一开始的配置初始化来为checkTask添加监听者。

另外附上本地字节数组

public class LocalBitArray implements BitArray {

    private static final int LONG_ADDRESSABLE_BITS = 6;
    private AtomicLongArray data;
    private LongAdder bitCount;
    private final long bitSize;
    private final String key;

    public LocalBitArray(String key,Long bitSize) {
        long[] longs = new long[Ints.checkedCast(LongMath.divide(bitSize, 64, RoundingMode.CEILING))];
        this.data = new AtomicLongArray(longs);
        this.bitCount = new LongAdder();
        long bitCount = 0;
        this.bitCount.add(bitCount);
        this.bitSize = bitSize;
        this.key=key;
    }




    @Override
    public boolean set(long[] indexs) {
        for (long bitIndex : indexs) {
            if (!getBitIndex(bitIndex, data) && setBitIndex(bitIndex, data)) {
                bitCount.increment();
            }
        }
        return true;
    }


    @Override
    public boolean get(long[] indexs) {

        for (long index : indexs) {
            if (!getBitIndex(index, data)) {
                return false;
            }
        }
        return true;
    }

    private boolean getBitIndex(long bitIndex, AtomicLongArray data) {
        return (data.get((int) (bitIndex >>> LONG_ADDRESSABLE_BITS)) & (1L << bitIndex)) != 0;
    }

    private boolean setBitIndex(long bitIndex, AtomicLongArray data) {
        int longIndex = (int) (bitIndex >>> LONG_ADDRESSABLE_BITS);
        // only cares about low 6 bits of bitIndex
        long mask = 1L << bitIndex;
        long oldValue;
        long newValue;
        do {
            oldValue = data.get(longIndex);
            newValue = oldValue | mask;
            if (oldValue == newValue) {
                return false;
            }
        } while (!data.compareAndSet(longIndex, oldValue, newValue));
        return true;
    }

    @Override
    public long bitSize() {
        return (long) data.length() * Long.SIZE;
    }

    @Override
    public void reset() {
        long[] longs = new long[Ints.checkedCast(LongMath.divide(bitSize, 64, RoundingMode.CEILING))];
        this.bitCount = new LongAdder();
        this.data = new AtomicLongArray(longs);
    }

    @Override
    public void clear() {
        this.data = null;
        this.bitCount = null;
    }

    @Override
    public String getKey() {
        return this.key;
    }



    @Override
    public List<Boolean> getBatch(List indexs) {
        List<Boolean> list = new ArrayList(indexs.size());
        for (Object o : indexs) {
            long[] temp = (long[]) o;
            boolean b = get(temp);
            list.add(b);
        }
        return list;
    }

    @Override
    public boolean setBatch(List indexs) {
        for (Object o : indexs) {
            long[] temp = (long[]) o;
            set(temp);
        }
        return true;
    }



}

大概的实现思路就是这样,关于简单的实现,可以查看我的另一篇文章,但功能相对较少

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值