本文目录导读:

我来为你详细介绍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% | 大型系统 |
布隆过滤器是解决缓存穿透的绝佳方案,特点:
- ✅ 内存占用极低
- ✅ 查询速度快
- ✅ 实现简单
- ⚠️ 有误判率(但可通过参数控制)
- ❌ 不支持删除操作
建议结合缓存空值、分布式锁等多重手段,构建完整的缓存防御体系。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播