布隆过滤器(Bloom Filter)

首先看下面几个场景:

  • 字处理软件中,需要检查一个英语单词是否拼写正确
  • 在 FBI,一个嫌疑人的名字是否已经在嫌疑名单上
  • 网页爬虫对URL的去重,避免爬取相同的URL地址
  • 反垃圾邮件,从数十亿个垃圾邮件列表中判断某邮箱是否垃圾邮箱(同理,垃圾短信)
  • 缓存击穿,将已存在的缓存放到布隆过滤器中,当黑客访问不存在的缓存时迅速返回避免缓存及DB挂掉。

以上场景归纳为一个问题就是:如何在一个很大数据集合中迅速查询一个元素?

有人会说,我们可以直接把数据放在redis缓存或者存在数据库,查询的时候直接匹配就不就ok?当数据量比较小,我们内存够大的时候的确可以这样处理,甚至可以直接用HashSet、HashMap解决,但是如果我们数据量很大,几千万或者几亿,这种情况下又如何处理呢?布隆过滤器就应运而生。

基本概念

布隆过滤器实际上是一个很长的二进制向量(位数组)和一系列随机映射函数(哈希),作用就在于能跟迅速判断一个元素是否在一个集合中,且查询效率很高(1-N,最优能近于1)。

哈希函数

首先简单介绍下哈希函数:将任意大小的数据转换成特定大小的数据的函数,转换后的数据称为哈希值或哈希编码。比如我们常用的md5。下面一副示意图:


原始数据与哈希编码的映射是近乎一对一的(非常非常低的几率下两个不同key的hash值是可能重复的)。哈希函数是实现哈希表和布隆过滤器的基础。

布隆过滤器原理:

初始状态下是一个m位的全为0的bit数组,以及k个hash函数,如下图所示


image

假定我们要维护的集合为{N1, N2},首先输入N1,经过hash函数f1(N1)%m得到2,f2(N1)%m得到5,那么就将bit数组的2,5位置改为1,如下图所示:


image

同理计算N2:


image

此时,如果我们要查询一个元素N3,判断N3是否在集合{N1, N2}中,我们只需要进行f1(N3)%m,f2(N3)%m的计算:如果f1(N3)%m,f2(N3)%m对应下标的值有一个为0,那就说明元素不在集合内,反之如果两个值都为1,则说明元素在集合内。但实际上存在元素不在集合中却对应都为1的情况,这就是误判率的存在。随着存入的元素数量增加,误算率随之增加。但是如果元素数量太少,则使用散列表足矣。

通常我们需要根据实际业务来计算误判率,确定m/n和k的值(m代表bit数组长度,n代表集合元素数,k代表hash函数个数),具体可以参考这篇文章

由上我们可以看出布隆过滤器的优点就是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难(一般情况下是不能删除的)。

php和redis实现bloom filter方法

第一步,获取几个hash函数,比如BKDRHash,JSHash,RSHash等等。这些hash函数我们直接获取就可以了。

/**函数集合类
*/
class HashSet {
    const JSHASH = 'JSHash';
    const BKDRHASH = 'BKDRHash';
    const RSHASH = 'RSHash';
    const PJWHASH = 'PJWHash';
    const ELFHash = 'ELFHash';
    const BKDRHash = 'BKDRHash';
    const SDBMHash = 'SDBMHash';
    const DJBHash = 'DJBHash';
    const DEKHash = 'DEKHash';
    const FNVHash = 'FNVHash';

    //函数具体实现不详述了
    public static function JSHash($string) {}
    public static function BKDRHash($string) {}
    public static function RSHash($string) {}
    public static function BKDRHash($string) {}
    public static function SDBMHash($string) {}  
    ...
}

第二步,使用redis的setBit和getBit操作来实现过滤器。当然我们也可以用php作位运算,但是比较麻烦这里不赘述了。值得注意的是setBit和getBit必须2.2以上版本的redis才能支持。

/**使用redis实现的布隆过滤器
*/
abstract class BloomFilterBase {
    //bit数组
    protected $bit_array_name;
    protected $bit_array_length;
    //hash函数集合
    protected $hash_set;
    protected $redis_obj;

    public function __construct() {
        if (!$this->hash_set) {
            $this->hash_set = HashSet::$hash_set;
        }       
        $this->redis_obj = new Redis;         
    }

    /**定义bitArray
    */
    public function setBitArray($name, $lengh) {
        $this->bit_array_name = $name;
        $this->bit_array_lengh = $length; 
    }

     /**添加元素到集合
     */
     public function add ($string) {
         if (!$this->bit_array_name || !$this->bit_array_length) {
             throw new Exception("需要预定义bitArray", 1);
         }
         foreach ($this->hash_set as $function) {
             $hash = HashSet::$function($string) % $this->bit_array_length;
             $this->redis_obj->setBit($this->bit_array_name, $hash, 1);
         }        
     }

    /**判断是否存在
    */
    public function isExists($string) {
         if (!$this->bit_array_name || !$this->bit_array_length) {
             throw new Exception("需要预定义bitArray", 1);
         }
         foreach ($this->hash_set as $function) {
             $hash = HashSet::$function($string) % $this->bit_array_length;
             $res = $this->redis_obj->getBit($this->bit_array_name, $hash);
             if  ($res == 0) {
                return false;
             }  
         } 
         return true;
    }
}

第三步,上面定义的是一个抽象类,可以根据具体的业务来使用。

/**
 * 重复内容过滤器
 * 该布隆过滤器总位数为2^32位, 判断条数为2^30条. hash函数最优为3个.(能够容忍最多的hash函数个数)
 * 使用的三个hash函数为
 * BKDR, SDBM, JSHash
 *
 * 注意, 在存储的数据量到2^30条时候, 误判率会急剧增加, 因此需要定时判断过滤器中的位为1的的数量是否超过50%, 超过则需要清空.
 */
class FilteRepeatedComments extends BloomFilterBase
{
    protected $bit_array_name = 'repeated_comments_bloom_filter';
    protected $bit_array_length = 0xFFFFFFFF;
    protected $hash_set = array(      
          HashSet::JSHASH,
          HashSet::BKDRHASH,
          HashSet::RSHASH,
    );
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容

  • 布隆过滤器 (Bloom Filter) 详解 原文链接:http://www.cnblogs.com/allen...
    JackChen1024阅读 11,819评论 0 3
  • 布隆过滤器使用场景 之前在《数学之美》里面看到过布隆过滤器的介绍。那么什么场景下面需要使用布隆过滤器呢? 看下下面...
    骊骅阅读 37,021评论 4 43
  • 维基百科 布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随...
    网虫子阅读 556评论 0 1
  • 时间一晃,又是一年。 想到两年前的这个时候,吹着六月的夏风,揣摩这学长学姐们会有的心理状态,为他们倒计时,心里惴惴...
    四一一阅读 501评论 0 1
  • 刚刚过去三天的二零一六 恍如隔世 来不及总结 一直在前行 因为静静而来到简书 简单书写几个词 告别过去 迎接未来 ...
    天蝎小豆芽阅读 436评论 0 2