«

布隆过滤器 php+redis

时间:2024-5-21 15:45     作者:聆听岁月     分类: php


前言

在实际开发中,我们是否有遇到以下情况。

基于此,导致数据库宕机了。怎么来处理呢?

答案:做布隆过滤吧

布隆过滤:目标数据晒查出来的结果是:可能存在;一定不存在。

原理

把集合中的每一个值按照提供的Hash算法算出对应的Hash值,然后将Hash值对数组长度取模后得到需要计入数组的索引值,并且将数组这个位置的值从0改成1。在判断一个元素是否存在于这个集合中时,你只需要将这个元素按照相同的算法计算出索引值,如果这个位置的值为1就认为这个元素极大可能在集合中,否则认为绝对不在集合中。
在这里插入图片描述
hash解析映射

在这里插入图片描述
redis bit map 映射

实现

1、先定义出hash算法。用于对数据进行hash计算

class BloomFilterHash
{
    /**
     * 由Justin Sobel编写的按位散列函数
     */
    public function JSHash($string, $len = null)
    {
        $hash = 1315423911;
        $len || $len = strlen($string);
        for ($i=0; $i<$len; $i++) {
            $hash ^= (($hash << 5) + ord($string[$i]) + ($hash >> 2));
        }
        return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;
    }

    /**
     * 该哈希算法基于AT&T贝尔实验室的Peter J. Weinberger的工作。
     * Aho Sethi和Ulman编写的“编译器(原理,技术和工具)”一书建议使用采用此特定算法中的散列方法的散列函数。
     */
    public function PJWHash($string, $len = null)
    {
        $bitsInUnsignedInt = 4 * 8; //(unsigned int)(sizeof(unsigned int)* 8);
        $threeQuarters = ($bitsInUnsignedInt * 3) / 4;
        $oneEighth = $bitsInUnsignedInt / 8;
        $highBits = 0xFFFFFFFF << (int) ($bitsInUnsignedInt - $oneEighth);
        $hash = 0;
        $test = 0;
        $len || $len = strlen($string);
        for($i=0; $i<$len; $i++) {
            $hash = ($hash << (int) ($oneEighth)) + ord($string[$i]); } $test = $hash & $highBits; if ($test != 0) { $hash = (($hash ^ ($test >> (int)($threeQuarters))) & (~$highBits));
        }
        return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;
    }

    /**
     * 类似于PJW Hash功能,但针对32位处理器进行了调整。它是基于UNIX的系统上的widley使用哈希函数。
     */
    public function ELFHash($string, $len = null)
    {
        $hash = 0;
        $len || $len = strlen($string);
        for ($i=0; $i<$len; $i++) {
            $hash = ($hash << 4) + ord($string[$i]); $x = $hash & 0xF0000000; if ($x != 0) { $hash ^= ($x >> 24);
            }
            $hash &= ~$x;
        }
        return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;
    }

    /**
     * 这个哈希函数来自Brian Kernighan和Dennis Ritchie的书“The C Programming Language”。
     * 它是一个简单的哈希函数,使用一组奇怪的可能种子,它们都构成了31 .... 31 ... 31等模式,它似乎与DJB哈希函数非常相似。
     */
    public function BKDRHash($string, $len = null)
    {
        $seed = 131;  # 31 131 1313 13131 131313 etc..
        $hash = 0;
        $len || $len = strlen($string);
        for ($i=0; $i<$len; $i++) {
            $hash = (int) (($hash * $seed) + ord($string[$i]));
        }
        return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;
    }

    /**
     * 这是在开源SDBM项目中使用的首选算法。
     * 哈希函数似乎对许多不同的数据集具有良好的总体分布。它似乎适用于数据集中元素的MSB存在高差异的情况。
     */
    public function SDBMHash($string, $len = null)
    {
        $hash = 0;
        $len || $len = strlen($string);
        for ($i=0; $i<$len; $i++) {
            $hash = (int) (ord($string[$i]) + ($hash << 6) + ($hash << 16) - $hash);
        }
        return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;
    }

    /**
     * 由Daniel J. Bernstein教授制作的算法,首先在usenet新闻组comp.lang.c上向世界展示。
     * 它是有史以来发布的最有效的哈希函数之一。
     */
    public function DJBHash($string, $len = null)
    {
        $hash = 5381;
        $len || $len = strlen($string);
        for ($i=0; $i<$len; $i++) {
            $hash = (int) (($hash << 5) + $hash) + ord($string[$i]);
        }
        return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;
    }

    /**
     * Donald E. Knuth在“计算机编程艺术第3卷”中提出的算法,主题是排序和搜索第6.4章。
     */
    public function DEKHash($string, $len = null)
    {
        $len || $len = strlen($string);
        $hash = $len;
        for ($i=0; $i<$len; $i++) {
            $hash = (($hash << 5) ^ ($hash >> 27)) ^ ord($string[$i]);
        }
        return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;
    }

    /**
     * 参考 http://www.isthe.com/chongo/tech/comp/fnv/
     */
    public function FNVHash($string, $len = null)
    {
        $prime = 16777619; //32位的prime 2^24 + 2^8 + 0x93 = 16777619
        $hash = 2166136261; //32位的offset
        $len || $len = strlen($string);
        for ($i=0; $i<$len; $i++) {
            $hash = (int) ($hash * $prime) % 0xFFFFFFFF;
            $hash ^= ord($string[$i]);
        }
        return ($hash % 0xFFFFFFFF) & 0xFFFFFFFF;
    }
}

2、抽象布隆过滤操作

<?php

namespace App\Components\BloomFilter\Core;

/**
 * 布隆过滤
 */
abstract class BloomFilterCore
{
    /**
     * 增加
     *
     * @param string $bucket
     * @param string $string
     */
    abstract public function add(string $bucket,string $string) : void;

    /**
     * 效验
     *
     * @param string $string
     * @return bool
     */
    abstract public function exists(string $bucket,string $string) : bool;
}

3、布隆过滤封装实现

a、通用版 - redis

<?php

namespace App\Components\BloomFilter;

use App\Components\BloomFilter\Core\BloomFilterCore;
use App\Components\BloomFilter\Core\HashFunction;
use App\Components\BloomFilter\Core\HashFunctionType;
use App\Exceptions\AppException;
use Illuminate\Redis\RedisManager;

/**
 * 布隆过滤  通用版
 */
class BloomFilterBase extends BloomFilterCore
{
    /**
     * bucket 名称
     *
     * @var string
     */
    protected string $bucket = 'base';

    /**
     * hash 算法
     *
     * @var array
     */
    protected array $hashFunction = [
        HashFunctionType::BKDRHash,
        HashFunctionType::SDBMHash,
        HashFunctionType::JSHash
    ];

    protected HashFunction $hashFunctionObj;

    protected RedisManager $redis;

    public function __construct()
    {
        if (empty($this->bucket) || empty($this->hashFunction))
        {
            throw new AppException("需要定义bucket和hashFunction");
        }

        $this->hashFunctionObj = new HashFunction();

        $this->redis = app()->get('redis');
    }

    /**
     * 添加到集合中
     */
    public function add(string $bucket,string $string) : void
    {

        /**
         * @var \Redis $pipe
         */
        $pipe = $this->redis->multi();
        foreach ($this->hashFunction as $function)
        {
            $hash = $this->handlerHashNum(
                call_user_func([$this->hashFunctionObj,$function->value],$string)
            );
            $this->redis->setBit($bucket ?? $this->bucket, $hash, 1);
        }
        $pipe->exec();
    }

    public function handlerHashNum($num) : int
    {
        $limit = 11;

        if($num > $limit * 100000000)
        {
            $num = substr($num,0,9);
        }

        return intval($num);
    }

    /**
     * 查询是否存在, 如果曾经写入过,必定回true,如果没写入过,有一定几率会误判为存在
     */
    public function exists(string $bucket,string $string) : bool
    {
        /**
         * @var \Redis $pipe
         */
        $pipe = $this->redis->multi();
        $len = strlen($string);
        foreach ($this->hashFunction as $function)
        {
            $hash = $this->handlerHashNum(
                call_user_func([$this->hashFunctionObj,$function->value],$string)
            );
            $pipe = $pipe->getBit($bucket ?? $this->bucket, $hash);
        }
        $res = $pipe->exec();

        foreach ($res as $bit)
        {
            if ($bit == 0)
            {
                return false;
            }
        }
        return true;
    }
}

b、integer 版 - redis

<?php

namespace App\Components\BloomFilter;

use App\Components\BloomFilter\Core\BloomFilterCore;
use App\Exceptions\AppException;
use Illuminate\Redis\RedisManager;

/**
 * 布隆过滤  integer 性能版
 */
class BloomFilterIntegerBase extends BloomFilterCore
{
    /**
     * bucket 名称
     *
     * @var string
     */
    protected string $bucket = 'integer_base';

    protected RedisManager $redis;

    public function __construct()
    {
        if (empty($this->bucket))
        {
            throw new AppException("需要定义bucket和hashFunction");
        }

        $this->redis = app()->get('redis');
    }

    public function add(string $bucket, string $string): void
    {
        $this->redis->setBit($bucket ?? $this->bucket, intval($string), 1);
    }

    public function exists(string $bucket, string $string): bool
    {
        return $this->redis->getBit($bucket ?? $this->bucket, intval($string)) != 0;
    }

}

关于使用

<?php
//布隆过滤 - 通用版 - 测试
$key = 'mall_shop_2';

\App\Components\BloomFilter\BloomFilter::add('test1',$key);

$result2 = \App\Components\BloomFilter\BloomFilter::exists('test1',$key);

dd([
    // '$result1'  =>  $result1,
    '$result2'  =>  $result2,
]);

//布隆过滤 - integer版 - 测试

$key = '1111';

\App\Components\BloomFilter\BloomFilterInteger::add('test1',$key);

$result2 = \App\Components\BloomFilter\BloomFilterInteger::exists('test1',$key);

dd([
    // '$result1'  =>  $result1,
    '$result2'  =>  $result2,
]);

应用场景