分布式缓存穿透,手撕布隆过滤器,Bloom Filter的java代码实现

相信大家都用过redis缓存,无论是面试或者生产实践中,肯定遇到过缓存穿透相关问题,常见的解决方案如下:
1、缓存空对象
2、布隆过滤器
今天主要和大家分享下用java代码实现布隆过滤器

一、布隆过滤器的概念
布隆过滤器是一种基于位数组和哈希的数据结构,能够高效的插入和查找,相比于HashMap等,布隆过滤器占用内存低。但是布隆过滤器存在一定的误报率,所以使用布隆过滤器,业务上要允许误差,不过误报率可调。
布隆过滤器认为不存在的数据一定不存在,布隆过滤器认为存在的数据可能存在。就好似一个人不认识一个人一定不认识,认识一个人可能认识,和之前的人长得很像。
二、布隆过滤器两个重要参数
假设样本个数为n,即要输入n个对象构建布隆过滤器。
则布隆过滤器大小m和哈希函数的个数k,计算公式如下:(小数向上取整)
在这里插入图片描述
三、Java代码实现

package com.rising.bloom;

import java.util.BitSet;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Author: rising
 * @Description:
 * @Date: create in 2020/9/12 12:03
 */
public class BloomFilter {

    //默认布隆过滤器样本数
    private static int DEFAULT_SIZE = 10 * 10000;
    //默认左移位数
    private static int DEFAULT_LEFT = 4;
    //默认右移位数
    private static int DEFAULT_RIGHT = 16;
    private static Double DEFAULT_FALSE_POSITIVE_RATE = 0.001;
    private int needHashs;
    //布隆过滤器已使用总数
    private AtomicInteger sum = new AtomicInteger(0);
    private RotatingHash[] rotatingHashes;
    private BitSet bitSet;

    /**
     * 默认样本数是10万,误报率是千分之一
     */
    public BloomFilter() {
        this(DEFAULT_LEFT, DEFAULT_RIGHT, DEFAULT_SIZE, DEFAULT_FALSE_POSITIVE_RATE);
    }

    /**
     *
     * @param size  样本数
     * @param falsePositiveRate  误报率
     */
    public BloomFilter(int size, Double falsePositiveRate) {
        this(DEFAULT_LEFT, DEFAULT_RIGHT, size, falsePositiveRate);
    }

    /**
     * 采用的是位移Hash,所以需要提供左位移数,右位移数
     * @param left
     * @param right
     * @param size   样本数
     * @param falsePositiveRate    误报率
     */
    public BloomFilter(int left, int right, int size, Double falsePositiveRate) {
        //计算布隆过滤器总空间,利用已经网上已经推导的公式
        int length = (int)Math.ceil(-Math.log(falsePositiveRate) / (Math.log(2) * Math.log(2))) * size;
        bitSet = new BitSet(length);
        //计算需要的hash函数总数
        needHashs = (int) Math.ceil(0.7 * length / size);
        rotatingHashes = new RotatingHash[needHashs];
        for (int i = 0; i < needHashs; i++) {
            rotatingHashes[i] = new RotatingHash(left + i, right + i, length - 1);
        }
    }

    /**
     * 添加方法
     * @param value
     */
    synchronized public void add(String value) {
        sum.incrementAndGet();
        //通过不同的hash计算出索引,将其所在的位置设置1
        for (RotatingHash rotatingHash : rotatingHashes) {
            bitSet.set(rotatingHash.rotatingHash(value), true);
        }
    }

    /**
     * 必须所有的hash计算值所在的位置都为1,才能返回true(存在误判率)
     * 布隆过滤器认为不存在的一定不存在
     * @param value
     * @return
     */
    public boolean contain(String value) {
        if (value == null) {
            return false;
        }
        boolean ret = true;
        for (RotatingHash rotatingHash : rotatingHashes) {
            ret = ret && bitSet.get(rotatingHash.rotatingHash(value));
        }
        return ret;
    }

    /**
     * 获取总数
     * @return
     */
    public int getSum() {
        return sum.intValue();
    }

    /**
     * 位运算计算hash
     */
    public static class RotatingHash {
        //左移位数
        private int left;
        //右移位数
        private int right;
        //任意质数
        private int prime;

        public RotatingHash(int left, int right, int prime) {
            this.left = left;
            this.right = right;
            this.prime = prime;
        }

        public int rotatingHash(String key) {
            int hash, i;
            for (hash = key.length(), i = 0; i < key.length(); ++i) {
                hash = (hash << left) ^ (hash >> right) ^ key.charAt(i);
            }
            return Math.abs(hash % prime);
        }
    }
}

四、测试案例

package com.rising.bloom;

import com.carrotsearch.sizeof.RamUsageEstimator;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author: rising
 * @Description:
 * @Date: create in 2020/9/12 12:25
 */
public class BloomFilterTest {
    public static void main(String[] args) {
        System.out.println("===================测试布隆过滤器===================");
        int datas = 100 * 10000;
        BloomFilter bloomFilter = new BloomFilter(datas, 0.001);
        for (int i = 0; i < datas; i++) {
            bloomFilter.add(String.valueOf(i));
        }
        //测试1000万个不存在,看误报率
        int falseSum = 0;
        long startTime = System.currentTimeMillis();
        for (int i = datas; i < datas + 1000 * 10000; i++) {
            boolean contain = bloomFilter.contain(String.valueOf(i));
            if (contain) {
                falseSum++;
            }
        }
        long endTime = System.currentTimeMillis();
        double bloomByteSize = new BigDecimal(RamUsageEstimator.sizeOf(bloomFilter) / (1024.0 * 1024)).setScale(3, BigDecimal.ROUND_HALF_UP).doubleValue();
        System.out.println("布隆过滤器总数:" + bloomFilter.getSum());
        System.out.println("布隆过滤器误报数:" + falseSum);
        System.out.println("布隆过滤器占用空间:" + bloomByteSize + "MB字节");
        System.out.println("布隆过滤器处理时间:" + (endTime - startTime) + "ms");
        System.out.println("===================测试HashMap===================");
        Map<String, Object> map = new HashMap<String, Object>(datas);
        for (int i = 0; i < datas; i++) {
            map.put(i + "", null);
        }
        //测试1000万个不存在,看误报率
        falseSum = 0;
        startTime = System.currentTimeMillis();
        for (int i = datas; i < datas + 1000 * 10000; i++) {
            boolean contain = map.containsKey(i + "");
            if (contain) {
                falseSum++;
            }
        }
        endTime = System.currentTimeMillis();
        double mapByteSize = new BigDecimal(RamUsageEstimator.sizeOf(map) / (1024.0 * 1024)).setScale(3, BigDecimal.ROUND_HALF_UP).doubleValue();
        System.out.println("HashMap总数:" + map.size());
        System.out.println("HashMap误报数:" + falseSum);
        System.out.println("HashMap占用空间:" + mapByteSize + "MB字节");
        System.out.println("HashMap处理时间:" + (endTime - startTime) + "ms");
    }
}

五、测试结果
在这里插入图片描述
可以看到,100万个样本数据,测试1000万个不存在的数据,布隆过滤器误报数只有74,HashMap消耗时间是布隆过滤器将近2倍,同时HashMap占用约92MB,布隆过滤器占用只有2MB不到。

路漫漫其修远兮,吾将上下而求索

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值