使用Node.js和Redis了解Bloom过滤器的魔力

在正确的用例中,Bloom过滤器似乎很神奇。 这是一个大胆的声明,但是在本教程中,我们将探索奇怪的数据结构,如何最好地使用它,以及一些使用Redis和Node.js的实际示例。

布隆过滤器是一种概率的单向数据结构。 在这种情况下,“过滤器”一词可能会造成混淆。 filter表示它是一个活跃的事物,一个动词,但将其视为存储(名词)可能会更容易。 使用简单的Bloom过滤器,您可以做两件事:

  1. 添加一个项目。
  2. 检查以前是否未添加项目。

这些是要理解的重要限制-您不能删除项目,也不能在Bloom过滤器中列出这些项目。 另外,您不能肯定地说过去是否有项目添加到过滤器中。 这就是布隆过滤器的概率性质出现的地方—可能出现误报,但不会出现误报。 如果正确设置了过滤器,则误报极少发生。

存在布隆过滤器的变体,它们增加了其他功能,例如删除或缩放,但也增加了复杂性和局限性。 在继续使用变体之前,首先了解简单的Bloom过滤器很重要。 本文将仅介绍简单的Bloom过滤器。

有了这些限制,您将获得许多好处:固定大小,基于哈希的加密和快速查找。

设置布隆过滤器时,请为其指定大小。 此大小是固定的,因此,如果过滤器中有一项或十亿个项目,它将永远不会超过指定的大小。 当您向过滤器中添加更多项目时,误报的机会就会增加。 如果您指定了一个较小的过滤器,则此误报率将比较大的过滤器增加得更快。

布隆过滤器建立在单向哈希的概念上。 就像正确存储密码一样,Bloom过滤器使用哈希算法来确定传递给它的项目的唯一标识符。 从本质上讲,哈希是无法逆转的,并且由看似随机的字符串表示。 因此,如果有人访问了Bloom过滤器,它将不会直接显示任何内容。

最后,布隆过滤器非常快速。 与其他方法相比,该操作所涉及的比较少得多,并且可以轻松地将其存储在内存中,从而防止数据库性能下降。

现在您已经了解了Bloom过滤器的局限性和优点,下面让我们看一下可以使用它们的情况。

建立

我们将使用Redis和Node.js来说明Bloom过滤器。 Redis是Bloom过滤器的存储介质。 它是快速的,内存中的,并且具有一些特定的命令( GETBITSETBIT ),这些命令可提高实现效率。 我假设您在系统上安装了Node.js,npm和Redis。 您的Redis服务器应该在localhost的默认端口上运行,我们的示例才能正常工作。

在本教程中,我们不会从头开始实现过滤器; 取而代之的是,我们将重点关注npm中预构建模块的实际用途: bloom-redis 。 bloom-redis有一套非常简洁的方法: addcontainsclear

如前所述,Bloom过滤器需要一种哈希算法来生成商品的唯一标识符。 Bloom-redis使用了著名的MD5算法,该算法虽然可能不是Bloom滤波器的理想选择(有点慢,对比特的过大杀伤力),但效果很好。

唯一的用户名

用户名,尤其是在URL中标识用户的用户名,必须唯一。 如果您构建了一个允许用户更改用户名的应用程序,那么您可能会希望使用从未使用的用户名,以避免用户名的混淆和割伤。

如果没有Bloom过滤器,则您需要引用一个表,该表包含曾经使用过的每个用户名,并且从规模上讲,这可能会非常昂贵。 布隆过滤器使您可以在用户每次采用新名称时添加一个项目。 当用户检查是否使用了用户名时,您要做的就是检查Bloom过滤器。 它可以绝对确定地告诉您是否先前已添加了所请求的用户名。 过滤器可能会错误地返回未使用的用户名,但这在谨慎方面是错误的,并且不会造成实际伤害(除了用户可能无法声明“ k3w1d00d47”之外) 。

为了说明这一点,让我们用Express构建一个快速的REST服务器。 首先, 创建您的package.json文件,然后运行以下终端命令。

npm install bloom-redis --save

npm install express --save

npm install redis --save

Bloom-redis的默认选项的大小设置为2 MB。 在谨慎方面这是错误的,但是它很大。 设置Bloom过滤器的大小至关重要:太大会浪费内存,太小会导致误报率太高。 确定大小所涉及的数学运算非常复杂,超出了本教程的范围,但是值得庆幸的是,这里有一个Bloom Bloom大小计算器可以完成工作而不会破解教科书。

现在,如下创建您的app.js

var
  Bloom         =   require('bloom-redis'),
  express       =   require('express'),
  redis         =   require('redis'),
  
  app,
  client,
  filter;

//setup our Express server
app = express();

//create the connection to Redis
client = redis.createClient();


filter = new Bloom.BloomFilter({ 
  client    : client, //make sure the Bloom module uses our newly created connection to Redis
  key       : 'username-bloom-filter', //the Redis key
  
  //calculated size of the Bloom filter.
  //This is where your size / probability trade-offs are made
  //http://hur.st/bloomfilter?n=100000&p=1.0E-6
  size      : 2875518, // ~350kb
  numHashes : 20
});

app.get('/check', function(req,res,next) {
  //check to make sure the query string has 'username'
  if (typeof req.query.username === 'undefined') {
    //skip this route, go to the next one - will result in a 404 / not found
    next('route');
  } else {
   filter.contains(
     req.query.username, // the username from the query string
     function(err, result) {
       if (err) { 
        next(err); //if an error is encountered, send it to the client
        } else {
          res.send({ 
            username : req.query.username, 
            //if the result is false, then we know the item has *not* been used
            //if the result is true, then we can assume that the item has been used
            status : result ? 'used' : 'free' 
          });
        }
      }
    );
  }
});


app.get('/save',function(req,res,next) {
  if (typeof req.query.username === 'undefined') {
    next('route');
  } else {
    //first, we need to make sure that it's not yet in the filter
    filter.contains(req.query.username, function(err, result) {
      if (err) { next(err); } else {
        if (result) {
          //true result means it already exists, so tell the user
          res.send({ username : req.query.username, status : 'not-created' });
        } else {
          //we'll add the username passed in the query string to the filter
          filter.add(
            req.query.username, 
            function(err) {
              //The callback arguments to `add` provides no useful information, so we'll just check to make sure that no error was passed
              if (err) { next(err); } else {
                res.send({ 
                  username : req.query.username, status : 'created' 
                });
              }
            }
          );
        }
      }
    });
  }
});

app.listen(8010);

要运行此服务器: node app.js 转到浏览器并将其指向: https://localhost:8010/check?username=kyle 。 响应应为: {"username":"kyle","status":"free"}

现在,通过将浏览器指向http://localhost:8010/save?username=kyle 。 响应将是: {"username":"kyle","status":"created"} 。 如果返回地址http://localhost:8010/check?username=kyle ,则响应将为{"username":"kyle","status":"used"} 。 同样,返回http://localhost:8010/save?username=kyle将导致{"username":"kyle","status":"not-created"}

在终端上,您可以看到过滤器的大小: redis-cli strlen username-bloom-filter

现在,只有一项,它应该显示338622

现在,继续尝试使用/save路由添加更多用户名。 尝试任意数量。

如果然后再次检查大小,您可能会注意到大小略有增加,但并非每次添加都变大。 很好奇吧? 在内部,Bloom过滤器在用户名存储区保存的字符串的不同位置设置单个位(1/0)。 但是,它们不是连续的,因此,如果先在索引0处设置一个位,然后在索引10,000处设置一个位,则两者之间的所有值都将为0。对于实际用途,了解每个操作的精确机制一开始并不重要,只要知道是正常的,您在Redis中的存储将永远不会超过您指定的值。

新鲜内容

网站上的新鲜内容使用户回头客,那么您如何每次向用户展示新内容呢? 使用传统的数据库方法,您可以在表中添加带有用户标识符和故事标识符的新行,然后在决定显示内容时查询该表。 就像您想象的那样,您的数据库将快速增长,尤其是随着用户和内容的增长。

在这种情况下,假阴性(例如,不显示看不见的内容)几乎没有后果,这使得Bloom过滤器成为可行的选择。 乍一看,您可能会认为您需要为每个用户使用Bloom过滤器,但是我们将使用用户标识符和内容标识符的简单连接,然后将该字符串插入到我们的过滤器中。 这样,我们可以为所有用户使用单个过滤器。

在此示例中,让我们构建另一个显示内容的基本Express服务器。 每次您访问/show-content/any-username路线( any-username是任何URL安全值)时,都会显示一条新内容,直到该站点不存在为止。 在此示例中,内容是有关Gutenberg项目的前十本书的第一行。

我们将需要再安装一个npm模块。 从终端运行: npm install async --save

您的新app.js文件:

var
  async         =   require('async'),
  Bloom         =   require('bloom-redis'),
  express       =   require('express'),
  redis         =   require('redis'),
  
  app,
  client,
  filter,
  
  // From Project Gutenberg - opening lines of the top 10 public domain ebooks
  // https://www.gutenberg.org/browse/scores/top
  openingLines = {
    'pride-and-prejudice' : 
      'It is a truth universally acknowledged, that a single man in possession of a good fortune, must be in want of a wife.',
    'alices-adventures-in-wonderland' : 
      'Alice was beginning to get very tired of sitting by her sister on the bank, and of having nothing to do: once or twice she had peeped into the book her sister was reading, but it had no pictures or conversations in it' }

如果您仔细注意开发工具中的往返时间,您会发现请求带有用户名的单个路径越多,花费的时间就越长。 虽然检查过滤器需要固定的时间,但在此示例中,我们正在检查是否存在更多项目。 布隆过滤器只能告知您有限信息,因此您正在测试每个项目的存在。 当然,在我们的示例中,这非常简单,但是测试数百个项目的效率很低。

陈旧数据

在此示例中,我们将构建一个小型Express服务器,该服务器将执行以下两项操作:通过POST接受新数据,并显示当前数据(带有GET请求)。 将新数据发布到服务器后,应用程序将检查过滤器中是否存在该数据。 如果不存在,则将其添加到Redis中的集合中,否则将返回null。 GET请求将从Redis获取它,并将其发送到客户端。

这与前两种情况不同,因为误报是不可能的。 我们将使用布隆过滤器作为第一道防线。 给定Bloom过滤器的属性,我们只能肯定地确定过滤器中没有东西,因此在这种情况下,我们可以继续进行操作,让数据进入。如果Bloom过滤器返回的可能是过滤器中的数据,将对照实际数据源进行检查。

那么,我们可以获得什么呢? 我们获得了不必每次都与实际来源进行核对的速度。 在数据源很慢的情况下(外部API,pokey数据库,平面文件的中间),确实需要提高速度。 为了演示速度,我们在示例中添加150ms的实际延迟。 我们还将使用console.time / console.timeEnd记录Bloom过滤器检查和非Bloom过滤器检查之间的差异。

在此示例中,我们还将使用数量非常有限的位:仅1024。它将快速填充。 填满后,它将显示出越来越多的误报-随着误报率填满,您将看到响应时间增加。

该服务器使用与以前相同的模块,因此将app.js文件设置为:

var
  async           =   require('async'),
  Bloom           =   require('bloom-redis'),
  bodyParser      =   require('body-parser'),
  express         =   require('express'),
  redis           =   require('redis'),
  
  app,
  client,
  filter,
  
  currentDataKey  = 'current-data',
  usedDataKey     = 'used-data';
  
app = express();
client = redis.createClient();

filter = new Bloom.BloomFilter({ 
  client    : client,
  key       : 'stale-bloom-filter',
  //for illustration purposes, this is a super small filter. It should fill up at around 500 items, so for a production load, you'd need something much larger!
  size      : 1024,
  numHashes : 20
});

app.post(
  '/',
  bodyParser.text(),
  function(req,res,next) {
    var
      used;
      
    console.log('POST -', req.body); //log the current data being posted
    console.time('post'); //start measuring the time it takes to complete our filter and conditional verification process
    
    //async.series is used to manage multiple asynchronous function calls.
    async.series([
      function(cb) {
        filter.contains(req.body, function(err,filterStatus) {
          if (err) { cb(err); } else {
            used = filterStatus;
            cb(err);
          }
        });
      },
      function(cb) {
        if (used === false) {
          //Bloom filters do not have false negatives, so we need no further verification
          cb(null);
        } else {
          //it *may* be in the filter, so we need to do a follow up check
          //for the purposes of the tutorial, we'll add a 150ms delay in here since Redis can be fast enough to make it difficult to measure and the delay will simulate a slow database or API call
          setTimeout(function() {
            console.log('possible false positive');
            client.sismember(usedDataKey, req.body, function(err, membership) {
              if (err) { cb(err); } else {
                //sismember returns 0 if an member is not part of the set and 1 if it is.
                //This transforms those results into booleans for consistent logic comparison
                used = membership === 0 ? false : true;
                cb(err);
              }
            });
          }, 150);
        }
      },
      function(cb) {
        if (used === false) {
          console.log('Adding to filter');
          filter.add(req.body,cb);
        } else {
          console.log('Skipped filter addition, [false] positive');
          cb(null);
        }
      },
      function(cb) {
        if (used === false) {
          client.multi()
            .set(currentDataKey,req.body) //unused data is set for easy access to the 'current-data' key
            .sadd(usedDataKey,req.body) //and added to a set for easy verification later
            .exec(cb); 
        } else {
          cb(null);
        }
      }
      ],
      function(err, cb) {
        if (err) { next(err); } else {
          console.timeEnd('post'); //logs the amount of time since the console.time call above
          res.send({ saved : !used }); //returns if the item was saved, true for fresh data, false for stale data.
        }
      }
    );
});

app.get('/',function(req,res,next) {
  //just return the fresh data
  client.get(currentDataKey, function(err,data) {
    if (err) { next(err); } else {
      res.send(data);
    }
  });
});

app.listen(8012);

由于使用浏览器发布到服务器可能很棘手,因此让我们使用curl进行测试。

curl --data “your data goes here" --header "Content-Type: text/plain" http://localhost:8012/

快速的bash脚本可用于显示如何填充整个过滤器:

#!/bin/bash
for i in `seq 1 500`;
do
  curl --data “data $i" --header "Content-Type: text/plain" http://localhost:8012/
done

查看填充或完整过滤器很有趣。 由于这个很小,您可以使用redis-cli轻松查看它。 通过在添加项目之间从终端运行redis-cli get stale-filter ,您将看到单个字节增加。 每个字节的完整过滤器为\xff 。 此时,过滤器将始终返回正值。

结论

布隆过滤器不是万能的解决方案,但在正确的情况下,布隆过滤器可以为其他数据结构提供快速,高效的补充。一本书的用途是什么,

如果您仔细注意开发工具中的往返时间,您会发现请求带有用户名的单个路径越多,花费的时间就越长。 虽然检查过滤器需要固定的时间,但在此示例中,我们正在检查是否存在更多项目。 布隆过滤器只能告知您有限信息,因此您正在测试每个项目的存在。 当然,在我们的示例中,这非常简单,但是测试数百个项目的效率很低。

在此示例中,我们将构建一个小型Express服务器,该服务器将执行以下两项操作:通过POST接受新数据,并显示当前数据(带有GET请求)。 将新数据发布到服务器后,应用程序将检查过滤器中是否存在该数据。 如果不存在,则将其添加到Redis中的集合中,否则将返回null。 GET请求将从Redis获取它,并将其发送到客户端。

这与前两种情况不同,因为误报是不可能的。 我们将使用布隆过滤器作为第一道防线。 给定Bloom过滤器的属性,我们只能肯定地确定过滤器中没有东西,因此在这种情况下,我们可以继续进行操作,让数据进入。如果Bloom过滤器返回的可能是过滤器中的数据,将对照实际数据源进行检查。

那么,我们可以获得什么呢? 我们获得了不必每次都与实际来源进行核对的速度。 在数据源很慢的情况下(外部API,pokey数据库,平面文件的中间),确实需要提高速度。 为了演示速度,我们在示例中添加150ms的实际延迟。 我们还将使用console.time / console.timeEnd记录Bloom过滤器检查和非Bloom过滤器检查之间的差异。

在此示例中,我们还将使用数量非常有限的位:仅1024。它将快速填充。 填满后,它将显示出越来越多的误报-随着误报率填满,您将看到响应时间增加。

该服务器使用与以前相同的模块,因此将app.js文件设置为:

javascript var async = require('async'),Bloom = require('bloom-redis'),bodyParser = require('body-parser'),express = require('express'),redis = require(' redis'),

应用,客户端,过滤器,

currentDataKey ='当前数据',usedDataKey ='已使用数据';

app = express(); 客户端= redis.createClient();

filter = new Bloom.BloomFilter({client:client,key:'stale-bloom-filter',//出于说明目的,这是一个超小的过滤器。应填充约500个项目,因此对于生产负荷,你需要更大的东西!size:1024,numHashes:20});

app.post('/',bodyParser.text(),function(req,res,next){使用的var;

app.get('/',function(req,res,next){//仅返回新数据客户端。get(currentDataKey,function(err,data){if(err){next(err);} else { res.send(data);}});});

app.listen(8012); ```

由于使用浏览器发布到服务器可能很棘手,因此让我们使用curl进行测试。

curl --data “your data goes here" --header "Content-Type: text/plain" http://localhost:8012/

快速的bash脚本可用于显示如何填充整个过滤器:

bash #!/bin/bash for i in `seq 1 500`; do curl --data “data $i" --header "Content-Type: text/plain" http://localhost:8012/ done

查看填充或完整过滤器很有趣。 由于这个很小,您可以使用redis-cli轻松查看它。 通过在添加项目之间从终端运行redis-cli get stale-filter ,您将看到单个字节增加。 每个字节的完整过滤器为\xff 。 此时,过滤器将始终返回正值。

布隆过滤器不是万能的解决方案,但在正确的情况下,布隆过滤器可以为其他数据结构提供快速,有效的补充。 爱丽丝想

如果您仔细注意开发工具中的往返时间,您会发现请求带有用户名的单个路径越多,花费的时间就越长。 虽然检查过滤器需要固定的时间,但在此示例中,我们正在检查是否存在更多项目。 布隆过滤器只能告知您有限信息,因此您正在测试每个项目的存在。 当然,在我们的示例中,这非常简单,但是测试数百个项目的效率很低。

在此示例中,我们将构建一个小型Express服务器,该服务器将执行以下两项操作:通过POST接受新数据,并显示当前数据(带有GET请求)。 将新数据发布到服务器后,应用程序将检查过滤器中是否存在该数据。 如果不存在,则将其添加到Redis中的集合中,否则将返回null。 GET请求将从Redis获取它,并将其发送到客户端。

这与前两种情况不同,因为误报是不可能的。 我们将使用布隆过滤器作为第一道防线。 给定Bloom过滤器的属性,我们只能肯定地确定过滤器中没有东西,因此在这种情况下,我们可以继续进行操作,让数据进入。如果Bloom过滤器返回的可能是过滤器中的数据,将对照实际数据源进行检查。

那么,我们可以获得什么呢? 我们获得了不必每次都与实际来源进行核对的速度。 在数据源很慢的情况下(外部API,pokey数据库,平面文件的中间),确实需要提高速度。 为了演示速度,我们在示例中添加150ms的实际延迟。 我们还将使用console.time / console.timeEnd记录Bloom过滤器检查和非Bloom过滤器检查之间的差异。

在此示例中,我们还将使用数量非常有限的位:仅1024。它将快速填充。 填满后,它将显示出越来越多的误报-随着误报率填满,您将看到响应时间增加。

该服务器使用与以前相同的模块,因此将app.js文件设置为:

javascript var async = require('async'),Bloom = require('bloom-redis'),bodyParser = require('body-parser'),express = require('express'),redis = require(' redis'),

应用,客户端,过滤器,

currentDataKey ='当前数据',usedDataKey ='已使用数据';

app = express(); 客户端= redis.createClient();

filter = new Bloom.BloomFilter({client:client,key:'stale-bloom-filter',//出于说明目的,这是一个超小的过滤器。应填充约500个项目,因此对于生产负荷,你需要更大的东西!size:1024,numHashes:20});

app.post('/',bodyParser.text(),function(req,res,next){使用的var;

app.get('/',function(req,res,next){//仅返回新数据客户端。get(currentDataKey,function(err,data){if(err){next(err);} else { res.send(data);}});});

app.listen(8012); ```

由于使用浏览器发布到服务器可能很棘手,因此让我们使用curl进行测试。

curl --data “your data goes here" --header "Content-Type: text/plain" http://localhost:8012/

快速的bash脚本可用于显示如何填充整个过滤器:

bash #!/bin/bash for i in `seq 1 500`; do curl --data “data $i" --header "Content-Type: text/plain" http://localhost:8012/ done

查看填充或完整过滤器很有趣。 由于这个很小,您可以使用redis-cli轻松查看它。 通过在添加项目之间从终端运行redis-cli get stale-filter ,您将看到单个字节增加。 每个字节的完整过滤器为\xff 。 此时,过滤器将始终返回正值。

布隆过滤器不是万能的解决方案,但是在正确的情况下,布隆过滤器可以为其他数据结构提供快速,高效的补充,而无需图片或对话?

如果您仔细注意开发工具中的往返时间,您会发现请求带有用户名的单个路径越多,花费的时间就越长。 虽然检查过滤器需要固定的时间,但在此示例中,我们正在检查是否存在更多项目。 布隆过滤器只能告知您有限信息,因此您正在测试每个项目的存在。 当然,在我们的示例中,这非常简单,但是测试数百个项目的效率很低。

在此示例中,我们将构建一个小型Express服务器,该服务器将执行以下两项操作:通过POST接受新数据,并显示当前数据(带有GET请求)。 将新数据发布到服务器后,应用程序将检查过滤器中是否存在该数据。 如果不存在,则将其添加到Redis中的集合中,否则将返回null。 GET请求将从Redis获取它,并将其发送到客户端。

这与前两种情况不同,因为误报是不可能的。 我们将使用布隆过滤器作为第一道防线。 给定Bloom过滤器的属性,我们只能肯定地确定过滤器中没有东西,因此在这种情况下,我们可以继续进行操作,让数据进入。如果Bloom过滤器返回的可能是过滤器中的数据,将对照实际数据源进行检查。

那么,我们可以获得什么呢? 我们获得了不必每次都与实际来源进行核对的速度。 在数据源很慢的情况下(外部API,pokey数据库,平面文件的中间),确实需要提高速度。 为了演示速度,我们在示例中添加150ms的实际延迟。 我们还将使用console.time / console.timeEnd记录Bloom过滤器检查和非Bloom过滤器检查之间的差异。

在此示例中,我们还将使用数量非常有限的位:仅1024。它将快速填充。 填满后,它将显示出越来越多的误报-随着误报率填满,您将看到响应时间增加。

该服务器使用与以前相同的模块,因此将app.js文件设置为:

javascript var async = require('async'),Bloom = require('bloom-redis'),bodyParser = require('body-parser'),express = require('express'),redis = require(' redis'),

应用,客户端,过滤器,

currentDataKey ='当前数据',usedDataKey ='已使用数据';

app = express(); 客户端= redis.createClient();

filter = new Bloom.BloomFilter({client:client,key:'stale-bloom-filter',//出于说明目的,这是一个超小的过滤器。应填充约500个项目,因此对于生产负荷,你需要更大的东西!size:1024,numHashes:20});

app.post('/',bodyParser.text(),function(req,res,next){使用的var;

app.get('/',function(req,res,next){//仅返回新数据客户端。get(currentDataKey,function(err,data){if(err){next(err);} else { res.send(data);}});});

app.listen(8012); ```

由于使用浏览器发布到服务器可能很棘手,因此让我们使用curl进行测试。

curl --data “your data goes here" --header "Content-Type: text/plain" http://localhost:8012/

快速的bash脚本可用于显示如何填充整个过滤器:

bash #!/bin/bash for i in `seq 1 500`; do curl --data “data $i" --header "Content-Type: text/plain" http://localhost:8012/ done

查看填充或完整过滤器很有趣。 由于这个很小,您可以使用redis-cli轻松查看它。 通过在添加项目之间从终端运行redis-cli get stale-filter ,您将看到单个字节增加。 每个字节的完整过滤器为\xff 。 此时,过滤器将始终返回正值。

Bloom过滤器不是万能的解决方案,但是在正确的情况下,Bloom过滤器可以为其他数据结构提供快速,有效的补充。','a-christmas-carol':'Marley死了:首先。 ,'metamorphosis':“有一天早上,当Gregor Samsa从梦trouble以求的梦中醒来时,他发现自己躺在床上变成了可怕的害虫。”,“ frankenstein”:“您会高兴地听到,一场灾难并没有带来灾难。您曾以如此邪恶的预兆看到的企业。”,“哈克贝利芬冒险”:“您不要

如果您仔细注意开发工具中的往返时间,您会发现请求带有用户名的单个路径越多,花费的时间就越长。 虽然检查过滤器需要固定的时间,但在此示例中,我们正在检查是否存在更多项目。 布隆过滤器只能告知您有限信息,因此您正在测试每个项目的存在。 当然,在我们的示例中,这非常简单,但是测试数百个项目的效率很低。

在此示例中,我们将构建一个小型Express服务器,该服务器将执行以下两项操作:通过POST接受新数据,并显示当前数据(带有GET请求)。 将新数据发布到服务器后,应用程序将检查过滤器中是否存在该数据。 如果不存在,则将其添加到Redis中的集合中,否则将返回null。 GET请求将从Redis获取它,并将其发送到客户端。

这与前两种情况不同,因为误报是不可能的。 我们将使用布隆过滤器作为第一道防线。 给定Bloom过滤器的属性,我们只能肯定地确定过滤器中没有东西,因此在这种情况下,我们可以继续进行操作,让数据进入。如果Bloom过滤器返回的可能是过滤器中的数据,将对照实际数据源进行检查。

那么,我们可以获得什么呢? 我们获得了不必每次都与实际来源进行核对的速度。 在数据源很慢的情况下(外部API,pokey数据库,平面文件的中间),确实需要提高速度。 为了演示速度,我们在示例中添加150ms的实际延迟。 我们还将使用console.time / console.timeEnd记录Bloom过滤器检查和非Bloom过滤器检查之间的差异。

在此示例中,我们还将使用数量非常有限的位:仅1024。它将快速填充。 填满后,它将显示出越来越多的误报-随着误报率填满,您将看到响应时间增加。

该服务器使用与以前相同的模块,因此将app.js文件设置为:

javascript var async = require('async'),Bloom = require('bloom-redis'),bodyParser = require('body-parser'),express = require('express'),redis = require(' redis'),

应用,客户端,过滤器,

currentDataKey ='当前数据',usedDataKey ='已使用数据';

app = express(); 客户端= redis.createClient();

filter = new Bloom.BloomFilter({client:client,key:'stale-bloom-filter',//出于说明目的,这是一个超小的过滤器。应填充约500个项目,因此对于生产负荷,你需要更大的东西!size:1024,numHashes:20});

app.post('/',bodyParser.text(),function(req,res,next){使用的var;

app.get('/',function(req,res,next){//仅返回新数据客户端。get(currentDataKey,function(err,data){if(err){next(err);} else { res.send(data);}});});

app.listen(8012); ```

由于使用浏览器发布到服务器可能很棘手,因此让我们使用curl进行测试。

curl --data “your data goes here" --header "Content-Type: text/plain" http://localhost:8012/

快速的bash脚本可用于显示如何填充整个过滤器:

bash #!/bin/bash for i in `seq 1 500`; do curl --data “data $i" --header "Content-Type: text/plain" http://localhost:8012/ done

查看填充或完整过滤器很有趣。 由于这个很小,您可以使用redis-cli轻松查看它。 通过在添加项目之间从终端运行redis-cli get stale-filter ,您将看到单个字节增加。 每个字节的完整过滤器为\xff 。 此时,过滤器将始终返回正值。

Bloom过滤器不是万能的解决方案,但在正确的情况下,Bloom过滤器可以为其他数据结构提供快速,有效的补充。如果您没有读过《汤姆·索亚历险记》一书,就不会了解我。 但是那个

如果您仔细注意开发工具中的往返时间,您会发现请求带有用户名的单个路径越多,花费的时间就越长。 虽然检查过滤器需要固定的时间,但在此示例中,我们正在检查是否存在更多项目。 布隆过滤器只能告知您有限信息,因此您正在测试每个项目的存在。 当然,在我们的示例中,这非常简单,但是测试数百个项目的效率很低。

在此示例中,我们将构建一个小型Express服务器,该服务器将执行以下两项操作:通过POST接受新数据,并显示当前数据(带有GET请求)。 将新数据发布到服务器后,应用程序将检查过滤器中是否存在该数据。 如果不存在,则将其添加到Redis中的集合中,否则将返回null。 GET请求将从Redis获取它,并将其发送到客户端。

这与前两种情况不同,因为误报是不可能的。 我们将使用布隆过滤器作为第一道防线。 给定Bloom过滤器的属性,我们只能肯定地确定过滤器中没有东西,因此在这种情况下,我们可以继续进行操作,让数据进入。如果Bloom过滤器返回的可能是过滤器中的数据,将对照实际数据源进行检查。

那么,我们可以获得什么呢? 我们获得了不必每次都与实际来源进行核对的速度。 在数据源很慢的情况下(外部API,pokey数据库,平面文件的中间),确实需要提高速度。 为了演示速度,我们在示例中添加150ms的实际延迟。 我们还将使用console.time / console.timeEnd记录Bloom过滤器检查和非Bloom过滤器检查之间的差异。

在此示例中,我们还将使用数量非常有限的位:仅1024。它将快速填充。 填满后,它将显示出越来越多的误报-随着误报率填满,您将看到响应时间增加。

该服务器使用与以前相同的模块,因此将app.js文件设置为:

javascript var async = require('async'),Bloom = require('bloom-redis'),bodyParser = require('body-parser'),express = require('express'),redis = require(' redis'),

应用,客户端,过滤器,

currentDataKey ='当前数据',usedDataKey ='已使用数据';

app = express(); 客户端= redis.createClient();

filter = new Bloom.BloomFilter({client:client,key:'stale-bloom-filter',//出于说明目的,这是一个超小的过滤器。应填充约500个项目,因此对于生产负荷,你需要更大的东西!size:1024,numHashes:20});

app.post('/',bodyParser.text(),function(req,res,next){使用的var;

app.get('/',function(req,res,next){//仅返回新数据客户端。get(currentDataKey,function(err,data){if(err){next(err);} else { res.send(data);}});});

app.listen(8012); ```

由于使用浏览器发布到服务器可能很棘手,因此让我们使用curl进行测试。

curl --data “your data goes here" --header "Content-Type: text/plain" http://localhost:8012/

快速的bash脚本可用于显示如何填充整个过滤器:

bash #!/bin/bash for i in `seq 1 500`; do curl --data “data $i" --header "Content-Type: text/plain" http://localhost:8012/ done

查看填充或完整过滤器很有趣。 由于这个很小,您可以使用redis-cli轻松查看它。 通过在添加项目之间从终端运行redis-cli get stale-filter ,您将看到单个字节增加。 每个字节的完整过滤器为\xff 。 此时,过滤器将始终返回正值。

布隆过滤器不是万能的解决方案,但在正确的情况下,布隆过滤器可以为其他数据结构提供快速,高效的补充。','sherlock-holmes冒险':'致福尔摩斯她永远是女人。”,“弗雷德里克·道格拉斯的生平叙述”:“我出生在希尔斯伯勒附近的塔克霍,距马里兰州塔尔伯特县的伊斯顿约十二英里。”,“ the-prince':“对人实行统治的所有州,所有权力都曾经是共和国或公国。”,“ tom-saw-sawyer冒险”:“ TOM!” };

app = express(); 客户端= redis.createClient();

filter = new Bloom.BloomFilter({client:client,key:'3content-bloom-filter',// Redis密钥大小:2875518,//〜350kb //大小:1024,numHashes:20});

app.get('/ show-content /:user',function(req,res,next){//我们将遍历contentIds,检查它们是否在过滤器中。建议不要花大量时间在每个contentId上花费时间///但是,在这种情况下,contentId的数量很少/固定,并且我们的filter.contains函数很快,没关系。var // creates在openingLines contentIds = Object.keys(openingLines)中定义的键的数组,//从URI用户= req.params.user,获取路径的一部分,checkingContentId,found = false,done = false;

//由于filter.contains是异步的,因此我们使用异步库执行循环async.whilst(//检查函数,异步循环将在此结束函数(){return(!found &&!done);}, function(cb){//从contentIds数组中获取第一项checkingContentId = contentIds.shift();

app.listen(8011); </ pre>

如果您仔细注意开发工具中的往返时间,您会发现请求带有用户名的单个路径越多,花费的时间就越长。 虽然检查过滤器需要固定的时间,但在此示例中,我们正在检查是否存在更多项目。 布隆过滤器只能告知您有限信息,因此您正在测试每个项目的存在。 当然,在我们的示例中,这非常简单,但是测试数百个项目的效率很低。

在此示例中,我们将构建一个小型Express服务器,该服务器将执行以下两项操作:通过POST接受新数据,并显示当前数据(带有GET请求)。 将新数据发布到服务器后,应用程序将检查过滤器中是否存在该数据。 如果不存在,则将其添加到Redis中的集合中,否则将返回null。 GET请求将从Redis获取它,并将其发送到客户端。

This is different than the previous two situations, in that false positives would not be okay. We'll be using the Bloom filter as a first line of defense. Given the properties of Bloom filters, we'll only know for certain that something isn't in the filter, so in this case we can go ahead and let the data in. If the Bloom filter returns that is probably in the filter, we'll do a check versus the actual data source.

So, what do we gain? We gain the speed of not having to check versus the actual source every time. In situations where the data source is slow (external APIs, pokey databases, the middle of a flat file), the speed increase is really needed. To demonstrate the speed, let's add in a realistic delay of 150ms in our example. We'll also use the console.time / console.timeEnd to log the differences between a Bloom filter check and a non-Bloom filter check.

In this example, we'll also use an extremely constrained number of bits: just 1024. It will fill up quickly. As it fills, it will show more and more false positives—you'll see the response time increase as the false positive rate fills up.

This server uses the same modules as before, so set the app.js file to:

```javascript var async = require('async'), Bloom = require('bloom-redis'), bodyParser = require('body-parser'), express = require('express'), redis = require('redis'),

app, client, filter,

currentDataKey = 'current-data', usedDataKey = 'used-data';

app = express(); client = redis.createClient();

filter = new Bloom.BloomFilter({ client : client, key : 'stale-bloom-filter', //for illustration purposes, this is a super small filter. It should fill up at around 500 items, so for a production load, you'd need something much larger! size : 1024, numHashes : 20 });

app.post( '/', bodyParser.text(), function(req,res,next) { var used;

app.get('/',function(req,res,next) { //just return the fresh data client.get(currentDataKey, function(err,data) { if (err) { next(err); } else { res.send(data); } }); });

app.listen(8012); ```

Since POSTing to a server can be tricky with a browser, let's use curl to test.

curl --data “your data goes here" --header "Content-Type: text/plain" http://localhost:8012/

A quick bash script can be used to show how filling up the entire filter looks:

bash #!/bin/bash for i in `seq 1 500`; do curl --data “data $i" --header "Content-Type: text/plain" http://localhost:8012/ done

Looking at a filling or full filter is interesting. Since this one is small, you can easily view it with redis-cli . By running redis-cli get stale-filter from the terminal between adding items, you'll see the individual bytes increase. A full filter will be \xff for every byte. At this point, the filter will always return positive.

Bloom filters aren't a panacea solution, but in the right situation, a Bloom filter can provide a speedy, efficient complement to other data structures.

翻译自: https://code.tutsplus.com/articles/understanding-the-magic-of-bloom-filters-with-nodejs-redis--cms-25397

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值