swoft+Redis實現的布隆過濾器
阿新 • • 發佈:2022-03-23
布隆過濾器:本質上布隆過濾器是一種資料結構,比較巧妙的概率型資料結構,特點是高效的插入和查詢,可以用來告訴你“某樣東西一定不存在或者可能存在”。相比於傳統的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); 13for ($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