通俗易懂讲布隆过滤器(转载)

数学之美 第二版 pdf 阅读详情

目录

前言:

缓存雪崩

缓存穿透

从一道面试题说起

布隆过滤器

Guava实现布隆过滤器

Redis实现布隆过滤器

Redisson实现

带计数的布隆过滤器

布隆过滤器工作位置

布隆过滤器的其他应用场景


前言:

        哈哈,没想到昨天才看的字节青训营直播,今天就看到了直播某位大佬的文章,实在是太有缘分了

作者:小杰博士

链接:通俗易懂讲布隆过滤器 - 掘金 (juejin.cn)


缓存雪崩

缓存雪崩就是Redis的大量热点数据同时过期(失效),因为设置了相同的过期时间,刚好这个时候Redis请求的并发量又很大,就会导致所有的请求落到数据库。

这个问题怎么解决呢?

  • 加互斥锁或者使用队列,针对同一个key只允许一个线程到数据库查询
  • 缓存定时预先更新,避免同时失效
  • 通过加随机数,使key在不同的时间过期
  • 缓存永不过期


缓存穿透

大家看下这幅图,用户可能进行了一次条件错误的查询,这时候redis是不存在的,按照常规流程就是去数据库找了,可是这是一次错误的条件查询,数据库当然也不会存在,也不会往redis里面写值,返回给用户一个空,这样的操作一次两次还好,可是次数多了还了得,我放redis本来就是为了挡一挡,减轻数据库的压力,现在redis变成了形同虚设,每次还是去数据库查找了,这个就叫做缓存穿透,相当于redis不存在了,被击穿了,对于这种情况很好解决,我们可以在redis缓存一个空字符串或者特殊字符串,比如&&,下次我们去redis中查询的时候,当取到的值是空或者&&,我们就知道这个值在数据库中是没有的,就不会在去数据库中查询,注:这里缓存不存在key的时候一定要设置过期时间,不然当数据库已经新增了这一条记录的时候,这样会导致缓存和数据库不一致的情况。

上面这个是重复查询同一个不存在的值的情况,如果应用每次查询的不存在的值是不一样的呢?即使你每次都缓存特殊字符串也没用,因为它的值不一样,比如我们的数据库用户id是111,112,113,114依次递增,但是别人要攻击你,故意拿-100,-936,-545这种乱七八糟的key来查询,这时候redis和数据库这种值都是不存在的,人家每次拿的key也不一样,你就算缓存了也没用,这时候数据库的压力是相当大,比上面这种情况可怕的多,怎么办呢,这时候我们今天的主角布隆过滤器就登场了。。


从一道面试题说起

问:如何在海量元素中(例如 10 亿无序、不定长、不重复)快速判断一个元素是否存在? 好,我们最简单的想法就是把这么多数据放到数据结构里去,比如List、Map、Tree,一搜不就出来了吗,比如map.get(),我们假设一个元素1个字节的字段,10亿的数据大概需要 900G 的内存空间,这个对于普通的服务器来说是承受不了的,当然面试官也不希望听到你这个答案,因为太笨了吧,我们肯定是要用一种好的方法,巧妙的方法来解决,这里引入一种节省空间的数据结构,位图,他是一个有序的数组,只有两个值,0 和 1。0代表不存在,1代表存在。

有了这个屌炸天的东西,现在我们还需要一个映射关系,你总得知道某个元素在哪个位置上吧,然后在去看这个位置上是0还是1,怎么解决这个问题呢,那就要用到哈希函数,用哈希函数有两个好处,第一是哈希函数无论输入值的长度是多少,得到的输出值长度是固定的,第二是他的分布是均匀的,如果全挤的一块去那还怎么区分,比如MD5、SHA-1这些就是常见的哈希算法。

我们通过哈希函数计算以后就可以到相应的位置去找是否存在了,我们看红色的线,24和147经过哈希函数得到的哈希值是一样的,我们把这种情况叫做哈希冲突或者哈希碰撞。哈希碰撞是不可避免的,我们能做的就是降低哈希碰撞的概率,第一种是可以扩大维数组的长度或者说位图容量,因为我们的函数是分布均匀的,所以位图容量越大,在同一个位置发生哈希碰撞的概率就越小。但是越大的位图容量,意味着越多的内存消耗,所以我们想想能不能通过其他的方式来解决,第二种方式就是经过多几个哈希函数的计算,你想啊,24和147现在经过一次计算就碰撞了,那我经过5次,10次,100次计算还能碰撞的话那真的是缘分了,你们可以在一起了,但也不是越多次哈希函数计算越好,因为这样很快就会填满位图,而且计算也是需要消耗时间,所以我们需要在时间和空间上寻求一个平衡。。

比如说:我们存放100万个元素,到底需要多大的位图容量,需要多少个哈希函数呢?


布隆过滤器

今天的主角,布隆过滤器就登场了。这个事情早就有人研究过了,在 1970 年的时候,有一个叫做布隆的前辈对于判断海量元素中元素是否存在的问题进行了研究,也就是到底需要多大的位图容量和多少个哈希函数,它发表了一篇论文,提出的这个容器就叫做布隆过滤器。

大家来看下这个图,我们看集合里面3个元素,现在我们要存了,比如说a,经过f1(a),f2(a),f3(a)经过三个哈希函数的计算,在相应的位置上存入1,元素b,c也是通过这三个函数计算放入相应的位置。当取的时候,元素a通过f1(a)函数计算,发现这个位置上是1,没问题,第二个位置也是1,第三个位置上也是 1,这时候我们说这个a在布隆过滤器中是存在的,没毛病,同理我们看下面的这个d,通过三次计算发现得到的结果也都是1,那么我们能说d在布隆过滤器中是存在的吗,显然是不行的,我们仔细看d得到的三个1其实是f1(a),f1(b),f2(c)存进去的,并不是d自己存进去的,这个还是哈希碰撞导致的,我们把这种本来不存在布隆过滤器中的元素误判为存在的情况叫做假阳性(False Positive Probability,FPP)。

我们再来看另一个元素,e 元素。我们要判断它在容器里面是否存在,一样地要用这三个函数去计算。第一个位置是 1,第二个位置是 1,第三个位置是 0。那么e元素能不能判断是否在布隆过滤器中? 答案是肯定的,e一定不存在。你想啊,如果e存在的话,他存进去的时候这三个位置都置为1,现在查出来有一个位置是0,证明他没存进去啊。。通过上面这张图加说明,我们得出两个重要的结论

从容器的角度来说:

  • 如果布隆过滤器判断元素在集合中存在,不一定存在
  • 如果布隆过滤器判断不存在,一定不存在

从元素的角度来说:

  • 如果元素实际存在,布隆过滤器一定判断存在
  • 如果元素实际不存在,布隆过滤器可能判断存在

小伙们请牢记。。


Guava实现布隆过滤器

java为什么写的人多,基数大,因为是开源的,拥抱开源,框架多,轮子多,而且一个功能的轮子还不止一个,光序列化就有fastjson,jackson,gson,随你挑任你选,那布隆过滤器的轮子就是google提供的guava,我们用代码来看一下使用方法

首先引入我们的架包

      <dependency>
          <groupId>com.google.guava</groupId>
          <artifactId>guava</artifactId>
          <version>21.0</version>
      </dependency>

这里先往布隆过滤器里面存放100万个元素,然后分别测试100个存在的元素和9900个不存在的元素他们的正确率和误判率

    //插入多少数据
    private static final int insertions = 1000000;

    //期望的误判率
    private static double fpp = 0.02;

    public static void main(String[] args) {

        //初始化一个存储string数据的布隆过滤器,默认误判率是0.03
        BloomFilter<String> bf = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), insertions, fpp);

        //用于存放所有实际存在的key,用于是否存在
        Set<String> sets = new HashSet<String>(insertions);

        //用于存放所有实际存在的key,用于取出
        List<String> lists = new ArrayList<String>(insertions);

        //插入随机字符串
        for (int i = 0; i < insertions; i++) {
            String uuid = UUID.randomUUID().toString();
            bf.put(uuid);
            sets.add(uuid);
            lists.add(uuid);
        }

        int rightNum = 0;
        int wrongNum = 0;

        for (int i = 0; i < 10000; i++) {
            // 0-10000之间,可以被100整除的数有100个(100的倍数)
            String data = i % 100 == 0 ? lists.get(i / 100) : UUID.randomUUID().toString();

            //这里用了might,看上去不是很自信,所以如果布隆过滤器判断存在了,我们还要去sets中实锤
            if (bf.mightContain(data)) {
                if (sets.contains(data)) {
                    rightNum++;
                    continue;
                }
                wrongNum++;
            }
        }

        BigDecimal percent = new BigDecimal(wrongNum).divide(new BigDecimal(9900), 2, RoundingMode.HALF_UP);
        BigDecimal bingo = new BigDecimal(9900 - wrongNum).divide(new BigDecimal(9900), 2, RoundingMode.HALF_UP);
        System.out.println("在100W个元素中,判断100个实际存在的元素,布隆过滤器认为存在的:" + rightNum);
        System.out.println("在100W个元素中,判断9900个实际不存在的元素,误认为存在的:" + wrongNum + ",命中率:" + bingo + ",误判率:" + percent);
    }

最后得出的结果

我们看到这个结果正是印证了上面的结论,这100个真实存在元素在布隆过滤器中一定存在,另外9900个不存在的元素,布隆过滤器还是判断了216个存在,这个就是误判,原因上面也说过了,所以布隆过滤器不是万能的,但是他能帮我们抵挡掉大部分不存在的数据已经很不错了,已经减轻数据库很多压力了,另外误判率0.02是在初始化布隆过滤器的时候我们自己设的,如果不设默认是0.03,我们自己设的时候千万不能设0!

位图的容量是基于元素个数和误判率计算出来的。

long numBits = optimalNumOfBits(expectedInsertions, fpp);

根据位数组的大小,我们进一步计算出了哈希函数的个数。

int numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, numBits);

通过这个公式我们计算出来,存储100万个元素只占用了0.87M的内存,生成了5个哈希函数。

在线的计算网站:hur.st/bloomfilter…

源码在:com/xhj/bloom/memory/BloomFilterDemo.java


Redis实现布隆过滤器

上面使用guava实现布隆过滤器是把数据放在本地内存中,我们项目往往是分布式的,我们还可以把数据放在redis中,用redis来实现布隆过滤器,这就需要我们自己设计映射函数,自己度量二进制向量的长度,下面贴代码,大家可以直接拿来用的,已经经过测试了。。

/**
 * 布隆过滤器核心类
 *
 * @param <T>
 * @author jack xu
 */
public class BloomFilterHelper<T> {
    private int numHashFunctions;
    private int bitSize;
    private Funnel<T> funnel;

    public BloomFilterHelper(int expectedInsertions) {
        this.funnel = (Funnel<T>) Funnels.stringFunnel(Charset.defaultCharset());
        bitSize = optimalNumOfBits(expectedInsertions, 0.03);
        numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, bitSize);
    }

    public BloomFilterHelper(Funnel<T> funnel, int expectedInsertions, double fpp) {
        this.funnel = funnel;
        bitSize = optimalNumOfBits(expectedInsertions, fpp);
        numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, bitSize);
    }

    public int[] murmurHashOffset(T value) {
        int[] offset = new int[numHashFunctions];

        long hash64 = Hashing.murmur3_128().hashObject(value, funnel).asLong();
        int hash1 = (int) hash64;
        int hash2 = (int) (hash64 >>> 32);
        for (int i = 1; i <= numHashFunctions; i++) {
            int nextHash = hash1 + i * hash2;
            if (nextHash < 0) {
                nextHash = ~nextHash;
            }
            offset[i - 1] = nextHash % bitSize;
        }

        return offset;
    }

    /**
     * 计算bit数组长度
     */
    private int optimalNumOfBits(long n, double p) {
        if (p == 0) {
            p = Double.MIN_VALUE;
        }
        return (int) (-n * Math.log(p) / (Math.log(2) * Math.log(2)));
    }

    /**
     * 计算hash方法执行次数
     */
    private int optimalNumOfHashFunctions(long n, long m) {
        return Math.max(1, (int) Math.round((double) m / n * Math.log(2)));
    }
}

这里在操作redis的位图bitmap,你可能只知道redis五种数据类型,string,list,hash,set,zset,没听过bitmap,但是不要紧,你可以说他是一种新的数据类型,也可以说不是,因为他的本质还是string,后面我也会专门写一篇文章来介绍数据类型以及在他们在互联网中的使用场景。。

/**
 * redis操作布隆过滤器
 *
 * @param <T>
 * @author xhj
 */
public class RedisBloomFilter<T> {
    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 删除缓存的KEY
     *
     * @param key KEY
     */
    public void delete(String key) {
        redisTemplate.delete(key);
    }

    /**
     * 根据给定的布隆过滤器添加值,在添加一个元素的时候使用,批量添加的性能差
     *
     * @param bloomFilterHelper 布隆过滤器对象
     * @param key               KEY
     * @param value             值
     * @param <T>               泛型,可以传入任何类型的value
     */
    public <T> void add(BloomFilterHelper<T> bloomFilterHelper, String key, T value) {
        int[] offset = bloomFilterHelper.murmurHashOffset(value);
        for (int i : offset) {
            redisTemplate.opsForValue().setBit(key, i, true);
        }
    }

    /**
     * 根据给定的布隆过滤器添加值,在添加一批元素的时候使用,批量添加的性能好,使用pipeline方式(如果是集群下,请使用优化后RedisPipeline的操作)
     *
     * @param bloomFilterHelper 布隆过滤器对象
     * @param key               KEY
     * @param valueList         值,列表
     * @param <T>               泛型,可以传入任何类型的value
     */
    public <T> void addList(BloomFilterHelper<T> bloomFilterHelper, String key, List<T> valueList) {
        redisTemplate.executePipelined(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                connection.openPipeline();
                for (T value : valueList) {
                    int[] offset = bloomFilterHelper.murmurHashOffset(value);
                    for (int i : offset) {
                        connection.setBit(key.getBytes(), i, true);
                    }
                }
                return null;
            }
        });
    }

    /**
     * 根据给定的布隆过滤器判断值是否存在
     *
     * @param bloomFilterHelper 布隆过滤器对象
     * @param key               KEY
     * @param value             值
     * @param <T>               泛型,可以传入任何类型的value
     * @return 是否存在
     */
    public <T> boolean contains(BloomFilterHelper<T> bloomFilterHelper, String key, T value) {
        int[] offset = bloomFilterHelper.murmurHashOffset(value);
        for (int i : offset) {
            if (!redisTemplate.opsForValue().getBit(key, i)) {
                return false;
            }
        }
        return true;
    }
}

最后就是测试类了

    public static void main(String[] args) {
        RedisBloomFilter redisBloomFilter = new RedisBloomFilter();
        int expectedInsertions = 1000;
        double fpp = 0.1;
        redisBloomFilter.delete("bloom");
        BloomFilterHelper<CharSequence> bloomFilterHelper = new BloomFilterHelper<>(Funnels.stringFunnel(Charset.defaultCharset()), expectedInsertions, fpp);
        int j = 0;
        // 添加100个元素
        List<String> valueList = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            valueList.add(i + "");
        }
        long beginTime = System.currentTimeMillis();
        redisBloomFilter.addList(bloomFilterHelper, "bloom", valueList);
        long costMs = System.currentTimeMillis() - beginTime;
        log.info("布隆过滤器添加{}个值,耗时:{}ms", 100, costMs);
        for (int i = 0; i < 1000; i++) {
            boolean result = redisBloomFilter.contains(bloomFilterHelper, "bloom", i + "");
            if (!result) {
                j++;
            }
        }
        log.info("漏掉了{}个,验证结果耗时:{}ms", j, System.currentTimeMillis() - beginTime);
    }

注意这里用的是addList,他的底层是pipelining管道,而add方法的底层是一个个for循环的setBit,这样的速度效率是很慢的,但是他能有返回值,知道是否插入成功,而pipelining是不知道的,所以具体选择用哪一种方法看你的业务场景,以及需要插入的速度决定。。


Redisson实现

好,上面其实是手写了一个分布式的布隆过滤器,既然 Java 是开源的,那一定会有好事之者帮我们实现好了,不然每次我们都手写一个,不说麻烦,可能还会存在效率不高以及诸多未考虑到的点。那么这个成熟的框架就是大名鼎鼎的 Redisson!

参考文档如下,github.com/redisson/re…

下面我写了一个单节点的实现方式:

/**
 * @author jack xu
 */
public class SingleTest {
    public static void main(String[] args) {

        Config config = new Config();
        config.setCodec(new org.redisson.client.codec.StringCodec());

        //指定使用单节点部署方式
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");

        RedissonClient redisson = Redisson.create(config);

        RBloomFilter<SomeObject> bloomFilter = redisson.getBloomFilter("sample");
        // 初始化布隆过滤器,预计统计元素数量为55000000,期望误差率为0.03
        bloomFilter.tryInit(55000000L, 0.03);
        bloomFilter.add(new SomeObject("field1Value", "field2Value"));
        bloomFilter.add(new SomeObject("field5Value", "field8Value"));
        bloomFilter.contains(new SomeObject("field1Value", "field8Value"));
    }

}

而基于 Redis 的 Redisson 集群分布式布隆过滤器通过 RClusteredBloomFilter 接口,为集群状态下的 Redis 环境提供了布隆过滤器数据分片的功能。 通过优化后更加有效的算法,通过压缩未使用的比特位来释放集群内存空间。每个对象的状态都将被分布在整个集群中。所含最大比特数量为2^64。注:该功能仅限于Redisson PRO 版本。

RClusteredBloomFilter<SomeObject> bloomFilter = redisson.getClusteredBloomFilter("sample");
// 采用以下参数创建布隆过滤器
// expectedInsertions = 255000000
// falseProbability = 0.03
bloomFilter.tryInit(255000000L, 0.03);
bloomFilter.add(new SomeObject("field1Value", "field2Value"));
bloomFilter.add(new SomeObject("field5Value", "field8Value"));
bloomFilter.contains(new SomeObject("field1Value", "field8Value"));


带计数的布隆过滤器

如果数据库删除了数据,布隆过滤器的数据也要删除。但是布隆过滤器里面没有提供删除的方法。为什么布隆过滤器不提供删除的方法呢?

我们来分析一下,比如我们把a删除了,那么三个位置都要改成0。但是再来判断b元素是否存在的时候,因为有一个位置变成了0,所以判断b元素也不存在,但实际上b元素是存在的。就是因为存在哈希碰撞,所以元素只能存入,不能删除。

那如果我们要实现删除的功能怎么做,类似于HashMap的链地址法,我们可以在每个下标位置上增加一个计数器。比如这个位置命中两次,计数器就是2。当删除a元素的时候,先把计数器改成1。删除b元素的时候,计数器变成0,这个时候下边对应的位才置为0。

我们把这种带删除功能的bf叫做Counting Bloom Filter,源码在:com/xhj/bloom/countBloom/CountingBloomFilterTest.java


布隆过滤器工作位置

第一步是将数据库所有的数据加载到布隆过滤器。第二步当有请求来的时候先去布隆过滤器查询,如果bf说没有,第三步直接返回。如果bf说有,在往下走之前的流程。


布隆过滤器的其他应用场景

  • 网页爬虫对URL去重,避免爬取相同的 URL 地址;
  • 反垃圾邮件,从数十亿个垃圾邮件列表中判断某邮箱是否垃圾邮箱;
  • Google Chrome 使用布隆过滤器识别恶意 URL;
  • Medium 使用布隆过滤器避免推荐给用户已经读过的文章;
  • Google BigTable,Apache HBbase 和 Apache Cassandra使用布隆过滤器减少对不存在的行和列的查找。

好,布隆过滤器到这里就结束了,以后在面试中面试官在问到缓存击穿怎么办,我相信你应该能够回答的头头是道了,就像我这样通俗易懂的说出来即可,然后在工作中也可以应用,比如鉴权服务,当用户登录的时候可以先用布隆过滤器判断下,而不是直接去redis、数据库查。


本文源码:https://github.com/xuhaoj/redis-demo
 

Redis——Bloom Filter原理与应用 摘要 在我们平时开发过程中,会有一些 bool 型数据需要存取,比如用户一年的签到记录,签了是 1,没签是 0,要记录 365 天。如果使用普通的 key/value,每个用户要记录 365个,当用户上亿的时候,需要的存储空间是惊人的。为了解决这个问题, Redis 提供了位图数据结构,这样每天的签到记录只占据一个位,365 天就是 365 个位, 46 个字节 (一个稍长一点的字符串) 就可以完全容纳下,这就大大节约了存储空间。 位图不是特殊的数据结构,它的内容其实就是普通的字符串,也就是 byte 阅读详情

相关推荐

从0开始学算法:程序员必须掌握的核心算法有哪些?

本文列出想要学习算法要学会的一些知识点,后续博主会根据此目录,逐个讲解知识点,有我自己学习后根据自己的理解,尽量写的通俗易懂、易于大家理解的文章,也有博主看到的特别好的文章转载过来,希望对有兴趣学习算法的同学有帮助。 一、算法最最基础 1.时间复杂度 2.空间复杂度 二、基础数据结构 1.线性表 列表(必学) 链表(必学) 跳跃表(知道原理,应用,最后自己实现一遍) 并查集(建议结合刷题学习) ...

weixin_38356640的博客 4972

布隆过滤器

一个长度为100的int型数组,它所占用的内存为字节一个长度为100的long型数组,它所占用的内存为字节那么一个长度为100的bit(计算机中内存都是按照字节来计算的,一个字节=8bit)数组,它所占用的内存为字节如果我们所需的业务场景,可以用int数组实现,可以用long数组实现,也可以用bit数组来实现,那显然,使用bit数组来实现是最节省内存的做法。/**/*** 因为int和bit之间是32倍的关系。

weixin_41249051的博客 1566

STM32H7B0VBT6使用Free RTOS配置SD卡+Fatfs文件管理系统

STM32H7B0VBT6使用Free RTOS配置SD卡+Fatfs文件管理系统,记录配置过程

Jack的小窝 5942

Redis从入门到深入-布隆过滤器(20)

1. 布隆过滤器前言 不知道从什么时候开始,本来默默无闻的布隆过滤器一下子名声大燥,在面试中面试官问到怎么避免缓存穿透,你的第一反应可能就是布隆过滤器,缓存穿透=布隆过滤器成了标配,但具体什么是布隆过滤器,怎么使用布隆过滤器不是很清楚,那今天我们就来把他说清楚,讲明白。。 1.1 缓存穿透 大家看下这幅图,用户可能进行了一次条件错误的查询,这时候redis是不存在的,按照常规流程就是去数据库找了,可是这是一次错误的条件查询,数据库当然也不会存在,也不会往redis里面写值,返回给用户一个空,这样的操作一次

山楂小王的博客 329

TPAMI审稿人关注什么

写在开头:被tpami接收大概是每个cs er最开心的事情吧!今天阅读TPAMI的文章时,发现有扩刊的文章,也有不是扩刊的文章,那么这两类文章哪个更容易被接收呢?

carlous0的专栏 1685

Kaggle时间序列竞赛金牌方案解析与实战指南

本文深度解析了Kaggle时间序列竞赛的金牌方案,揭示了其成功不仅依赖先进模型,更在于系统工程思维。文章系统阐述了从数据理解、特征工程到模型集成与后处理的全流程,并重点剖析了分层建模、自定义损失函数、时序交叉验证等核心技巧,为参赛者提供了从理论到实战的完整指南。

weixin_29196315的博客 357

布隆过滤器(Bloom Filter)

布隆过滤器本身不支持更新或删除操作,这是因为它的结构决定了无法安全地删除或修改一个元素而不影响其他元素。对于需要删除或更新数据的场景,可以考虑使用计数布隆过滤器、定期重建布隆过滤器或将布隆过滤器与其他数据结构结合使用。这些解决方案可以在保持较高查询效率的同时,适应数据变化的需求。

galoiszhou的博客 1354

博途S7-1200与昆仑通态MCGS纯仿真联调实战指南

本文详细介绍了博途S7-1200与昆仑通态MCGS纯仿真联调的实战指南,包括环境搭建、配置步骤、通信桥接及故障排除。通过TIA Portal和MCGS Pro搭建的仿真环境,可实现80%以上的基础功能验证,适用于工业自动化学习和开发,大幅降低硬件投入成本。

weixin_26834815的博客 439

布隆过滤器之 Counting Bloom Filter

结论:适用于大数据量下的集合过滤。优点:空间效率和查询时间都远远超过一般的算法。缺点:有一定的误识别率和无法删除。网页爬虫对URL的去重,避免爬取相同的URL地址;反垃圾邮件,从数十亿个垃圾邮件列表中判断某邮箱是否垃圾邮箱(同理,垃圾短信);缓存击穿,将已存在的缓存放到布隆中,当黑客访问不存在的缓存时迅速返回避免缓存及DB挂掉。结论:Bloom Filter的变种,支持了计数及删除。优点:保证了空间和时间的同时,支持了计数及删除。

qq798280904的博客 2497

飞书日历同步Outlook翻车?我踩过的坑和最终用Google Calendar成功的完整配置记录

本文详细记录了从Outlook日历同步到飞书时遇到的常见问题及解决方案,特别是通过Google Calendar实现稳定同步的完整配置流程。文章揭示了Outlook同步方案易翻车的原因,并提供了Google Calendar的避坑指南、高级技巧和企业级部署建议,帮助用户高效实现日历同步。

weixin_33724046的博客 314

【ESP32设备通信】-两个ESP32间UART通信

在本文中,我们将使用 Arduino IDE 的 UART 硬件库在两个 ESP32 板之间执行 UART 或串行通信。 要使用 USB 端口调试和编程 ESP32,需要使用称为通用异步接收器/发送器 (UART) 通信的串行端口。 对于大多数传感器和系统,主要的通信方法被认为是 UART。 为了共享工作负载、信息和执行不同的任务,有时需要在两个 ESP32 开发板之间进行通信。

视觉与物联智能 2996

【转载】布隆过滤器-leetcode题解,记录自己的leetcode解题之路

该文章属于转载文章,仅用作收藏学习用途。如有侵权,请联系删除。 下面给出该文章的网址,该网站写了不少LeetCode题解,内容质量不错,讲解通俗易懂。 网站地址:布隆过滤器-leetcode题解,记录自己的leetcode解题之路 场景 假设你现在要处理这样一个问题,你有一个网站并且拥有很多访客,每当有用户访问时,你想知道这个ip是不是第一次访问你的网站。 hashtable 可以么 一个显而易见的答案是将所有的ip用hashtab.

MumuziD的博客 578

152:vue+leaflet 判断一个点是否在一个电子围栏内

本文介绍了一个基于Vue+Leaflet的示例,演示如何判断点是否在电子围栏内。作者大剑师兰特(优快云知名博主)展示了使用polygon.getBounds().contains(latlng)方法检测点与多边形边界的关系。代码仅需80行,通过点击地图触发判断逻辑,结果显示提示信息。该技术可广泛应用于地理围栏、位置服务等场景,需要安装turf依赖。文章提供了完整源码和配置说明,适合GIS开发者学习参考。

大剑师兰特的GIS世界 758

MySQL 主从,6 分钟带你掌握

大家好,我是楼仔! 今天写的这个主题内容,其实非常基础,但是作为高并发非常重要的几个场景,绝对绕不开,估计大家面试时,也经常会遇到。 这个主题的文章,网上非常多,本来想直接转载一篇,但是感觉没有合适的,要么文章不够精炼,要么就是精简过头,所以还是自己写一篇吧。 内容虽然基础,但我还是秉承以往的写作风格,参考众多优秀的博客后,打算写一篇能通俗易懂,又不失全面的文章。 前言 我们先看一下正常情况的查询过程: 先查询 Redis,如果查询成功,直接返回,查询不存在,去查询 DB; 如..

飞楼踏梦 487

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

缓存穿透、缓存击穿、缓存雪崩

今天写的这个主题内容,其实非常基础,但是作为高并发非常重要的几个场景,绝对绕不开,估计大家面试时,也经常会遇到。这个主题的文章,网上非常多,本来想直接转载一篇,但是感觉没有合适的,要么文章不够精炼,要么就是精简过头,所以还是自己写一篇吧。内容虽然基础,但我还是秉承以往的写作风格,参考众多优秀的博客后,打算写一篇能通俗易懂,又不失全面的文章。我们先看一下正常情况的查询过程:先查询 Redis,如果查询成功,直接返回,查询不存在,去查询 DB;如果 DB 查询成功,数据回写 Redis,查询不存在,直接返回。

m0_67698950的博客 102

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

面试必备:缓存穿透、缓存击穿、缓存雪崩(荣耀典藏版)

这个主题的文章,网上非常多,本来想直接转载一篇,但是感觉没有合适的,要么文章不够精炼,要么就是精简过头,所以还是自己写一篇吧。

人生若只初相见@的博客 2万+

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

雪崩之下,没有一片雪花是无辜的 ~~

先查询 Redis,如果查询成功,直接返回,查询不存在,去查询 DB;如果 DB 查询成功,数据回写 Redis,查询不存在,直接返回。

m0_73311735的博客 176

海量数据处理算法—Bloom Filter

海量数据处理算法—Bloom Filter

skiwnc的博客 233

数学之美--关于图论引申出来的爬虫构想

转载请注明出处:http://www.cnblogs.com/codefish/p/4971664.html 在了解爬虫之前,我一直认为是简单的对单一网站的采集,无非就是对于一个域名内定点的数据抓取而已,2012年买了《数学之美》后,就一直没有正儿八经的看,或者当时看了之后,由于自己的水平有限,压根就没有留下深刻的印象,以至于现在开始系统的研究一个框架的时候,总是粗浅的了解它的技术实现和A...

weixin_33915554的博客 125

信息论的应用

  推荐Google中国科学家写的一个系列《数学之美》,很不错,很通俗易懂。与我讲的信息科学基础课程关系很密切,可以作为课外阅读材料,帮助大家理解信息论的作用。   文章目录如下: 1. 统计语言模型 2. 谈谈中文分词 3. 隐含马尔可夫模型在语言处理中的应用 4. 怎样度量信息 5. 简单之美:布尔代数和搜索引擎的索引 6. 图论和网络爬虫 (Web Crawle...

weixin_33975951的博客 1019

海量数据处理之Bloom Filter详解

海量数据处理之Bloom Filter详解

gdfyug的博客 125

redis常见问题

通俗易懂的Redis 一,为什么要使用redis? 1.提高性能,缓存查询速度要比数据库查询速度快(内存VS硬盘) 2.提高并发能力,缓存分担了部分请求,支持更高的并发请求 问题:如果我们的缓存挂掉了,所有的请求全部都跑去数据库了,怎么办? 二,造成雪崩的原因? 1.redis挂掉了,所有的请求全部走了数据库 2.对缓存设置了相同的过期时间,导致了一段时间内缓存全部失效,所有请求全部走了数据库 问...

sun_flower_x的博客 214
上一篇: redis实战——实现(类)短信登录
下一篇: 操作系统——进程调度
486过于烦躁
博客等级 码龄6年 17粉丝 34原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值