Redis系列1:深刻理解高性能Redis的本质

Redis系列2:数据持久化提高可用性

Redis系列3:高可用之主从架构

Redis系列4:高可用之Sentinel(哨兵模式)

Redis系列5:深入分析Cluster 集群模式

追求性能极致:Redis6.0的多线程模型

追求性能极致:客户端缓存带来的革命

Redis系列8:Bitmap实现亿万级数据计算

Redis系列9:Geo 类型赋能亿级地图位置计算

Redis系列10:HyperLogLog实现海量数据基数统计

Redis系列11:内存淘汰策略

Redis系列12:Redis 的事务机制

Redis系列13:分布式锁实现

Redis系列14:使用List实现消息队列

Redis系列15:使用Stream实现消息队列

Redis系列16:聊聊布隆过滤器(原理篇)

1 介绍

布隆过滤器(Bloom Filter)是 Redis 4.0 版本提供的新功能,我们一般将它当做插件加载到 Redis 服务器中,给 Redis 提供强大的去重功能。

它是一种概率性数据结构,可用于判断一个元素是否存在于一个集合中。相比较之 Set 集合的去重功能,布隆过滤器空间上能节省 90% +,不足之处是去重率大约在 99% 左右,那就是有 1% 左右的误判率,这种误差是由布隆过滤器的自身结构决定的。

  • 优点:空间效率和查询时间都比一般的算法要好的多
  • 缺点:有一定的误识别率和删除困难

2 使用场景介绍

我们在遇到数据量大的时候,为了去重并避免大批量的重复计算,可以考虑使用 Bloom Filter 进行过滤。

具体常用的经典场景如下:

  • 解决大流量下缓存穿透的问题,参考笔者这篇《一次缓存雪崩的灾难复盘》。
  • 过滤被屏蔽、拉黑、减少推荐的信息,一般你在浏览抖音或者百度App的时候,看到不喜欢的会设置减少推荐、屏蔽此类信息等,都可以采用这种原理设计。
  • 各种名单过滤,使用布隆过滤器实现第一层的白名单或者黑名单过滤,可用于各种AB场景。

下面以缓存穿透为解决目标进行讲解。

3 实战介绍

缓存穿透是指访问一个不存在的key,缓存不起作用,请求会穿透到DB,流量井喷时会导致DB挂掉。

比如 我们查询用户的信息,程序会根据用户的编号去缓存中检索,如果找不到,再到数据库中搜索。如果你给了一个不存在的编号:XXXXXXXX,那么每次都比对不到,就透过缓存进入数据库。

这样风险很大,如果因为某些原因导致大量不存在的编号被查询,甚至被恶意伪造编号进行攻击,那将是灾难。

解决方案质疑就是在缓存之前在加一层 BloomFilter :

  • 把存在的key记录在BloomFilter中,在查询的时候先去 BloomFilter 去查询 key 是否存在,如果不存在则说明数据库和缓存都没有,就直接返回,
  • 存在再走查缓存 ,投入数据库去查询,这样减轻了数据库的压力。

3.1 巨量查询信息案例解析

下面以火车票订购和查询为案例进行说明,如果火车票被恶意攻击,模拟了一模一样的火车票订单编号,那很可能通过大量的请求穿透过缓存层把数据库打雪崩了,所以使用布隆过滤器为服务提供一层保障。

具体的做法就是,我们在购买火车票成功的时候,把订单号的ID写入(异步或者消息队列的方式)到布隆过滤器中,保障后续的查询都在布隆过滤器中走一遍再进到缓存中去查询。火车票订单Id同步到 Bloom Filter 的步骤如下:

3.2 创建Bloom Filter的方式

创建 Bloom Filter 的语法如下:

# BF.RESERVE {key} {error_rate} {capacity} [EXPANSION {expansion}] [NONSCALING]
BF.RESERVE ticket_orders 0.01 1000000
  • key:布隆过滤器的名字,这边指的是创建一个名为 ticket_orders 的过滤器。
  • error_rate:期望的错误率,默认值为 0.1,值越低,需要的空间越大。就像我们上一节说的,空间越大碰撞的可能性越低。
  • capacity:初始的空间容量,默认值为 100,当实际元素的数量超过这个初始化容量时,碰撞的可能性越高,误判率也越高。
  • EXPANSION:可选参,数据达到初始容量后,布隆过滤器会自动创建一个子过滤器,大小为上一个过滤器乘以 expansion。expansion 的默认值为 2,也就是说默认扩容2倍;
  • NONSCALING:可选参,指的是数据达到初始容量后,不会扩容过滤器,并抛出异常((error) ERR non scaling filter is full)。

而上面那句命令是,通过BF.RESERVE命令手动创建一个名字为 ticket_orders,错误率为 0.01 ,初始容量为 1000000 的布隆过滤器。

这边需要注意的一些点是:

  • error_rate 越小,对碰撞的容忍度越小,需要的存储空间就越大。如果允许一定比例的不准确,对精确度要求不高的场景,error_rate 可以设的稍大一点。
  • capacity 设置的过大,会浪费存储空间,设置过小,准确度不高。所以评估的时候需要精准一点,既要避免浪费空间也要保证准确比例。

3.3 添加火车票订单Id到Bloom Filter

# BF.ADD {key}  {value ... }

# 添加单个订单号
BF.ADD ticket_orders 2023061008795
(integer) 1 # 添加多个订单号
BF.MADD ticket_orders 2023061008796 2023061008797 2023061008798
1) (integer) 1
2) (integer) 1
3) (integer) 1

以上的语句是将已经订好的车票订单号存储到Bloom Filter中,包括一次存储单个和一次存储多个。

3.4 判断火车票订单Id是否存在

# BF.EXISTS {key} {value} ,存在的话返回 1,不存在返回 0
BF.EXISTS ticket_orders 2023061008795
(integer) 1 # 批量判断多个值是否存在于布隆过滤器,语句如下:
BF.MEXISTS ticket_orders 2023061008796 2023061008797 2023061008798
1) (integer) 0
2) (integer) 1
3) (integer) 0

BF.EXISTS 判断一个元素是否存在于 Bloom Filter中,返回值 = 1 表示存在,返回值 = 0 表示不存在。可以一次性判断单个元素,或者一次性判断多个元素。

3.5 Review已建的布隆过滤器列表

# 使用 BF.INFO {key} 语法查看

BF.INFO ticket_orders
1) Capacity
2) (integer) 1000000
3) Size
4) (integer) 3
5) Number of filters
6) (integer) 1
7) Number of items inserted
8) (integer) 3
9) Expansion rate
10) (integer) 2

返回值解析:

Capacity:预设容量,我们前面设置了1000000。

Size:实际占用情况,我们前面设置了3个值:2023061008796、 2023061008797、 2023061008798。

Number of filters:过滤器的层数。

Number of items inserted:实际已插入的元素数量。

Expansion rate:子过滤器扩容的系数,咱们前面创建的时候未设值,所以这边是默认 2。

综上,我们通过 BF.RESERVE、BF.ADD、BF.EXISTS、BF.INFO 等几个指令就能实现布隆过滤器的建设,避免缓存穿透的情况发生。

因为你查询缓存的时候,必然你先到Bloom Filter中先过滤一次,这样就不会因为无效的key把缓存打穿。

4 程序实现说明

Spring Boot版本: 2.5.x。

4.1 添加 Redission Maven 依赖

如果实际情况可以使用更高版本

<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.17.1</version>
</dependency>

4.2 Spring boot Yaml中的 Redission 配置

spring:
application:
name: redission
redis:
cluster:
nodeAddresses: [
"redis://127.0.0.1:8000",
"redis://127.0.0.1:8001",
"redis://127.0.0.1:8002",
"redis://127.0.0.1:8003",
"redis://127.0.0.1:8004",
"redis://127.0.0.1:8005"
]
password: ********
single:
address: "redis://127.0.0.1:6379"
database: 6

4.3 创建布隆过滤器相关

@Service
public class BloomFilterService { @Autowired
private RedissonClient redissonClient; /**
* 创建布隆过滤器
* @param filterKey 过滤器名称,等同上面的key
* @param expectedCapacity 预计元素容量,等同于上面的capacity
* @param falseRate 允许的误判率,等同于上面的error_rate
* @param <T>
* @return
*/
public <T> RBloomFilter<T> create(String filterKey, long expectedCapacity, double falseRate) {
// 集群模式
RClusteredBloomFilter<T> bloomFilter = redissonClient.getClusteredBloomFilter(filterKey);
// 以下是单实例模式
// RBloomFilter<T> bloomFilter = redissonClient.getBloomFilter(filterKey);
bloomFilter.tryInit(expectedCapacity, falseRate);
return bloomFilter;
}
}

4.4 测试实现

    @Autowired
private BloomFilterService bloomFilterService; @Test
public void testBloomFilter() {
// 预计元素容量 1000000
long expectedCapacity = 1000000L;
// 错误率
double falseRate = 0.01;
RBloomFilter<Long> bloomFilter = bloomFilterService.create("ticket_orders", expectedCapacity, falseRate); // 元素增加测试并输出统计
for (long idx = 0; idx < expectedCapacity; idx++) {
bloomFilter.add(idx);
}
long eleCount = bloomFilter.count();
log.info("eleCount = {}.", elementCount);
}

5 总结

本篇介绍了布隆过滤器的几种实现场景。

并以火车票订单信息查询为案例进行说明,如何使用布隆过滤器避免缓存穿透,避免被恶意攻击。

Redis系列17:聊聊布隆过滤器(实践篇)的更多相关文章

  1. 浅谈redis的HyperLogLog与布隆过滤器

    首先,HyperLogLog与布隆过滤器都是针对大数据统计存储应用场景下的知名算法. HyperLogLog是在大数据的情况下关于数据基数的空间复杂度优化实现,布隆过滤器是在大数据情况下关于检索一个元 ...

  2. Redis解读(4):Redis中HyperLongLog、布隆过滤器、限流、Geo、及Scan等进阶应用

    Redis中的HyperLogLog 一般我们评估一个网站的访问量,有几个主要的参数: pv,Page View,网页的浏览量 uv,User View,访问的用户 一般来说,pv 或者 uv 的统计 ...

  3. redis 系列17 持久化 AOF

    一.概述 除了上篇介绍的RDB持久化功能之外,Redis还提供了AOF(Append Only File)持久化功能.与RDB保存数据库中的键值对来记录数据库状态不同,AOF是通过保存redis服务器 ...

  4. 09 redis中布隆过滤器的使用

    我们在使用新闻客户端看新闻时,它会给我们不停地推荐新的内容,它每次推荐时要去重,去掉那些已经看过的内容.问题来了,新闻客户端推荐系统如何实现推送去重的? 会想到服务器记录了用户看过的所有历史记录,当推 ...

  5. 详细解析Redis中的布隆过滤器及其应用

    欢迎关注微信公众号:万猫学社,每周一分享Java技术干货. 什么是布隆过滤器 布隆过滤器(Bloom Filter)是由Howard Bloom在1970年提出的一种比较巧妙的概率型数据结构,它可以告 ...

  6. Redis详解(十三)------ Redis布隆过滤器

    本篇博客我们主要介绍如何用Redis实现布隆过滤器,但是在介绍布隆过滤器之前,我们首先介绍一下,为啥要使用布隆过滤器. 1.布隆过滤器使用场景 比如有如下几个需求: ①.原本有10亿个号码,现在又来了 ...

  7. Redis中的布隆过滤器及其应用

    什么是布隆过滤器 布隆过滤器(Bloom Filter)是由Howard Bloom在1970年提出的一种比较巧妙的概率型数据结构,它可以告诉你某种东西一定不存在或者可能存在.当布隆过滤器说,某种东西 ...

  8. 细谈布隆过滤器及Redis实现

    ​ 何为布隆过滤器? 本质上是一种数据结构,是1970年由布隆提出的.它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数).可以用于检索一个元素是否在一个集合中. 数据结构: 布隆过 ...

  9. 第三百五十八节,Python分布式爬虫打造搜索引擎Scrapy精讲—将bloomfilter(布隆过滤器)集成到scrapy-redis中

    第三百五十八节,Python分布式爬虫打造搜索引擎Scrapy精讲—将bloomfilter(布隆过滤器)集成到scrapy-redis中,判断URL是否重复 布隆过滤器(Bloom Filter)详 ...

  10. 布隆过滤器(Bloom Filter)简要介绍

    一种节省空间的概率数据结构 布隆过滤器可以理解为一个不怎么精确的 set 结构,当你使用它的 contains 方法判断某个对象是否存在时,它可能会误判.但是布隆过滤器也不是特别不精确,只要参数设置的 ...

随机推荐

  1. 有了HTTP,为啥还要用RPC

    既然有 HTTP 请求,为什么还要用 RPC 调用? 一直以来都没有深究过RPC和HTTP的区别,不都是写一个服务然后在客户端调用么? HTTP和RPC最本质的区别,就是 RPC 主要是基于 TCP/ ...

  2. innerHTML和outerHTML区别

     1.innerHTML <body> <p>你好</p> <div id="test"><h5>就是喜欢你</h ...

  3. 更优雅的OrientDB Java API

    OrientDB API v1.0.0(OrientDB 3.x) Gitee OrientDB介绍 OrientDB是一个开源的NoSQL数据库管理系统,同时也是一款高性能的图数据库,支持ACID事 ...

  4. java开发技术栈如何选型

    前言 2023泰山景区门票免费政策是从1月21日到3月31,今天4.1起不再免费啦,泰山的人.山和系统终于平安的渡劫过去! 洪峰时疯狂的抢票.各类攻击,分销MT两次凌晨抗洪事件,我及其我的团队又一次得 ...

  5. webrtc QOS笔记三 Nack机制浅析

    nack源码浅析 nack源码浅析 Video Nack nack模块 nack list keyFrame list & recovered list nack 发送的策略 nack 模块的 ...

  6. kubernetes核心实战(六)--- ConfigMap

    8.ConfigMap 抽取应用配置,并且可以自动更新 创建配置文件 [root@k8s-master-node1 ~/yaml/test]# vim configmap.yaml [root@k8s ...

  7. pysimplegui之进度表one_line_progress_meter

    我们的代码中都有循环.'等待,看着文本窗口中滚动过去的计数器不是很快乐吗?一行代码如何获得一个进度表,其中包含有关您的代码的统计信息? one_line_progress_meter(title, c ...

  8. [Git/GitLab]使用SSH远程登录GitLab/GitHub

    1 前言 近日,换了台新电脑. 今日,正要更新(git pull)GitLab的源码时,在配置(用户名,邮箱,密码git config --global -l)完全无误的情况下,却报出如下错误: $ ...

  9. DES算法图解、密码学

  10. django渲染模版时比实际少了8小时?

    这是因为django的时间是UTC时间. 我们通过改配置文件将其改成本地时间 修改配置文件 # 将时间从UTC转化成当前时间 TIME_ZONE = 'Asia/Shanghai' # USE_TZ ...