swoft+Redis实现的布隆过滤器


       布隆过滤器:本质上布隆过滤器是一种数据结构,比较巧妙的概率型数据结构,特点是高效的插入和查询,可以用来告诉你“某样东西一定不存在或者可能存在”。相比于传统的list,set,map等数据结构,它更高效,占用空间更少,但是缺点是其返回的结果是概率性的,而不是确切的。

      由于Redis实现了setbit和getbit操作,天然适合实现布隆过滤器,redis也有布隆过滤器插件。这里使用php+redis实现布隆过滤器。

     首先定义一个hash函数集合类,这些hash函数不一定都用到,实际上32位hash值的用3个就可以了,具体的数量可以根据你的位序列总量和你需要存入的量决定,上面已经给出最佳值。

  1 <?php declare(strict_types=1);
  2 namespace App\Common;
  3 
  4 class BloomFilterHash
  5 {
  6     /**
  7      * 由Justin Sobel编写的按位散列函数
  8      */
  9     public function JSHash($string, $len = null)
 10     {
 11         $hash = 1315423911;
 12         $len || $len = strlen($string);
 13         for ($i=0; $i<$len; $i++) {
 14             $hash ^= (($hash << 5) + ord($string[$i]) + ($hash >> 2));
 15         }
 16         return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;
 17     }
 18 
 19     /**
 20      * 该哈希算法基于AT&T贝尔实验室的Peter J. Weinberger的工作。
 21      * Aho Sethi和Ulman编写的“编译器(原理,技术和工具)”一书建议使用采用此特定算法中的散列方法的散列函数。
 22      */
 23     public function PJWHash($string, $len = null)
 24     {
 25         $bitsInUnsignedInt = 4 * 8; //(unsigned int)(sizeof(unsigned int)* 8);
 26         $threeQuarters = ($bitsInUnsignedInt * 3) / 4;
 27         $oneEighth = $bitsInUnsignedInt / 8;
 28         $highBits = 0xFFFFFFFF << (int) ($bitsInUnsignedInt - $oneEighth);
 29         $hash = 0;
 30         $test = 0;
 31         $len || $len = strlen($string);
 32         for($i=0; $i<$len; $i++) {
 33             $hash = ($hash << (int) ($oneEighth)) + ord($string[$i]); } $test = $hash & $highBits; if ($test != 0) { $hash = (($hash ^ ($test >> (int)($threeQuarters))) & (~$highBits));
 34     }
 35         return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;
 36     }
 37 
 38     /**
 39      * 类似于PJW Hash功能,但针对32位处理器进行了调整。它是基于UNIX的系统上的widley使用哈希函数。
 40      */
 41     public function ELFHash($string, $len = null)
 42     {
 43         $hash = 0;
 44         $len || $len = strlen($string);
 45         for ($i=0; $i<$len; $i++) {
 46             $hash = ($hash << 4) + ord($string[$i]); $x = $hash & 0xF0000000; if ($x != 0) { $hash ^= ($x >> 24);
 47             }
 48             $hash &= ~$x;
 49         }
 50         return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;
 51     }
 52 
 53     /**
 54      * 这个哈希函数来自Brian Kernighan和Dennis Ritchie的书“The C Programming Language”。
 55      * 它是一个简单的哈希函数,使用一组奇怪的可能种子,它们都构成了31 .... 31 ... 31等模式,它似乎与DJB哈希函数非常相似。
 56      */
 57     public function BKDRHash($string, $len = null)
 58     {
 59         $seed = 131;  # 31 131 1313 13131 131313 etc..
 60         $hash = 0;
 61         $len || $len = strlen($string);
 62         for ($i=0; $i<$len; $i++) {
 63             $hash = (int) (($hash * $seed) + ord($string[$i]));
 64         }
 65         return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;
 66     }
 67 
 68     /**
 69      * 这是在开源SDBM项目中使用的首选算法。
 70      * 哈希函数似乎对许多不同的数据集具有良好的总体分布。它似乎适用于数据集中元素的MSB存在高差异的情况。
 71      */
 72     public function SDBMHash($string, $len = null)
 73     {
 74         $hash = 0;
 75         $len || $len = strlen($string);
 76         for ($i=0; $i<$len; $i++) {
 77             $hash = (int) (ord($string[$i]) + ($hash << 6) + ($hash << 16) - $hash);
 78         }
 79         return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;
 80     }
 81 
 82     /**
 83      * 由Daniel J. Bernstein教授制作的算法,首先在usenet新闻组comp.lang.c上向世界展示。
 84      * 它是有史以来发布的最有效的哈希函数之一。
 85      */
 86     public function DJBHash($string, $len = null)
 87     {
 88         $hash = 5381;
 89         $len || $len = strlen($string);
 90         for ($i=0; $i<$len; $i++) {
 91             $hash = (int) (($hash << 5) + $hash) + ord($string[$i]);
 92         }
 93         return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;
 94     }
 95 
 96     /**
 97      * Donald E. Knuth在“计算机编程艺术第3卷”中提出的算法,主题是排序和搜索第6.4章。
 98      */
 99     public function DEKHash($string, $len = null)
100     {
101         $len || $len = strlen($string);
102         $hash = $len;
103         for ($i=0; $i<$len; $i++) {
104             $hash = (($hash << 5) ^ ($hash >> 27)) ^ ord($string[$i]);
105         }
106         return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;
107     }
108 
109     /**
110      * 参考 http://www.isthe.com/chongo/tech/comp/fnv/
111      */
112     public function FNVHash($string, $len = null)
113     {
114         $prime = 16777619; //32位的prime 2^24 + 2^8 + 0x93 = 16777619
115         $hash = 2166136261; //32位的offset
116         $len || $len = strlen($string);
117         for ($i=0; $i<$len; $i++) {
118             $hash = (int) ($hash * $prime) % 0xFFFFFFFF;
119             $hash ^= ord($string[$i]);
120         }
121         return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;
122     }
123 }

接着就是连接redis来进行操作

 1 <?php declare(strict_types=1);
 2 namespace App\Common;
 3 
 4 use App\Exception\ApiException;
 5 use Swoft\Redis\Redis;
 6 
 7 abstract class BloomFilterRedis
 8 {
 9     protected $bucket;
10     protected $hashFunction;
11 
12     public function __construct()
13     {
14         if(!$this->bucket || !$this->hashFunction){
15             throw new ApiException('需要定义',400);
16         }
17 
18         $this->Hash = new BloomFilterHash();
19     }
20 
21     public function add($string)
22     {
23         $pipe = Redis::multi();
24         foreach ($this->hashFunction as $function){
25             $hash = $this->Hash->$function($string);
26             $pipe = $pipe->setBit($this->bucket, $hash, true);
27             echo "hash结果:".$hash."\n";
28         }
29 
30         return $pipe->exec();
31     }
32 
33 
34     public function exists($string)
35     {
36         $len = strlen($string);
37         $pipe = Redis::multi();
38         foreach ($this->hashFunction as $function){
39             $hash = $this->Hash->$function($string, $len);
40             $pipe = $pipe->getBit($this->bucket, $hash);
41             echo "hash获取结果:".$hash."\n";
42         }
43 
44         $res = $pipe->exec();
45         foreach ($res as $bit){
46             if(!$bit) return false;
47         }
48         return true;
49     }
50 }

上面定义的是一个抽象类,如果要使用,可以根据具体的业务来使用。比如下面是一个过滤重复内容的过滤器。

 1 <?php declare(strict_types=1);
 2 namespace App\Bean;
 3 
 4 use App\Common\BloomFilterRedis;
 5 use Swoft\Bean\Annotation\Mapping\Bean;
 6 
 7 
 8 /**
 9  * 重复内容过滤器
10  * 该布隆过滤器总位数为2^32位, 判断条数为2^30条. hash函数最优为3个.(能够容忍最多的hash函数个数)
11  * 使用的三个hash函数为
12  * BKDR, SDBM, JSHash
13  * 注意, 在存储的数据量到2^30条时候, 误判率会急剧增加, 因此需要定时判断过滤器中的位为1的的数量是否超过50%, 超过则需要清空.
14  * Class FileRepeatedComments
15  * @package App\Bean
16  * @Bean()
17  */
18 class FileRepeatedComments extends BloomFilterRedis
19 {
20     protected $bucket = 'rptc';
21 
22     protected $hashFunction = array(
23         'BKDRHash', 'SDBMHash', 'JSHash'
24     );
25 }

调用过滤器

 1 <?php
 2 namespace App\Http\Controller;
 3 
 4 use Swoft\Http\Server\Annotation\Mapping\Controller;
 5 use Swoft\Http\Server\Annotation\Mapping\RequestMapping;
 6 use Swoft\Http\Server\Annotation\Mapping\RequestMethod;
 7 use Swoft\Bean\Annotation\Mapping\Inject;
 8 use App\Bean\FileRepeatedComments;
 9 use Swoft\Http\Message\Request;
10 
11 /**
12  * Class TestController
13  * @Controller(prefix="/test")
14  */
15 class TestController
16 {
17     /**
18      * @Inject()
19      * @var FileRepeatedComments
20      */
21     protected $fileRepeatedComments;
22 
23     /**
24      * @RequestMapping(route="setBloomFilter", method={RequestMethod::GET})
25      */
26     public function setBloomFilterRedis(Request $request){
27         $id = $request->get('id');
28         $this->fileRepeatedComments->add($id);
29     }
30 
31 
32     /**
33      * @RequestMapping(route="getBloomFilter", method={RequestMethod::GET})
34      */
35     public function getBloomFilterRedis(Request $request){
36         $id = $request->get('id');
37         if($this->fileRepeatedComments->exists($id)){
38             echo "存在";
39         }else{
40             echo "不存在";
41         }
42     }
43 }

转载地址:https://segmentfault.com/a/1190000038989098

相关