PHP布隆过滤器防穿透

wen PHP项目 4

本文目录导读:

PHP布隆过滤器防穿透

  1. 什么是布隆过滤器
  2. 核心原理
  3. PHP实现方案
  4. 实际应用注意事项
  5. 性能对比

我来为你详细介绍PHP中实现布隆过滤器防止缓存穿透的方案。

什么是布隆过滤器

布隆过滤器是一种空间效率极高的概率型数据结构,用于判断一个元素是否在集合中,它可能会误判(假阳性),但绝不会漏判(假阴性)。

核心原理

  1. 初始化:创建一个m位的位数组,全部置为0
  2. 添加元素:使用k个哈希函数,将元素映射到k个位置,置为1
  3. 查询元素:检查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% 大型系统

布隆过滤器是解决缓存穿透的绝佳方案,特点:

  • ✅ 内存占用极低
  • ✅ 查询速度快
  • ✅ 实现简单
  • ⚠️ 有误判率(但可通过参数控制)
  • ❌ 不支持删除操作

建议结合缓存空值、分布式锁等多重手段,构建完整的缓存防御体系。

抱歉,评论功能暂时关闭!