本文目录导读:

我来为你详细介绍PHP中实现布隆过滤器防止缓存穿透的方案。
什么是布隆过滤器
布隆过滤器是一种空间效率极高的概率型数据结构,用于判断一个元素是否在集合中,它可能会误判(假阳性),但绝不会漏判(假阴性)。
核心原理
- 初始化:创建一个m位的位数组,全部置为0
- 添加元素:使用k个哈希函数,将元素映射到k个位置,置为1
- 查询元素:检查k个位置是否全为1
PHP实现方案
基础实现(使用Redis)
<?php
class BloomFilterRedis
{
private $redis;
private $key;
private $bitSize; // 位数组大小
private $hashCount; // 哈希函数数量
public function __construct($redis, $key = 'bloom_filter', $bitSize = 100000, $hashCount = 5)
{
$this->redis = $redis;
$this->key = $key;
$this->bitSize = $bitSize;
$this->hashCount = $hashCount;
}
/**
* 添加元素到布隆过滤器
*/
public function add($item)
{
for ($i = 0; $i < $this->hashCount; $i++) {
$bit = $this->hash($item, $i) % $this->bitSize;
$this->redis->setBit($this->key, $bit, 1);
}
}
/**
* 批量添加
*/
public function addMultiple(array $items)
{
$pipe = $this->redis->pipeline();
foreach ($items as $item) {
for ($i = 0; $i < $this->hashCount; $i++) {
$bit = $this->hash($item, $i) % $this->bitSize;
$pipe->setBit($this->key, $bit, 1);
}
}
$pipe->exec();
}
/**
* 检查元素是否可能存在
*/
public function contains($item)
{
for ($i = 0; $i < $this->hashCount; $i++) {
$bit = $this->hash($item, $i) % $this->bitSize;
if (!$this->redis->getBit($this->key, $bit)) {
return false;
}
}
return true;
}
/**
* 哈希函数实现
*/
private function hash($item, $index)
{
switch ($index % 3) {
case 0:
return crc32($item . $index);
case 1:
return abs(crc32(sha1($item)) + $index);
case 2:
return abs(md5($item . $index) + $index * 31);
default:
return crc32($item);
}
}
/**
* 重置过滤器
*/
public function reset()
{
$this->redis->del($this->key);
}
}
完整缓存穿透防御方案
<?php
class CacheAntiPenetration
{
private $redis;
private $bloomFilter;
private $cachePrefix = 'cache:';
private $lockPrefix = 'lock:';
private $emptyValue = '__EMPTY__';
public function __construct($redisConnection)
{
$this->redis = $redisConnection;
$this->bloomFilter = new BloomFilterRedis(
$redisConnection,
'product_filter',
1000000,
7
);
}
/**
* 初始化布隆过滤器(请求量大时)
*/
public function initBloomFilter($key, $callback, $batchSize = 1000)
{
// 获取所有键
$cursor = '0';
$count = 0;
do {
$result = $this->redis->scan($cursor, [
'match' => $key . '*',
'count' => $batchSize
]);
$cursor = $result[0];
$keys = $result[1];
foreach ($keys as $dbKey) {
$originalKey = substr($dbKey, strlen($this->cachePrefix));
$this->bloomFilter->add($originalKey);
$count++;
}
} while ($cursor !== '0');
// 如果有回调函数,也可以用于初始化数据库数据
if (is_callable($callback)) {
call_user_func($callback, $this->bloomFilter);
}
return $count;
}
/**
* 查询数据(防穿透)
*/
public function query($key, $loadDataCallback)
{
// 1. 检查布隆过滤器
if (!$this->bloomFilter->contains($key)) {
return null; // 肯定不存在
}
// 2. 查询缓存
$cacheKey = $this->cachePrefix . $key;
$cached = $this->redis->get($cacheKey);
if ($cached !== false) {
if ($cached === $this->emptyValue) {
return null; // 缓存了空值,防止缓存击穿
}
return json_decode($cached, true);
}
// 3. 加分布式锁,防止缓存击穿
$lockKey = $this->lockPrefix . $key;
$lockValue = uniqid();
$timeout = 5; // 锁超时时间
if (!$this->acquireLock($lockKey, $lockValue, $timeout)) {
// 没拿到锁,等待重试
usleep(100000); // 100ms
return $this->query($key, $loadDataCallback);
}
try {
// 4. 查询数据库
$data = call_user_func($loadDataCallback, $key);
// 5. 写入缓存
if ($data === null || empty($data)) {
// 缓存空值,设置较短过期时间
$this->redis->setex($cacheKey, 60, $this->emptyValue);
} else {
// 缓存真实数据
$this->redis->setex($cacheKey, 3600, json_encode($data));
}
return $data;
} finally {
// 释放锁
$this->releaseLock($lockKey, $lockValue);
}
}
/**
* 获取分布式锁
*/
private function acquireLock($key, $value, $timeout)
{
$lock = $this->redis->set($key, $value, [
'NX', 'EX' => $timeout
]);
if ($lock === true) {
return true;
}
// 简单重试机制
$retries = 3;
while ($retries > 0) {
usleep(50000); // 50ms
$lock = $this->redis->set($key, $value, [
'NX', 'EX' => $timeout
]);
if ($lock === true) {
return true;
}
$retries--;
}
return false;
}
/**
* 释放锁
*/
private function releaseLock($key, $value)
{
$script = <<<LUA
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
LUA;
$this->redis->eval($script, [$key, $value], 1);
}
}
使用示例
<?php
// 创建Redis连接
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
// 实例化防护类
$cache = new CacheAntiPenetration($redis);
// 初始化布隆过滤器(启动时执行一次)
$cache->initBloomFilter('product:', function($bloomFilter) {
// 从数据库加载所有商品ID
$products = getAllProductIdsFromDB();
$bloomFilter->addMultiple($products);
});
// 查询商品数据
$productId = '1001';
$product = $cache->query('product:' . $productId, function($key) {
// 从数据库查询
$id = str_replace('product:', '', $key);
$result = getProductByIdFromDB($id);
return $result ?: null;
});
if ($product === null) {
echo "商品不存在";
} else {
echo "商品数据: " . json_encode($product);
}
性能优化版本
<?php
class OptimizedBloomFilter
{
private $bitSize;
private $hashCount;
private $bitArray = [];
public function __construct($bitSize = 100000, $hashCount = 5)
{
$this->bitSize = $bitSize;
$this->hashCount = $hashCount;
$this->bitArray = array_fill(0, ceil($bitSize / 8), 0);
}
/**
* 批量添加(性能优化)
*/
public function addBatch(array $items)
{
foreach ($items as $item) {
$positions = $this->getPositions($item);
foreach ($positions as $pos => $bitValue) {
$byteIndex = floor($pos / 8);
$bitIndex = $pos % 8;
$this->bitArray[$byteIndex] |= (1 << $bitIndex);
}
}
}
/**
* 优化后的哈希计算
*/
private function getPositions($item)
{
$positions = [];
$hash1 = crc32($item);
$hash2 = md5($item);
for ($i = 0; $i < $this->hashCount; $i++) {
$combined = ($hash1 + $i * $hash2) % $this->bitSize;
$positions[$combined] = $combined;
}
return $positions;
}
/**
* 序列化和反序列化(用于持久化)
*/
public function export()
{
return base64_encode(gzcompress(serialize($this->bitArray)));
}
public function import($data)
{
$this->bitArray = unserialize(gzuncompress(base64_decode($data)));
}
}
实际应用注意事项
参数选择
// 计算最优参数
function calculateOptimalParams($expectedItems, $falsePositiveRate = 0.01)
{
$bitSize = ceil((-1 * $expectedItems * log($falsePositiveRate)) / pow(log(2), 2));
$hashCount = ceil(log(2) * ($bitSize / $expectedItems));
return [
'bitSize' => $bitSize,
'hashCount' => $hashCount
];
}
$params = calculateOptimalParams(1000000); // 100万数据
echo "位数组大小: {$params['bitSize']}";
echo "哈希函数数量: {$params['hashCount']}";
定期更新策略
// 定期重建布隆过滤器
public function periodicUpdate($interval = 3600)
{
while (true) {
sleep($interval);
echo "开始更新布隆过滤器...";
// 使用临时key
$tempKey = $this->bloomFilter->key . '_temp_' . time();
// 创建新的过滤器
$tempFilter = new BloomFilterRedis(
$this->redis,
$tempKey,
$this->bloomFilter->bitSize,
$this->bloomFilter->hashCount
);
// 重新加载数据
$this->initBloomFilter($tempFilter);
// 原子切换
$this->redis->rename($tempKey, $this->bloomFilter->key);
echo "布隆过滤器更新完成";
}
}
性能对比
| 方案 | 内存占用 | 查询速度 | 误判率 | 适用场景 |
|---|---|---|---|---|
| Redis位图 | 约12.5KB | ~1ms | 1% | 分布式系统 |
| PHP数组 | 约100KB | ~3ms | 1% | 单机应用 |
| Redis BloomFilter模块 | 约12.5KB | ~0.3ms | 1% | 大型系统 |
布隆过滤器是解决缓存穿透的绝佳方案,特点:
- ✅ 内存占用极低
- ✅ 查询速度快
- ✅ 实现简单
- ⚠️ 有误判率(但可通过参数控制)
- ❌ 不支持删除操作
建议结合缓存空值、分布式锁等多重手段,构建完整的缓存防御体系。