首页
学习
活动
专区
圈层
工具
发布

用 PHP 布隆过滤器给网络管理监控软件 “去重”

在网络管理监控软件的日常工作里,经常会遇到成千上万甚至更多的网络请求 URL。如果不对这些 URL 去重,软件就会反复分析同样的请求,既浪费资源又拖慢速度。过去常用的去重方法,比如存在数据库或者用哈希表,一旦 URL 数量达到百万、千万级别,就会出现内存占用过高、查询速度变慢等问题,根本没办法满足软件实时处理和节省资源的需求。

而布隆过滤器就像一个特别节省空间的 “智能小管家”,它是一种概率型的数据结构。虽然偶尔会判断失误,但只要设置好参数,这个失误率完全在可接受范围内。它能通过多个哈希函数快速判断某个 URL 是否存在,特别适合用来给网络管理监控软件的 URL 去重。

一、布隆过滤器与网络管理监控软件的适配逻辑

网络管理监控软件在分析网络请求时,必须先把重复的 URL 去掉,保证分析的数据都是独一无二的,这样后续的分析才能又快又准。布隆过滤器在这个场景里特别好用,主要体现在三个方面:

首先,它特别 “省内存”。网络管理监控软件每天要处理海量 URL,如果用传统哈希表存,存一条 URL 就要占几十字节内存,存 100 万条差不多得 40MB 内存。但布隆过滤器用位向量存储,同样存 100 万条 URL,只需要大概 4MB 内存。这就能让软件在配置不那么高的服务器上也能稳定运行。

其次,它查得快,跟得上实时监控的节奏。网络管理监控软件判断 URL 是不是重复,必须在极短时间内完成,不然整个监控流程都会受影响。布隆过滤器查询的速度只和哈希函数的数量有关(一般用 3 到 5 个哈希函数),不管数据量有多大,都能很快给出判断结果,保证软件及时处理网络请求。

最后,它能随时 “更新数据”。网络管理监控软件收到的 URL 数据是不断新增的,布隆过滤器添加新数据特别方便,只要用哈希函数算出位置,把对应位置标记一下就行,不用大费周章调整数据结构,完全能跟上数据实时更新的速度。

二、布隆过滤器核心原理与 PHP 实现

布隆过滤器的核心思路很简单:用多个独立的哈希函数,把数据 “映射” 到位向量的不同位置,这样就能快速判断数据在不在里面。刚开始用的时候,先创建一个长度为 m 的位向量,所有位置都设为 0;存入数据时,用 k 个哈希函数算出 k 个位置,把这些位置都改成 1;查询数据时,如果这 k 个位置都是 1,那就说明数据 “可能存在”(但也有可能判断错了);要是有任何一个位置是 0,那就肯定不存在。它出错的概率和位向量长度 m、哈希函数数量 k、数据总量 n 有关,计算公式是\((1-(1-\frac{1}{m})^{kn})^k \approx (1-e^{-\frac{kn}{m}})^k\) 。

下面这段 PHP 代码,就是专门给网络管理监控软件 URL 去重写的布隆过滤器。代码用了 MurmurHash3 算法来实现多个哈希映射,可以往里面添加 URL,也能判断 URL 是不是重复:

<?php

class BloomFilter {

private $bitVector; // 用字符串存位向量

private $bitSize; // 位向量的长度

private $hashCount; // 哈希函数的数量

private $seeds; // 哈希函数的种子数组

/**

* 初始化布隆过滤器

* @param int $expectedCount 预计要存多少条URL

* @param float $falseRate 能接受的误判概率

*/

public function __construct(int $expectedCount, float $falseRate) {

// 计算位向量长度 m = -n*ln(p)/(ln2)^2

$this->bitSize = (int)(-($expectedCount * log($falseRate)) / (log(2) ** 2));

// 计算哈希函数数量 k = m/n * ln2

$this->hashCount = (int)(($this->bitSize / $expectedCount) * log(2));

// 初始化位向量,每个字符存8位,不够就补0

$byteSize = (int)ceil($this->bitSize / 8);

$this->bitVector = str_repeat(chr(0), $byteSize);

// 初始化哈希函数种子,保证每个种子都不一样

$this->seeds = [];

for ($i = 0; $i < $this->hashCount; $i++) {

$this->seeds[] = 100 + $i * 43; // 种子间隔43,让结果更随机

}

}

/**

* MurmurHash3哈希函数(生成32位哈希值)

* @param string $data 要处理的URL

* @param int $seed 哈希种子

* @return int 生成的32位哈希值

*/

private function murmurHash3(string $data, int $seed): int {

$len = strlen($data);

$h1 = $seed;

$c1 = 0xcc9e2d51;

$c2 = 0x1b873593;

$blockSize = 4;

$blocks = unpack('V*', $data);

$blockCount = count($blocks);

// 处理4字节的数据块

for ($i = 0; $i < $blockCount; $i++) {

$k1 = $blocks[$i + 1];

$k1 *= $c1;

$k1 = ($k1 << 15) | (($k1 >> 17) & 0x7fff); // 32位循环左移

$k1 *= $c2;

$h1 ^= $k1;

$h1 = ($h1 << 13) | (($h1 >> 19) & 0x1fff);

$h1 = ($h1 * 5) + 0xe6546b64;

}

// 处理剩下不足4字节的数据

$tail = substr($data, $blockCount * $blockSize);

$k1 = 0;

$tailLen = strlen($tail);

if ($tailLen >= 3) $k1 ^= ord($tail[2]) << 16;

if ($tailLen >= 2) $k1 ^= ord($tail[1]) << 8;

if ($tailLen >= 1) {

$k1 ^= ord($tail[0]);

$k1 *= $c1;

$k1 = ($k1 << 15) | (($k1 >> 17) & 0x7fff);

$k1 *= $c2;

$h1 ^= $k1;

}

// 最后调整哈希值

$h1 ^= $len;

$h1 ^= ($h1 >> 16);

$h1 *= 0x85ebca6b;

$h1 ^= ($h1 >> 13);

$h1 *= 0xc24b8b70;

$h1 ^= ($h1 >> 16);

return $h1 & 0xffffffff; // 保证是32位无符号整数

}

/**

* 往布隆过滤器里添加URL

* @param string $url 要添加的URL

*/

public function insert(string $url): void {

foreach ($this->seeds as $seed) {

$hashVal = $this->murmurHash3($url, $seed);

$index = $hashVal % $this->bitSize; // 算到位向量里的位置

$byteIndex = (int)floor($index / 8); // 算出在哪个字节

$bitOffset = $index % 8; // 算出在字节里的具体位置

// 把对应位置设为1

$this->bitVector[$byteIndex] = chr(ord($this->bitVector[$byteIndex]) | (1 << $bitOffset));

}

}

/**

* 判断URL是不是已经存过

* @param string $url 要检查的URL

* @return bool true表示可能存过(还得再确认);false表示肯定没存过

*/

public function exists(string $url): bool {

foreach ($this->seeds as $seed) {

$hashVal = $this->murmurHash3($url, $seed);

$index = $hashVal % $this->bitSize;

$byteIndex = (int)floor($index / 8);

$bitOffset = $index % 8;

// 只要有一个对应位置是0,就说明肯定没存过

if (!(ord($this->bitVector[$byteIndex]) & (1 << $bitOffset))) {

return false;

}

}

return true;

}

}

// 演示怎么用布隆过滤器给URL去重

// 1. 初始化布隆过滤器:预计存50万条URL,误判率设为0.0001

$bloomFilter = new BloomFilter(500000, 0.0001);

echo "网络管理监控软件布隆过滤器初始化完成\n";

// 2. 模拟添加URL数据

$sampleUrls = [

'http://example.com/login',

'http://test.org/api/data',

'http://monitor.net/metrics'

];

foreach ($sampleUrls as $url) {

$bloomFilter->insert($url);

echo "网络管理监控软件已插入URL:{$url}\n";

}

// 3. 模拟判断URL是不是重复

$testUrls = [

'http://example.com/login', // 已经存过的URL

'http://new-site.com/page', // 新URL

'http://test.org/api/data' // 已经存过的URL

];

foreach ($testUrls as $url) {

if ($bloomFilter->exists($url)) {

echo "网络管理监控软件判断URL【{$url}】可能已存在,需进一步验证去重\n";

} else {

echo "网络管理监控软件判断URL【{$url}】不存在,可直接纳入分析\n";

}

}

?>

三、布隆过滤器在网络管理监控软件中的应用优势

在网络管理监控软件给 URL 去重这件事上,布隆过滤器有不少厉害之处。第一,它特别省资源、速度又快。和传统哈希表比,布隆过滤器按位存数据,内存占用只有哈希表的五分之一到十分之一,而且查询速度始终稳定,不会因为数据变多就变慢,能满足软件实时去重的要求。第二,它虽然偶尔会判断错,但错得不多,也不会影响准确性。只要仔细算好位向量长度和哈希函数数量,就能把误判率压得很低(比如低于 0.0001)。就算误判,也只是把不存在的 URL 误判为可能存在,后续再用数据库查一遍就能修正,绝对不会漏掉已经存在的 URL。第三,它用起来特别方便,还能灵活调整。上面这段 PHP 代码结构简单,直接就能加到软件的数据处理模块里,不用依赖复杂的第三方工具。而且不管 URL 数据量是多是少,都能通过调整初始化参数来适配。

布隆过滤器给网络管理监控软件的 URL 去重提供了一个高效又省事的办法,能让软件跑得更快、更省资源,为网络监控分析打好数据基础。

  • 发表于:
  • 原文链接https://page.om.qq.com/page/ODSz-4C5cqzRq7FqQSdEDvAA0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。
领券