3

今天和大家聊一下布隆过滤器(bloom Filter)。

布隆过滤器(bloom Filter)是1970年由Bloom,Burton H.提出的。

布隆过滤器(bloom Filter)是是一种用于支持成员资格查询的数据结构。
简单地说,布隆过滤器(bloom Filter)是用于测试元素是否为给定数据集合的成员。

布隆过滤器有以下特点:

*   与要测试的数据集的数据量相比,存储布隆过滤器所需的空间量更小。
*   检测一个测试对象数据是否属于给定数据集的成员所用的时间与测试对象数据集集合中包含的元素数无关。
*   假反例(False negatives,即如果某个元素确实没有在该集合中,那么Bloom Filter 是不会报告该元素存在于集合中)是不可能出现。
*   假正例(False positives,即Bloom Filter报告某一元素存在于某集合中,但是实际上该元素并不在集合中)是可能的,但可以控制其发生频率。

换成下面的话可能更容易理解:

布隆过滤器是一种把大数据集换成小数据集以节约下一步处理所需要时间的方法,它可能没有过滤掉所有不符合标准的数据,但不会把符合标准数据错误的过滤掉。

那么布隆过滤器是如何实现的呢?

布隆过滤器基于一个最初设置为 0 的 m 位数组(b1、b2、......bm)和 k 个独立的哈希函数(h1、h2、...、hk),每个哈希函数返回介于 1 和 m 之间的值。为了将测试对象数据集"存储"到m位数组中,必须将每个哈希函数应用于测试对象数据,并且将每个函数(r1、r2、...、...、rk)的返回值 作为偏移量, 设置 m 位数组相应的BIT为 1 。由于存在 k 个哈希函数,因此位数组中最多为 k 位将设置为 1(也可能比 k 少,因为多个哈希函数可能返回相同的值)。下图是 m=16、k=4 和 e 是要"存储在"位数组中的元素的示例。

image.png

那么如何检查元素是否"存储在"位数组中呢,这个过程和上面类似。唯一的区别是,它不设置数组中的 k 位,而是检查它们中的任何一个都是否设置为 0。如果是,则意味着元素未"存储在"位数组中。

让我们看一个示例,说明如何在实践中使用布隆过滤器。

假设有一个应用程序以高吞吐量接收输入数据。在处理它之前,它必须检查该数据是否属于存储在数据库表中的给定集。请注意,此验证的拒绝率非常高。若要执行验证,应用程序必须调用远程服务。不幸的是,网络往返和实际查找所需时间太长。由于无法进一步优化它(例如,网络延迟受物理限制),因此必须实现另一种方法。在这种情况下,一般会想到缓存。通过复制远程数据存储在本地缓存中,然后在本地执行验证。可是在这个场景下,这是不可行的。因为本地没有足够的资源来存储数据。在这种情况下,布隆过滤器可能发挥很大的作用。因为布隆过滤器需要的空间比实际数据需要的存储少得多。另外,由于布隆过滤器受到误报(False positives)的影响,不可能将所有不需要的数据都过滤掉,只是在调用远程服务之前尽可能的丢弃不符合验证函数的输入数据。因此,使用布隆过滤器并不会提高原始验证运行速度,而是将需要验证的数据集变小,以此减少整个处理所需的时间和存储空间。

下面我用PL/SQL来模拟一个布隆过滤器。

我的PL/SQL脚本包含以下三个存储过程/函数:

•init :用来初始化布隆过滤器
•add_value :用来“存储”字符串到 bit 数组
•contain :用来“检查”一个字符串是否 “存储在” bit 数组

PACKAGE定义:

CREATE OR REPLACE PACKAGE bloom_filter IS
   PROCEDURE init (p_m IN BINARY_INTEGER, p_n IN BINARY_INTEGER);
   FUNCTION add_value (p_value IN VARCHAR2) RETURN BINARY_INTEGER;
   FUNCTION contain (p_value IN VARCHAR2) RETURN BINARY_INTEGER;
END bloom_filter;

PACKAGE BODY定义:

CREATE OR REPLACE PACKAGE BODY bloom_filter IS
   TYPE t_bitarray IS TABLE OF BOOLEAN;
   g_bitarray t_bitarray;
   g_m BINARY_INTEGER;
   g_k BINARY_INTEGER;

   PROCEDURE init (p_m IN BINARY_INTEGER, p_n IN BINARY_INTEGER) IS
   BEGIN
      g_m := p_m;
      g_bitarray := t_bitarray();
      g_bitarray.extend(p_m);

      FOR i IN g_bitarray.FIRST..g_bitarray.LAST
      LOOP
        g_bitarray(i) := FALSE;
      END LOOP;

      g_k := ceil(p_m / p_n * ln(2));

   END init;

   FUNCTION add_value (p_value IN VARCHAR2) RETURN BINARY_INTEGER IS
   BEGIN
      dbms_random.seed(p_value);

      FOR i IN 0..g_k
      LOOP
         g_bitarray(dbms_random.value(1, g_m)) := TRUE;
      END LOOP;
      RETURN 1;
   END add_value;

   FUNCTION contain (p_value IN VARCHAR2) RETURN BINARY_INTEGER IS
      l_ret BINARY_INTEGER := 1;
   BEGIN
      dbms_random.seed(p_value);

      FOR i IN 0..g_k
      LOOP
         IF NOT g_bitarray(dbms_random.value(1, g_m))
         THEN
            l_ret := 0;
            EXIT;
         END IF;
      END LOOP;
      RETURN l_ret;
   END contain;
END bloom_filter;

接下来来测试一下位数组宽度和假正例(False positives)个数的关系。

SQL> execute bloom_filter.init(1024, 1000);

PL/SQLプロシージャが正常に完了しました。

SQL> SELECT count(bloom_filter.add_value(value)) FROM t WHERE rownum <= 1000;

COUNT(BLOOM_FILTER.ADD_VALUE(VALUE))
------------------------------------
                                1000

SQL> SELECT count(*) FROM t WHERE bloom_filter.contain(value) = 1;

  COUNT(*)
----------
      7602

SQL> execute bloom_filter.init(2048, 1000);

PL/SQLプロシージャが正常に完了しました。

SQL> SELECT count(bloom_filter.add_value(value)) FROM t WHERE rownum <= 1000;

COUNT(BLOOM_FILTER.ADD_VALUE(VALUE))
------------------------------------
                                1000

SQL> SELECT count(*) FROM t WHERE bloom_filter.contain(value) = 1;

  COUNT(*)
----------
      5072

SQL> execute bloom_filter.init(4096, 1000);

PL/SQLプロシージャが正常に完了しました。

SQL> SELECT count(bloom_filter.add_value(value)) FROM t WHERE rownum <= 1000;

COUNT(BLOOM_FILTER.ADD_VALUE(VALUE))
------------------------------------
                                1000

SQL> SELECT count(*) FROM t WHERE bloom_filter.contain(value) = 1;

  COUNT(*)
----------
      2306

SQL> execute bloom_filter.init(8192, 1000);

PL/SQLプロシージャが正常に完了しました。

SQL> SELECT count(bloom_filter.add_value(value)) FROM t WHERE rownum <= 1000;

COUNT(BLOOM_FILTER.ADD_VALUE(VALUE))
------------------------------------
                                1000

SQL> SELECT count(*) FROM t WHERE bloom_filter.contain(value) = 1;

  COUNT(*)
----------
      1205

SQL> execute bloom_filter.init(16384, 1000);

PL/SQLプロシージャが正常に完了しました。

SQL> SELECT count(bloom_filter.add_value(value)) FROM t WHERE rownum <= 1000;

COUNT(BLOOM_FILTER.ADD_VALUE(VALUE))
------------------------------------
                                1000

SQL> SELECT count(*) FROM t WHERE bloom_filter.contain(value) = 1;

  COUNT(*)
----------
      1003

SQL> execute bloom_filter.init(32768, 1000);

PL/SQLプロシージャが正常に完了しました。

SQL> SELECT count(bloom_filter.add_value(value)) FROM t WHERE rownum <= 1000;

COUNT(BLOOM_FILTER.ADD_VALUE(VALUE))
------------------------------------
                                1000

SQL> SELECT count(*) FROM t WHERE bloom_filter.contain(value) = 1;

  COUNT(*)
----------
      1000

通过上面的结果,我们可以看到随着 Bit 位数组宽度的增加,假正例(False positives)个数相应减少。

image.png

以上,我讲述了布隆过滤器的基本原理和应用场景。
关于布隆过滤器在ORACLE数据库中的应用,我会在下一篇文章里介绍。


Scott
39 声望40 粉丝

ORACLE数据库专家,现就职甲骨文SSC部门。