【敲开BAT的大门】系列:避免缓存击穿的利器之Bloom Filter

C++网络爬虫实战:从零构建高性能数据采集引擎 网络爬虫作为数据采集的核心技术,其本质是通过程序自动化访问网页并提取结构化信息。其工作原理通常基于HTTP协议发送请求、接收响应,然后解析HTML/XML文档以定位和抽取目标数据。在技术选型上,Python因其丰富的库生态而广为人知,但对于追求极致性能、精准内存控制和深度系统集成的场景,C++展现出独特价值。它通过编译为原生机器码,消除了运行时开销,在处理高并发I/O和计算密集型解析任务时,能实现更高的吞吐量和更低的延迟。结合libcurl或cpr库进行网络通信,并利用libxml2进行XPath解析,开发 阅读详情

引言

在开发或者面试过程中,时常遇到过海量数据需要查询,秒杀时缓存击穿怎么避免等等这样的问题呢?掌握好本篇介绍的知识点将有助于你在之后的工作、面试中策马奔腾。

Bloom Filter概念

Bloom Filter,即传说中的布隆过滤器。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
在这里插入图片描述

Bloom Filter的原理

布隆过滤器的原理是,当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。这就是布隆过滤器的基本思想。

Bloom Filter跟单哈希函数Bit-Map不同之处在于:Bloom Filter使用了k个哈希函数,每个字符串跟k个bit对应。从而降低了冲突的概率。

在这里插入图片描述

缓存击穿

在这里插入图片描述
Bloom Filter在避免缓存击穿中的应用方法:简而言之就是先把我们数据库的数据都加载到我们的过滤器中,比如数据库的id现在有:1,2,3…,n,以上面的原理图为例,将id所有值 经过三次hash之后,将hash得到的结果对应的地方由0修改为1。这样做之后,每次请求过来通过id查询数据,如果缓存没有命中,再在过滤器中查询,通过同样的hash算法将请求的id值进行运算,获得三个索引值,如果有任何一个对应索引的值为0,说明MySQL中也不存在该id,则直接报错返回。
试想想这样做的好处是什么?假设这样的一种场景,如果有1000个参数非法请求同时访问(所谓参数非法是指数据库也不存在这类的值,比如id全为负值),缓存中都没有命中,此时如果这1000个请求同时打到DB,数据库层是扛不住的,所以此时Bloom Filter就显得十分必要。

Bloom Filter的缺点

Bloom Filter之所以能做到在时间和空间上的效率比较高,是因为牺牲了判断的准确率、删除的便利性

  • 存在误判,可能要查到的元素并没有在容器中,但是hash之后得到的k个位置上值都是1。如果Bloom Filter中存储的是黑名单,那么可以通过建立一个白名单来存储可能会误判的元素。

  • 删除困难。一个放入容器的元素映射到bit数组的k个位置上是1,删除的时候不能简单的直接置为0,可能会影响其他元素的判断。

Bloom Filter 实现

在实现Bloom Filter时,绕不过的两点就是hash函数的选取以及bit数组的大小。
对于一个确定的场景,我们预估要存的数据量为n,期望的误判率为fpp,然后需要计算我们需要的Bit数组的大小m,以及hash函数的个数k,并选择hash函数。
1 Bit数组大小选择
  根据预估数据量n以及误判率fpp,bit数组大小的m的计算方式:
在这里插入图片描述
2 哈希函数选择
​ 由预估数据量n以及bit数组长度m,可以得到一个hash函数的个数k:
在这里插入图片描述
3 应用测试
本篇采用的是Google的Bloom Filter,首先需要引入jar包:

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

测试分两步:

1、往过滤器中放五千万个数,然后去验证这五千万个数是否能顺利通过过滤器;

2、另外找一万个不在过滤器中的数,检查Bloom Filter误判的几率。

import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;

/**
 * @author Carson Chu
 * @date 2020/3/15 14:48
 * @description 布隆过滤器测试样例
 */
public class BloomFilterTest {
    private static int capacity = 50000000;
    private static BloomFilter<Integer> bf = BloomFilter.create(Funnels.integerFunnel(), capacity);
//    private static BloomFilter<Integer> bf = BloomFilter.create(Funnels.integerFunnel(), total, 0.001);

    public static void main(String[] args) {
        // 初始化50000000条数据到过滤器中
        for (int i = 0; i < capacity; i++) {
            bf.put(i);
        }

        // 匹配已在过滤器中的值,是否有匹配不上的
        for (int i = 0; i < capacity; i++) {
            if (!bf.mightContain(i)) {
                System.out.println("有坏人逃脱了~~~");
            }
        }

        // 匹配不在过滤器中的10000个值,有多少匹配出来
        int count = 0;
        for (int i = capacity; i < capacity + 10000; i++) {
            if (bf.mightContain(i)) {
                count++;
            }
        }
        System.out.println("误命中的数量:" + count);
    }
}

在这里插入图片描述
运行结果表示,遍历这五千万个在过滤器中的数时,都被识别出来了。一万个不在过滤器中的数,误伤了297个,误判率是2.9%左右。
如果想要降低误判率该怎么做呢,不要急,源码为我们提供了这一机制:

@CheckReturnValue
    public static <T> BloomFilter<T> create(Funnel<? super T> funnel, int expectedInsertions) {
        return create(funnel, (long)expectedInsertions);
    }

    @CheckReturnValue
    public static <T> BloomFilter<T> create(Funnel<? super T> funnel, long expectedInsertions) {
        return create(funnel, expectedInsertions, 0.03D);
    }
 @CheckReturnValue
    public static <T> BloomFilter<T> create(Funnel<? super T> funnel, int expectedInsertions, double fpp) {
        return create(funnel, (long)expectedInsertions, fpp);
    }

    @CheckReturnValue
    public static <T> BloomFilter<T> create(Funnel<? super T> funnel, long expectedInsertions, double fpp) {
        return create(funnel, expectedInsertions, fpp, BloomFilterStrategies.MURMUR128_MITZ_64);
    }
    
    /* create()方法的最底层实现 */
	@VisibleForTesting
    static <T> BloomFilter<T> create(Funnel<? super T> funnel, long expectedInsertions, double fpp, BloomFilter.Strategy strategy) {
        Preconditions.checkNotNull(funnel);
        Preconditions.checkArgument(expectedInsertions >= 0L, "Expected insertions (%s) must be >= 0", new Object[]{expectedInsertions});
        Preconditions.checkArgument(fpp > 0.0D, "False positive probability (%s) must be > 0.0", new Object[]{fpp});
        Preconditions.checkArgument(fpp < 1.0D, "False positive probability (%s) must be < 1.0", new Object[]{fpp});
        Preconditions.checkNotNull(strategy);
        if (expectedInsertions == 0L) {
            expectedInsertions = 1L;
        }

        long numBits = optimalNumOfBits(expectedInsertions, fpp);
        int numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, numBits);

        try {
            return new BloomFilter(new BitArray(numBits), numHashFunctions, funnel, strategy);
        } catch (IllegalArgumentException var10) {
            throw new IllegalArgumentException("Could not create BloomFilter of " + numBits + " bits", var10);
        }
    }

BloomFilter一共四个create方法,不过最终都是走向第四个。看一下每个参数的含义:
funnel:数据类型(一般是调用Funnels工具类中的)
expectedInsertions:期望插入的值的个数
fpp:错误率(默认值为0.03)
strategy:Bloom Filter的算法策略

错误率越大,所需空间和时间越小,错误率越小,所需空间和时间约大。

Bloom Filter的应用场景

  • cerberus在收集监控数据的时候, 有的系统的监控项量会很大, 需要检查一个监控项的名字是否已经被记录到DB过了,如果没有的话就需要写入DB;
  • 爬虫过滤已抓到的url就不再抓,可用Bloom Filter过滤;
  • 垃圾邮件过滤。如果用哈希表,每存储一亿个email地址,就需要1.6GB的内存(用哈希表实现的具体办法是将每一个email地址对应成一个八字节的信息指纹,然后将这些信息指纹存入哈希表,由于哈希表的存储效率一般只有 50%,因此一个email地址需要占用十六个字节。一亿个地址大约要 1.6GB,即十六亿字节的内存)。因此存贮几十亿个邮件地址可能需要上百GB的内存。而Bloom Filter只需要哈希表 1/8到 1/4 的大小就能解决同样的问题。

总结

布隆过滤器主要是在解决缓存击穿问题的时候引出来的,了解他的原理并能实习运用,在开发和面试中都是大有裨益的。

点点关注,不会迷路

多智能体通信调度:解决协作卡顿与丢包的核心技术 多智能体系统(MAS)中,智能体协作失效往往并非源于单体决策能力不足,而是通信机制缺乏节律性设计——在带宽受限、延迟敏感的真实场景下,‘想说就说’必然导致拥塞、超时与状态不同步。其本质是通信资源稀缺性与协作实时性之间的矛盾,需通过事件驱动调度、重要性量化评估和动态时隙分配等机制实现按需通信。该技术广泛应用于机器人集群控制、分布式传感器网络、无人机编队及工业AGV调度等场景,尤其适用于已具备基础Agent能力但上线后频发丢包、响应滞后、状态不同步等问题的工程落地阶段。 阅读详情

相关推荐

数据库之Redis运维手册(私人整理)

1、redis配置文件 #cat 8000.conf daemonize no #[yes/no]默认值no,该参数用于定制redis服务是否以守护模式运行。 pidfile /var/run/redis.pid #默认值/var/run/redis.pid,指定redis服务的进程号文件路径,以守护模式运行时需要配置本参数; port 8000 #默认值6379,指定redis服务...

企业实战系列集 ●●● https://ximenjianxue.blog.youkuaiyun.com 8638

爬虫教程( 4 ) --- scrapy-redis、集群、curl-cffi、scrapy-stealth、aio-scrapy、omnicrawl-core、反检测(反爬)浏览器

分布式爬虫 scrapy-redis、集群

freeking101的博客 6826

软考系统架构设计师论文涉及知识点之Redis(16)

软考系统架构设计师论文涉及知识点之Redis(16)

phmatthaus的专栏 105

HashMap底层原理与性能调优:从哈希碰撞到红黑树治理

HashMap是Java中最基础也最易被低估的核心集合类,其本质是基于哈希表的键值映射结构,通过扰动函数、位运算索引和动态扩容机制实现平均O(1)时间复杂度。原理层面涉及哈希函数设计、数组+链表+红黑树的混合存储结构,以及JDK7到JDK8在死循环防护、树化阈值(8)、扩容算法(局部迁移)等关键演进。技术价值在于以空间换时间的工程权衡——如TreeNode复用降低内存抖动、CAS+synchronized提升并发吞吐。典型应用场景包括缓存实现、临时数据聚合与高频读写中间件;而当面临哈希碰撞退化、扩容阻塞或并

weixin_33888907的博客 514

linux离线安装nginx

linux离线安装nginx

zhang_130914036的博客 9347

USB设备识别码VID和PID详解:如何查询和自定义你的设备ID

本文详细解析了USB设备识别码VID和PID的核心作用、查询方法及自定义配置技巧。VID(供应商识别码)和PID(产品识别码)是USB设备的唯一身份证,用于驱动匹配和功能识别。文章提供了Windows和Linux系统下的查询方法,并探讨了如何合法获取和自定义VID/PID,适合硬件开发者和DIY爱好者参考。

weixin_29168675的博客 1459

Flink 学习日志 03:自定义一个SourceFunction,完成对Redis中数据的读取

本文介绍了在Flink中自定义SourceFunction读取Redis数据的完整流程:通过Jedis客户端连接WSL2中的Redis服务,编写RedisSource实现循环读取所有Key-value并逐条输出,结合Thread.sleep()实现每秒一条的流式效果。关键步骤包括环境准备、添加Jedis依赖、配置IDEA运行时加载provided依赖,并注意生产环境中应避免使用阻塞的keys *,改用scan命令。最终实现动态读取、实时打印,验证了Flink与Redis的数据集成能力。

木木的学习笔记 421

双脉冲测试中IGBT驱动电阻和驱动电容的研究

  在LTspice中没有双脉冲波形发生器,要做一个双脉冲的波形只能用电压源来进行特殊设置,不是很方便,为此我写了一个双脉冲波形发生器,有需要的可以下载。双脉冲波形发生器。   如下,该元器件可以自定义延迟时间Td,第一个脉冲宽度T1,第二个脉冲宽度T3,两个脉冲间隔T2,脉冲循环个数Ncycle以及高低电平Vhigh,Vlow。       IGBT开通时Rg的影响: 如果增大Rg: ①开通延...

gaoyong_wang的博客 8354

Docker多阶段构建实现基于Alpine的Redis的编译安装

【代码】Docker多阶段构建实现基于Alpine的Redis的编译安装。

一直在努力学习的菜鸟 323

【声纳技术】声呐时间反转镜技术:从被动聚焦到主动空间复用

时间反转镜技术以其独特的物理机制,为水声通信与声呐探测提供了一条绕过传统自适应均衡器的新路径。被动时间反转利用接收端的空间分集实现脉冲压缩,结构简洁、计算高效;主动时间反转则通过发射端预编码,将多径能量引导至目标位置,为多用户下行链路的空间复用奠定了物理基础。从Kuperman等人在海洋中首次验证声学时间反转镜,到TR-MIMO声呐的湖试验证,再到VTRM-CRK多用户通信系统的海试成功,该技术已从理论概念逐步走向工程实用。

u012133341的博客 55

MFC Windows 程序设计[105]之彩色滚动条(附源码)

MFC是微软公司提供的一个类库(class libraries),用以减少应用程序开发人员的工作量。其中包含大量Windows句柄封装类和很多Windows的内建控件和组件的封装类。有很多初学MFC的小伙伴们都觉得MFC比较难学,看书吧,看了就忘了,网上的一些教程又是写的含糊不清,故而学习起来很吃力...... 故此,本专栏亲自研究,实践,由易到难,层层深入,将调试过程中所遭所遇进行详细讲解,注意事项进行一一列举,希望能够帮助到各位初学MFC的小伙伴,避免走弯路,费时费力。

hnjzfwy的博客 518

Spring Boot 工程通用能力建设:声明式事务定制与 Redis 缓存实践

本文探讨Spring Boot中声明式事务的进阶实践,通过解耦“切点”与“事务属性”,实现灵活控制。核心包括:基于方法名的只读规则(如get*、find*自动设为只读),组合事务属性源确保@Transactional注解优先于命名规则,以及自定义切点实现事务统一织入。该方案提升代码可维护性,避免误写,同时支持特殊场景覆盖,构建更健壮的事务治理体系。

BADAO_LIUMANG_QIZHI的博客 259

西安交通大学软件学院——分布式系统练习题(综合题)

摘要:本文探讨了分布式系统的核心特性与关键问题。首先阐述了分布式系统的8种透明性特性,包括访问、定位、迁移等。然后通过具体案例分析了分布式事务的回滚机制、域名解析的迭代与递归过程、不同分布式系统的异同比较。重点讨论了RPC通信中客户端/服务器崩溃的处理方法,包括消灭、再生和到期三种策略。最后介绍了Lamport时间戳算法在进程通信中的应用及问题解决方案。全文涵盖了分布式系统设计中的透明性、可靠性、一致性等核心问题。

qq_73044452的博客 204

Redis 数据类型全解 - ZSet:两个编码,七种玩法(五)

Redis ZSet 是唯一支持按可变分值有序且成员可快速定位的数据结构,适用于排行榜、延迟队列、滑动窗口限流等场景。其核心价值在于用 score 表达业务语义,但使用不当易引发性能问题:轮询导致 CPU 飙升、全量查询拖垮实例、非原子操作致超发。关键陷阱包括 ZRANGE 0 -1 误用为分值范围、GT/LT 条件逻辑误解、双精度精度上限导致分值冲突。正确使用需掌握原子写入(如 ZADD GT)、避免轮询、合理控制范围,并注意编码类型对复杂度的影响。

IT规划师 522

javascript 酷炫时钟

javascript 酷炫时钟

liuhao9999的博客 631

探索数据库领域 SQL 的 JSON 数据处理技巧

在当今数字化时代,JSON(JavaScript Object Notation)作为一种轻量级的数据交换格式,被广泛应用于各种领域,如 Web 应用、移动应用、大数据处理等。数据库作为数据存储和管理的核心,需要具备处理 JSON 数据的能力。本文章的目的在于深入探索 SQL 语言在数据库中处理 JSON 数据的技巧,涵盖从基础的 JSON 数据查询到复杂的 JSON 数据操作,帮助读者全面了解和掌握 SQL 处理 JSON 数据的方法。

2502_91592937的博客 924

RabbitMQ 消费端幂等:唯一索引、条件更新、Redis 去重三种方案的真实代价

MQ 只承诺至少投递一次,重复消息只能消费端自己吃掉。这篇对比唯一索引、条件更新、Redis 短路三种去重方案在 AI 任务场景下的实际代价,附三个踩过的坑。

zhangpan 207

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

redis

AOF 文件体积更大,恢复慢,因为恢复的时候要逐条重放命令。AOF 还有重写机制,用来消除冗余,比如同一个 key 连续 set a、set b、set c、set d,重写之后就只保留一条 set key d,精简文件。消息会被分发到每一个实例的队列,所有实例监听自己的队列,收到消息之后,各自删除本机 Caffeine 里面对应的本地缓存,实现一处修改、全部实例本地缓存失效。HyperLogLog 不存原始数据,基于概率算法估算独立数量,内存极小,支持海量 UV 统计,缺点是结果有微小误差,拿不到明细。

2401_84390420的博客 168

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

精雕软件(JDpaint) 5.5 绿色版

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

Redis Lua 原子限流:Lua 脚本、竞态条件与并发控制

Lua是轻量级可嵌入脚本语言,Redis内置Lua5.1解释器,支持通过EVAL或SCRIPTLOAD执行脚本,实现多命令原子操作。利用redis.call()调用Redis命令,结合KEYS和ARGV传参,可将读取、判断、更新等操作封装为原子逻辑,有效避免限流中的竞态条件。固定窗口限流通过Lua脚本实现计数与过期控制,保证操作连续性。但脚本不可回滚,需避免复杂逻辑;执行时间过长会阻塞Redis,且需满足Cluster哈希槽约束。实际应用中还需考虑故障降级策略。

Joy T的博客 131

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

如何在本地 Docker 容器中运行 Redis,并使用 Redis Insight 和 Redis CLI 进行管理

本文教程介绍了如何使用Docker本地运行Redis服务器与RedisInsight,实现数据的可视化管理。通过RedisInsight和RedisCLI,学习了对哈希、字符串、列表、集合、有序集、流及JSON等数据类型的增删改查(CRUD)操作,涵盖HSET、GET、SADD、XADD等核心命令,帮助开发者高效掌握Redis基本用法。

csdn_aspnet的专栏,请点击博客主页右上角三个点中的私信联系 276

Redis 从 0 到 1(二十五):连接池又打满了——PoolSize 公式、泄漏排查与 PoolStats

流量一涨就 connection pool timeout,加 PoolSize 治标不治本——可能是连接泄漏。本文给出 PoolSize 计算公式(QPS×RT),用 PoolStats 的 4 个指标判断池太小还是没归还,并演示典型泄漏代码与修复。

2501_92769340的博客 587

Redis 从 0 到 1(二十七):JSON 体积是 Protobuf 的 3.7 倍?3 种序列化的 Go 基准测试

缓存对象直接用 json.Marshal,1 亿条 key 多花 20G 内存和 3 倍带宽。本文对 JSON / MessagePack / Protobuf 做 Go 基准测试(体积、编解码耗时、CPU),给出不同场景的选型表和统一序列化封装。

2501_92769340的博客 184

RedisLockRegistry redis分布式锁(加锁/续期)使用

如果后续锁竞争更重,或者需要更完善的 Watchdog、公平锁、Pub/Sub 唤醒,可以逐步演进到 Redisson。所以它不是检测“业务方法有没有返回”,而是检测。同一个 lockKey,多次。,key 就是你传入的锁名。对象,不会重复 new。

weixin_46136610的博客 204

Redis 键管理命令详解(C++ 版):EXISTS/DEL/KEYS/TTL/TYPE

这是《Redis C++ 实战笔记》系列的第 02 篇。上一篇立了调用约定,从这一篇开始进入正题。先讲"键管理"——它不属于任何一种数据类型,但它操作的是键本身,后面每一种类型都会用到。

wbs_scy的博客 182

Redis 缓存机制:缓存一致性、穿透、击穿与雪崩

Redis缓存通过CacheAside模式减少数据库压力,提升读取性能。核心问题包括缓存一致性、穿透、击穿与雪崩。更新数据库后删除缓存可缓解不一致,但存在竞态风险;缓存穿透可通过空值缓存或布隆过滤器防御;击穿可用互斥锁或逻辑过期应对;雪崩则需随机TTL、高可用部署及限流降级。命中率、延迟等指标需持续监控,关键业务应避免依赖缓存强一致性。

Joy T的博客 164

多存储协同架构:JPA + MyBatis + Elasticsearch + Redis 的职责分工与通用实现

本文阐述了在高并发订单系统中,通过JPA、MyBatis、Elasticsearch与Redis的多存储协同架构实现职责分离:JPA负责事务性写操作与简单查询;MyBatis处理复杂联查与报表统计;Elasticsearch承载海量数据下的多维检索与聚合;Redis用于缓存、防回滚暂存及分布式锁。各存储分工明确,通过异步同步机制联动,兼顾一致性、性能与扩展性,构建高效稳定的系统架构。

BADAO_LIUMANG_QIZHI的博客 246
上一篇: 【搞定面试官】系列:Redis基础
下一篇: 【数据结构与算法精炼】LeetCode No.136 : Single Number,由一道算法题想到的关于XOR运算的使用
少侠露飞
博客等级 码龄9年 214粉丝 117原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值