1. 程式人生 > 其它 >swoft+Redis實現的布隆過濾器

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