简单地说,我们现在是Redis

了解更多

如何使用Redis内容过滤



回到博客

最近,我的一位同事提出了一个请求:如何在Redis中创建内容过滤器?他想把收到的信息和一系列的脏话过滤掉。这是一个非常常见的用例——任何接受用户输入的应用程序都可能希望至少粗略地扫描不合适的单词。我的第一个想法是他应该使用布卢姆过滤器,但我想知道是否有更好的选择,所以我想测试我的假设。我从我的字典里查到了一大堆坏话这里并对詹姆斯·乔伊斯(James Joyce)的《尤利西斯》(Ulysses)进行了初步测试(这是一本公共领域的大书,有很多有趣的语言!)

对于那些不熟悉Bloom过滤器的人来说,它是一种可以测试存在的概率(p11c)数据结构。这种方法乍一看可能有点吓人,但实际上相当简单:将项目添加到Bloom过滤器中,然后检查Bloom过滤器中是否存在。布鲁姆过滤器是概率性的,因为它们不能确定地回答存在-它们要么告诉你一个项目肯定不在过滤器中,要么可能在过滤器中。换句话说,盛开过滤器误报是可能的,但是错误的否定是不可能的。Bloom过滤器真正酷的地方在于,它们只使用了一小部分空间(相对于实际存储项目),并且可以以适度的计算复杂性确定可能存在的情况。我的妻子是一位文学教授,她很高兴我选择了尤利西斯作为测试对象,提醒我这本书的主人公叫布鲁姆。拟合。

回到手头的问题。您想要做的是在给定消息中取出所有单词,并找到与已知坏词的列表的十字路口。然后,您可以确定问题,拒绝或屏幕的单词。

为了使事情尽可能易于使用并避免任何网络瓶颈,我在Lua中实现了所有解决方案,并在Redis中运行它们。我使用了一个Lua表,其中键作为不合适的词,值为“true”,以防止在所有脚本中发出不必要的Redis命令。

关于消息处理的注释:在脚本中,我使用了一种非常简单的方法,只需拆分空间。Admittedly, this alone would not work in practice because you would need to handle punctuation, numbers, etc. In this case, it worked fine since we were treating the scripts the same way and weren’t trying to measure the speed of Lua string splitting.

使用“设置”进行内容过滤

除了Bloom过滤器之外,解决此问题的另一种方法是使用Redis集。我做的第一件事是使用SADD命令和1600多个参数将坏单词添加到SET键中。我把坏单词列表用双引号括起来,然后加上SADD和key的前缀,最后把它们都保存到一个文本文件(badwords set.txt)中。我用这个redis cli管道技巧执行它:

$ cat ./badwords-set.txt | redis-cli -a YourRedisPassword .txt

一种简单的方法是将消息分解为单词,并在消息中的每个单词上运行sismember以识别单词。我最初的想法是,纯粹的命令次数将使这种效率低下。让我们来看看它的实施方式:

我们在这里所做的就是拆分第一个参数(在本例中是消息),然后遍历单词并为每个单词运行SISMEMBER。如果SISMEMBER返回1,那么我们知道它是一个坏单词,并将它添加到一个表中,然后返回给Redis。

早些时候,我谈到了坏词和信息的词之间的交集。让我们用SADD和SINTER来做这个。再次强调,这似乎是大量的命令,但在Lua中是这样的:

这是与以前相同的拆分例程,但我没有为每个单词都发出SISMEMBER,而是使用SADD将其添加到临时Redis集合中。然后,我使用SINTER命令将这个临时集合与我们的脏话相交。最后,我去掉了带有UNLINK的临时设置,并返回了烧结结果。使用此方法时,请记住您正在向Redis写信。这对持久性和分片有一些影响,因此Lua脚本的相对简单性隐藏了一些操作复杂性。

使用Bloom过滤内容

对于我的第二种方法,我将所有不好的单词添加到Bloom过滤器中。像SADD,你可以一次添加多个项目布鲁姆过滤器与BF。MADD命令。实际上,我获取了包含所有坏单词和SADD命令的文本文件,并替换了命令(现在是BF.MADD)和键。

使用这种解决方案,重要的是要记住Bloom过滤器是有概率的——它们可能返回假阳性。根据您的用例,这可能需要对结果进行二次检查,或者只是将它们用作手动流程的屏幕。

首先,让我们看一看Lua脚本,以检查是否存在坏词:

类似于SISMEMBER方法,我们可以将传入的字符串分割成唯一的单词,然后运行BF。存在于每个单词上。男朋友。EXISTS将返回一个1,如果它可能在筛选器中存在,或者返回一个0,如果它肯定不存在。该解决方案将消息中的每个单词与过滤器进行比较,将任何不好的单词插入到表中,并在最后将其返回。

我们还可以使用ReBloom模块,该模块提供BF.MEXISTS命令来检查多个字符串和过滤器。它的功能与BF.EXISTS相同,但允许您一次做更多的事情。然而,在这一次,事情变得有点棘手。运行多个命令(甚至来自Lua)会给Redis带来一些开销,因此理想情况下,您希望运行尽可能少的命令,让Redis实际检查数据,而不是花费资源解释数千个命令。最好通过最大化参数数量来最小化命令调用(至少这是理论)。

我的第一次尝试是使用unpack函数将所有尤利西斯(45834)的独特单词放入BF.MEXISTS的一个命令中。这并没有奏效,因为Lua的固定堆栈大小远远小于我的45k参数。这迫使我把我的男朋友MEXIST分成了更小的词块。反复试验帮助我发现,在Lua抱怨之前,我可以安全地向BF.MEXISTS命令添加大约7000个参数。这是以Lua脚本的复杂性为代价的,但这意味着我可以运行66个命令,而不是超过45k。

下面是脚本最终的样子:

正如您所看到的,这已经开始成为大量的Lua代码。它遍历所有单词以获得7000个块,然后运行bloomcheck函数,该函数检查过滤器中的值块,并将这些坏单词添加到临时表中。最后,它返回所有可能的坏话。

测试

从CLI运行Lua脚本并不好玩,因为您正在处理大的争论,事情会变得粘。因此,我使用Benchmark.js框架编写了一个小节点.js脚本以多次运行我的测试,并在每次运行时获得正确的统计数据。除了尤利西斯,我还在1,000和500字的Lorem IPSum随机文本上运行它,以获得更短的信息。所有这些测试都在我的开发笔记本电脑上运行,因此它们的边际意义超出了相对差异。

让我们看看结果:

尤利西斯坏词检测

意思 标准差 误差范围
烧结(第4) 0.252477065875

女士(252.478)
0.06331450637410971 0.026739796333907027
成员(第一) 0.17936577490625秒(179.356毫秒) 0.034054703041132506 0.011799352611322679
梅克斯(第二) 0.19251170375862064(192.512毫秒) 0.05158925843435185 0.01961960405252156
男朋友(第三) 0.21505565530769238(215.056毫秒) 0.041931428091619434 0.016940265013395354

1000字Lorem Ipsom坏字检测

意思 标准差 误差范围
烧结矿(第二) 0.0005533430385665705(0.55毫秒) 0.00004782064272951418 0.00001047916037135063
成员(第一) 0.0004876821613932592(0.49ms) 0.00007453275890098545 0.000018260525930741436
bf.mexists(第3) 0.0005750750453153705(0.58毫秒) 0.00008423403428371551 0.000019593611749182812
男朋友。(4)存在 0.0006705186154166669(0.67ms) 0.000035960357334688716 0.000008810287546998735

Lorem ipsom的500个字坏词检测

意思 标准差 误差范围
烧结(第4) 0.0007199102990995225(0.72ms) 0.00026621924403751274 0.00006281610037055691
成员(第一) 0.0005127359608872813(0.51ms) 0.000042812670318675034 0.000010328955827771373
梅克斯(第二) 0.0005467939453535285(0.55毫秒) 0.00004135890147573503 0.00000948775881996386
男朋友(第三) 0.0007052701613834288(0.71毫秒) 0.00013297426439345952 0.000032836237873305535

结论

我认为这些结果非常有趣。我的初始假设对于这种情况不正确 - 绽放过滤器并不最快。所有测试中,普通的老僧人跑得更快。除此之外的一些重要的外卖会包括:

  • 《尤利西斯》是一种不寻常的工作,你可能不会有很多James joyce提交史诗小说
  • 所有1000和500字测试都产生了子毫秒的响应,将它们放在“即时”范围内。响应时间因分数毫秒而变化,因此差异不会察觉。
  • 如果SISMEMBER有能力接受可变参数(它没有),那么在这种情况下可能会更快。
  • 500字的意思中有些比1000字的意思长。我的怀疑是,在这些相对较小的工作负载上花费的大部分时间都是在开销上。

那你为什么要用布卢姆过滤器呢?存储效率。让我们看最后一件事:

127.0.0.1:6379>内存使用错误设置
(整数)68409
127.0.0.1:6379>内存使用不良词
(整数)4228

在这里,“badwords”是Bloom过滤器,而“badwordset”是Set结构。布鲁姆滤镜比它小了16倍以上。在本例中,大小并不重要,因为它是一个列表。但是,想象一下,如果一个网站上的每个用户都有他或她自己的坏词过滤器呢?如果使用Set结构,效果会更好,但如果使用Bloom过滤器,效果会更好。这个测试表明反应时间非常相似。

Baidu