10.04 使用redis解决新闻推送中的去重问题

使用 HyperLogLog 数据结构来进行估数,它非常有价值,可以解决很多精确度不高的统计需求。

但是如果我们想知道某一个值是不是已经在 HyperLogLog 结构里面了,它就无能为力了,它只提供了 pfadd 和 pfcount 方法,没有提供 pfcontains 这种方法。

有个使用场景,现在新闻客户端的推送,需要有一个去重功能,推送过的就不能再推送了。

那么如何实现这种去重功能?

使用redis解决新闻推送中的去重问题

你可能会想在后台数据库里保存一下看过的列表,但是当用户数太多,而且用户看到的列表也非常大的时候,性能可能会成为问题。

实际上,如果历史记录存储在关系数据库里,去重就需要频繁地对数据库进行 exists 查询,当系统并发量很高时,数据库是很难扛住压力的。

你可能又想到了缓存,但是如此多的历史记录全部缓存起来,那得浪费多大存储空间啊?而且这个存储空间是随着时间线性增长,你撑得住一个月,你能撑得住几年么?但是不缓存的话,性能又跟不上,这该怎么办?

这个时候就要介绍今天的主角了--布隆过滤器

布隆过滤器是这样一个神奇的东西,他是专门用来解决这种去重问题的,而且所需的内存相比set可以节省90%。

可以把它理解成一个不怎么精确的set。

你可以问他A存不存在?当他说不存在时,这肯定不存在。当他说存在时,也有可能是不存在的。这是因为可能存在一个和A很像的A1,导致布隆过滤器产生了误判。

套用到新闻推送去重的场景,我们把用户看过的新闻存起来,推送时,通过查一下是否看过,说没看过的,肯定就没看过,可以推送。

Redis 官方提供的布隆过滤器到了 Redis 4.0 提供了插件功能之后才正式登场。布隆过滤器作为一个插件加载到 Redis Server 中,给 Redis 提供了强大的布隆去重功能。

布隆过滤器有二个基本指令,bf.add 添加元素,bf.exists 查询元素是否存在,它的用法和 set 集合的 sadd 和 sismember 差不多。注意 bf.add 只能一次添加一个元素,如果想要一次添加多个,就需要用到 bf.madd 指令。同样如果需要一次查询多个元素是否存在,就需要用到 bf.mexists 指令。

使用redis解决新闻推送中的去重问题

Java 客户端 Jedis-2.x 没有提供指令扩展机制,可以使用 lettuce

误判率大约 1% 多点。你也许会问这个误判率还是有点高啊,有没有办法降低一点?答案是有的。 我们上面使用的布隆过滤器只是默认参数的布隆过滤器,它在我们第一次 add 的时候自动创建。Redis 其实还提供了自定义参数的布隆过滤器,需要我们在 add 之前使用bf.reserve指令显式创建。如果对应的 key 已经存在,bf.reserve会报错。

bf.reserve有三个参数,分别是 key, error_rate和initial_size。错误率越低,需要的空间越大。initial_size参数表示预计放入的元素数量,当实际数量超出这个数值时,误判率会上升。 所以需要提前设置一个较大的数值避免超出导致误判率升高。如果不使用 bf.reserve,默认的error_rate是 0.01,默认的initial_size是 100。

布隆过滤器的initial_size估计的过大,会浪费存储空间,估计的过小,就会影响准确率,用户在使用之前一定要尽可能地精确估计好元素数量,还需要加上一定的冗余空间以避免实际元素可能会意外高出估计值很多。 布隆过滤器的error_rate越小,需要的存储空间就越大,对于不需要过于精确的场合,error_rate设置稍大一点也无伤大雅。比如在新闻去重上而言,误判率高一点只会让小部分文章不能让合适的人看到,文章的整体阅读量不会因为这点误判率就带来巨大的改变。

布隆过滤器的原理

使用redis解决新闻推送中的去重问题

大体上是这样的,布隆过滤器会开辟一个大型的位数组,另外有多个不同的hash函数,比如这里有f、g、h三个hash函数。每次来一个值,都对其key执行所有的hash函数,将得到的值对位数组长度进行取模得到一个位置。将所有的位置置为1

这样下一个值过来判断是否存在时,同样也进行多个hash函数,得到位置。如果位置中有一个不是1,那这个值一定是不存在的。

全都是1说明高度相似,是极有可能存在的。

如果这个位数组比较稀疏,判断正确的概率就会很大,如果这个位数组比较拥挤,判断正确的概率就会降低。具体的概率计算公式比较复杂,感兴趣可以阅读扩展阅读,非常烧脑。

布隆过滤器的其它应用

在爬虫系统中,我们需要对 URL 进行去重,已经爬过的网页就可以不用爬了。但是 URL 太多了,几千万几个亿,如果用一个集合装下这些 URL 地址那是非常浪费空间的。这时候就可以考虑使用布隆过滤器。它可以大幅降低去重存储消耗,只不过也会使得爬虫系统错过少量的页面。

使用redis解决新闻推送中的去重问题

布隆过滤器在 NoSQL 数据库领域使用非常广泛,我们平时用到的 HBase、Cassandra 还有 LevelDB、RocksDB 内部都有布隆过滤器结构,布隆过滤器可以显著降低数据库的 IO 请求数量。当用户来查询某个 row 时,可以先通过内存中的布隆过滤器过滤掉大量不存在的 row 请求,然后再去磁盘进行查询。

邮箱系统的垃圾邮件过滤功能也普遍用到了布隆过滤器,因为用了这个过滤器,所以平时也会遇到某些正常的邮件被放进了垃圾邮件目录中,这个就是误判所致,概率很低。

使用redis解决新闻推送中的去重问题


分享到:


相關文章: