当前位置: 首页 > 后端技术 > Java

硬核-RedisBloomFilter原理与实践

时间:2023-04-01 18:28:58 Java

Redis中缓存击穿(失效)、缓存穿透、缓存雪崩如何解决?我们提到可以使用布隆过滤器来避免“缓存穿透”。码哥,Bloomfilter可以在什么场景下使用呢?例如,我们使用“MagoBeat”开发的“明天头条”APP看新闻。怎样才能避免每次推荐给用户的内容都重复,过滤已经阅读过的内容呢?你会说我们只需要记录每个用户看过的历史记录,每次推荐时查询数据库过滤已有的数据就可以实现去重。事实上,如果历史记录存储在关系数据库中,去重需要对数据库进行频繁的exists查询。当系统并发量很高时,数据库难以承受压力。代码兄,我可以使用缓存将历史数据存储在Redis中。绝对不会,这么多历史记录要浪费多少内存空间,所以这个时候我们可以使用布隆过滤器来解决这个去重问题。速度快又省内存,互联网开发必备!当遇到大量数据需要去重时,可以考虑Bloomfilter,以下场景:解决Redis缓存穿透问题(面试重点);邮件过滤,使用布隆过滤器实现邮件黑名单过滤;对爬虫爬取的网站进行过滤,爬取的网站不再爬取;推荐的新闻不再推荐;什么是布隆过滤器布隆过滤器(BloomFilter)由BurtonHowardBloom于1970年提出,是一种空间高效的概率数据结构,用于判断一个元素是否在一个集合中。当布隆过滤器说某个数据存在时,这个数据可能不存在;当布隆过滤器说某个数据不存在时,那么这个数据一定不存在。哈希表也可以用来判断一个元素是否在一个集合中,但是布隆过滤器只需要哈希表的1/8或1/4的空间复杂度就可以完成同样的问题。布隆过滤器可以插入元素,但不能删除已有元素。元素越多,假阳性率(falsepositiverate)越大,但是假阴性(missingnegative)是不可能的。布隆过滤器原理BloomFilter的算法是先分配一块内存空间作为位数组,数组的位的初始值全部设置为0。在添加元素时,使用k个独立的Hash函数来计算,然后将元素Hashmap的K个位置全部置1。检测key是否存在,还是用这k个Hash函数计算k个位置。如果所有位置都为1,则说明key存在,否则不存在。如下图所示:hash函数会发生碰撞,所以Bloomfilter会误判。这里的误报率指的是BloomFilter判断一个key存在,但实际上并不存在的概率,因为它存储的是key的Hash值,而不是key的值。所以有概率存在这样的key,虽然内容不一样,但是多次Hash后的Hash值是一样的。对于BloomFilter判断不存在的key,100%不存在。如果key存在,那么它在每个Hash之后对应的Hash值位置一定是1,而不是0。Bloomfilter不一定存在。代码兄,为什么不允许删除元素?删除是指对应的k位需要置0,可能是其他元素对应的位。所以remove会引入漏报,这是绝对不允许的。当Redis集成BloomfilterRedis4.0正式提供插件机制,Bloomfilter正式登场。以下网址可以下载官方编译好的可扩展模块。https://redis.com/redis-enter...码哥推荐使用Redis6.x版本,最低4.x版本集成Bloomfilter。使用以下命令检查版本。Mage安装的版本是6.2.6。redis-server-vRedisserverv=6.2.6sha=00000000:0malloc=libcbits=64build=b5524b65e12bbef5下载我们自己编译安装,需要到github上下载,目前release版本是v2.2.14,下载地址:https://github.com/RedisBloom...解压编译解压tar-zxvfRedisBloom-2.2.14.tar编译插件cdRedisBloom-2.2.14make编译成功,会看到redisbloom.so文件.安装集成需要修改redis.conf文件,添加loadmodule配置,重启Redis。loadmodule/opt/app/RedisBloom-2.2.14/redisbloom.so如果是集群,需要在配置中加入各个实例的配置文件。指定配置文件,启动Redis:redis-server/opt/app/redis-6.2.6/redis.conf加载成功页面如下:客户端连接Redis测试。BF.ADD--添加一个元素到BloomfilterBF.EXISTS--判断元素是否在BloomfilterBF.MADD--添加多个元素到BloomfilterBF.MEXISTS--判断多个元素是否InBloomfilterRedisBloomfilter实战下面我们用Bloomfilter来解决缓存穿透问题,缓存穿透:就是有一个特殊的请求去查询一个不存在的数据,也就是这个数据在Redis中不存在,在Redis中不存在数据库。当用户购买产品并创建订单时,我们向mq发送消息并将订单ID添加到布隆过滤器。在加入布隆过滤器之前,我们通过BF.RESERVE命令手动创建一个名称为orderserror_rate=0.1,初始容量为10000000的布隆过滤器:#BF.RESERVE{key}{error_rate}{capacity}[EXPANSION{expansion}][NONSCALING]BF.RESERVEorders0.110000000key:filtername;error_rate:预期错误率,默认0.1,值越低,所需空间越大;capacity:初始容量,默认100,当实际元素个数超过这个初始化容量时,误报率增加。EXPANSION:可选参数,当加入Bloomfilter的数据达到初始容量时,Bloomfilter会自动创建一个子filter,子filter的大小为上一个filter的大小乘以expansion;expansion默认值为2,也就是说Bloomfilter的expansion默认是2倍的expansion;NONSCALING:可选参数,设置此后,当加入布隆过滤器的数据达到初始容量时,过滤器将不再扩容,并抛出异常((错误)ERRnonscalingfilterisfull),说明:BloomFilter的扩展是通过增加BloomFilter的层数来完成的。每增加一层,可能会遍历多层BloomFilter完成查询,每层的容量是上一层的两倍(默认)。如果不使用BF.RESERVE命令创建,而是使用Redis自动创建的Bloomfilter,默认error_rate为0.01,capacity为100。Longfilter的error_rate越小,需要的存储空间越大.对于不需要太精确的场景,error_rate可以设置的稍微大一点。布隆过滤器的容量设置太大会浪费存储空间,设置太小又会影响准确率。因此,在使用之前,必须尽可能准确地估计出元素的数量,并且需要加入一定的冗余度。空间以避免实际元素可能意外地比设置值高很多。将订单ID添加到过滤器#BF.ADD{key}{item}BF.ADDorders10086(integer)1使用BF.ADD将元素10086添加到名为orders的布隆过滤器中。如果同时添加多个元素,使用BF.MADDkey{item...},如下:BF.MADDorders10087100891)(integer)12)(integer)1判断订单是否存在#BF.EXISTS{key}{item}BF.EXISTSorders10086(integer)1BF.EXISTS判断一个元素是否存在于BloomFilter中,返回值=1表示存在。如果需要批量检查布隆过滤器是否存在多个元素,使用BF.MEXISTS,返回值为数组:1:存在;0:不存在。#BF.MEXISTS{key}{item}BF.MEXISTSorders100100891)(integer)02)(integer)1一般来说,我们可以通过三个指令来避免缓存:BF.RESERVE、BF.ADD、BF.EXISTS穿透问题。码兄,如何查看创建的Bloomfilter信息?用BF.INFO键查??看,如下:BF.INFOorders1)Capacity2)(integer)100000003)Size4)(integer)77941845)Numberoffilters6)(integer)17)Numberofitemsinserted8)(integer)39)Expansionrate10)(integer)2返回值:Capacity:预设容量;Size:实际入住,但如何计算还需进一步确认;Numberoffilters:过滤层数;Numberofitemsinserted:Already实际插入的元素个数;Expansionrate:子过滤器扩展因子(默认2);码兄,布隆过滤器怎么删除?目前,BloomFilter不支持删除,但CuckooFilter支持。布隆过滤器通常在插入项目时表现出更好的性能和可扩展性(因此,如果您经常向数据集添加项目,布隆过滤器可能是理想的选择)。Cuckoo过滤器在检查操作上速度更快,并且还允许删除。有兴趣的可以看看:https://oss.redis.com/redisbl...)码哥,我想知道你是怎么掌握这么多技术的?其实我只是看了官方文档,做了一些简单的处理。本文实际内容以Redis官方文档中的上述示例为准:https://oss.redis.com/redisbl...。遇到问题要耐心地从官方文档中寻找答案,培养自己阅读问题和定位问题的能力。RedissionBloomfilter实战码哥示例代码基于SpringBoot2.1.4,代码地址:https://github.com/MageByte-Z...。添加Redission依赖:org.redissonredisson-spring-boot-starter3.16.7使用Springboot默认的Redis配置Redission的配置方法:spring:application:name:redissionredis:host:127.0.0.1port:6379ssl:false创建布隆过滤器@ServicepublicclassBloomFilterService{@AutowiredprivateRedissonClientredissonClient;/***创建布隆过滤器Filter*@paramfilterName过滤器名称*@paramexpectedInsertions预测插入数量*@paramfalseProbability误报率*@param*@return*/publicRBloomFiltercreate(StringfilterName,longexpectedInsertions,doublefalseProbability){RBloomFilterbloomFilter=redissonClient.getBloomFilter(filterName);bloomFilter.tryInit(expectedInsertions,falseProbability);返回布隆过滤器;}}单元测试@Slf4j@RunWith(SpringRunner.class)@SpringBootTest(classes=RedissionApplication.class)publicclassBloomFilterTest{@AutowiredprivateBloomFilterServicebloomFilterService;@TestpublicvoidtestBloomFilter(){//预期的插入次数longexpectedInsertions=10000L;//错误率doublefalsefalseProbability=0.01;RBlongFilter=bloomFilterService.create("ipBlackList",expectedInsertions,falseProbability);//布隆过滤器添加元素for(longi=0;ibloomFilter=redisson.getClusteredBloomFilter("sample");参考1.https://blog.csdn.net/u010066...2.https://juejin.cn/book/684473...3.https://www.cnblogs.com/heiha...4.https://www.cnblogs.com/allen...5.https://oss.redis.com/redisbl...6.https://oss.redis.com/redisbl...7.https://redis.com/blog/rebloo...