本文目录导读:

在PHP项目中实现布隆去重,主要有两种方式:
使用 Redis 的布隆过滤器(推荐)
Redis 4.0+ 支持布隆过滤器模块,这是最常用且高效的方式。
安装 RedisBloom 模块
# 下载并编译 git clone https://github.com/RedisBloom/RedisBloom.git cd RedisBloom make # 启动 Redis 时加载 redis-server --loadmodule ./redisbloom.so
PHP 代码实现
<?php
class BloomFilter {
private $redis;
private $key;
public function __construct($redisConfig = []) {
$this->redis = new Redis();
$this->redis->connect(
$redisConfig['host'] ?? '127.0.0.1',
$redisConfig['port'] ?? 6379
);
$this->key = 'bloom_filter';
}
/**
* 添加元素
*/
public function add($item) {
return $this->redis->rawCommand('BF.ADD', $this->key, $item);
}
/**
* 检查元素是否存在
*/
public function exists($item) {
return $this->redis->rawCommand('BF.EXISTS', $this->key, $item);
}
/**
* 批量添加
*/
public function mAdd($items) {
$args = array_merge([$this->key], $items);
return $this->redis->rawCommand('BF.MADD', ...$args);
}
/**
* 批量检查
*/
public function mExists($items) {
$args = array_merge([$this->key], $items);
return $this->redis->rawCommand('BF.MEXISTS', ...$args);
}
/**
* 创建过滤器(设置参数)
* @param float $errorRate 错误率(0-1之间)
* @param int $capacity 预期容量
*/
public function reserve($errorRate = 0.01, $capacity = 10000) {
return $this->redis->rawCommand('BF.RESERVE', $this->key, $errorRate, $capacity);
}
/**
* 获取过滤器信息
*/
public function info() {
return $this->redis->rawCommand('BF.INFO', $this->key);
}
}
// 使用示例
$bloom = new BloomFilter();
// 初始化(可选,不设置会自动使用默认参数)
$bloom->reserve(0.001, 100000); // 0.1%错误率,10万容量
// 添加元素
$bloom->add('user_12345');
$bloom->add('url_https://example.com');
// 检查是否存在
if ($bloom->exists('user_12345')) {
echo "可能存在(可能误判)\n";
} else {
echo "一定不存在\n";
}
// 批量操作
$users = ['user_1', 'user_2', 'user_3', 'user_4'];
$results = $bloom->mAdd($users); // 返回 [1, 1, 1, 1]
$checkUsers = ['user_1', 'user_999', 'user_3', 'user_888'];
$results = $bloom->mExists($checkUsers); // 返回 [1, 0, 1, 0]
纯 PHP 实现(不依赖外部服务)
适合小型项目或无法安装 Redis 的情况。
<?php
class PureBloomFilter {
private $bitArray;
private $size;
private $hashCount;
/**
* @param int $size 位数组大小
* @param int $hashCount 哈希函数数量
*/
public function __construct($size = 1000000, $hashCount = 3) {
$this->size = $size;
$this->hashCount = $hashCount;
$this->bitArray = new SplFixedArray($size / PHP_INT_SIZE + 1);
// 初始化位数组
for ($i = 0; $i < count($this->bitArray); $i++) {
$this->bitArray[$i] = 0;
}
}
/**
* 哈希函数
*/
private function hash($item, $seed) {
$hash = crc32($item . $seed);
return abs($hash) % $this->size;
}
/**
* 设置位
*/
private function setBit($index) {
$arrIndex = intdiv($index, PHP_INT_SIZE);
$bitIndex = $index % PHP_INT_SIZE;
$this->bitArray[$arrIndex] |= (1 << $bitIndex);
}
/**
* 获取位
*/
private function getBit($index) {
$arrIndex = intdiv($index, PHP_INT_SIZE);
$bitIndex = $index % PHP_INT_SIZE;
return ($this->bitArray[$arrIndex] >> $bitIndex) & 1;
}
/**
* 添加元素
*/
public function add($item) {
for ($i = 0; $i < $this->hashCount; $i++) {
$index = $this->hash($item, $i);
$this->setBit($index);
}
}
/**
* 检查元素是否存在
*/
public function exists($item) {
for ($i = 0; $i < $this->hashCount; $i++) {
$index = $this->hash($item, $i);
if (!$this->getBit($index)) {
return false;
}
}
return true;
}
/**
* 保存到文件
*/
public function save($filename) {
$data = serialize($this->bitArray);
file_put_contents($filename, $data);
}
/**
* 从文件加载
*/
public function load($filename) {
$data = file_get_contents($filename);
$this->bitArray = unserialize($data);
}
/**
* 获取当前元素数量(近似值)
* n ≈ -m * ln(1 - x/m) / k
*/
public function count() {
$countOnes = 0;
$count = count($this->bitArray);
for ($i = 0; $i < $count; $i++) {
$countOnes += PHP_INT_SIZE - $this->bitCount(~$this->bitArray[$i]);
}
return -$this->size * log(1 - $countOnes / $this->size) / $this->hashCount;
}
private function bitCount($n) {
$count = 0;
while ($n) {
$count++;
$n &= ($n - 1);
}
return $count;
}
}
// 使用示例
$filter = new PureBloomFilter(10000000, 5); // 1000万位,5个哈希函数
// 添加数据
$filter->add('item_1');
$filter->add('item_2');
// 检查
var_dump($filter->exists('item_1')); // true
var_dump($filter->exists('item_999')); // false
// 持久化
$filter->save('/tmp/bloom_filter.dat');
// 重新加载
$newFilter = new PureBloomFilter();
$newFilter->load('/tmp/bloom_filter.dat');
var_dump($newFilter->exists('item_1')); // true
使用位图文件实现(适合大量数据)
<?php
class FileBloomFilter {
private $filename;
private $size;
private $hashCount;
private $file;
public function __construct($filename, $size = 100000000, $hashCount = 7) {
$this->filename = $filename;
$this->size = $size;
$this->hashCount = $hashCount;
// 创建或打开位图文件
if (!file_exists($filename)) {
$bytes = ceil($size / 8);
$this->file = fopen($filename, 'wb+');
ftruncate($this->file, $bytes);
} else {
$this->file = fopen($filename, 'rb+');
}
}
private function getPositions($item) {
$positions = [];
$hash1 = crc32($item);
$hash2 = crc32(strrev($item));
for ($i = 0; $i < $this->hashCount; $i++) {
$position = abs($hash1 + $i * $hash2) % $this->size;
$positions[] = $position;
}
return $positions;
}
public function add($item) {
$positions = $this->getPositions($item);
foreach ($positions as $pos) {
$byteOffset = intdiv($pos, 8);
$bitOffset = $pos % 8;
fseek($this->file, $byteOffset);
$byte = ord(fgetc($this->file));
fseek($this->file, $byteOffset);
fwrite($this->file, chr($byte | (1 << $bitOffset)));
}
}
public function exists($item) {
$positions = $this->getPositions($item);
foreach ($positions as $pos) {
$byteOffset = intdiv($pos, 8);
$bitOffset = $pos % 8;
fseek($this->file, $byteOffset);
$byte = ord(fgetc($this->file));
if (!($byte & (1 << $bitOffset))) {
return false;
}
}
return true;
}
public function __destruct() {
if ($this->file) {
fclose($this->file);
}
}
}
选择建议
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Redis Bloom | 高性能、持久化、分布式 | 需要 Redis 服务 | 生产环境、高并发 |
| 纯 PHP | 无外部依赖、简单 | 内存占用大 | 小型项目、测试 |
| 文件位图 | 节省内存、持久化 | IO 慢 | 数据量大、性能要求不高 |
最佳实践
// 结合数据库的完整示例
class URLDeduplicator {
private $bloom;
private $db;
public function __construct() {
// 使用 Redis Bloom
$this->bloom = new BloomFilter();
// 数据库作为后备存储
$this->db = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
// 初始化布隆过滤器
$this->bloom->reserve(0.0001, 10000000);
}
public function isDuplicate($url) {
// 先在布隆过滤器中检查
if (!$this->bloom->exists($url)) {
return false;
}
// 布隆过滤器返回可能存在,再到数据库确认
$stmt = $this->db->prepare('SELECT id FROM urls WHERE url = ?');
$stmt->execute([$url]);
return (bool)$stmt->fetch();
}
public function add($url) {
// 先添加到布隆过滤器
$this->bloom->add($url);
// 再存入数据库
$stmt = $this->db->prepare('INSERT INTO urls (url) VALUES (?)');
$stmt->execute([$url]);
}
}
这样结合布隆过滤器和数据库,既能保证速度,又能确保准确性。
版权声明:足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播