缓存失效之布隆过滤|青训营笔记

91 阅读5分钟

缓存失效之布隆过滤|青训营笔记

这是我参与「第五届青训营」伴学笔记创作活动的第21天

在后面会依次倒叙回顾之前的学习课程,便于复习~

一、重点内容

  • 缓存失效
  • 布隆过滤算法

二、缓存失效

都说redis好用,但亦有缺陷,今天学习的是关于缓存失效的两种典型现象~

  • 缓存击穿

    缓存击穿是指,针对某个热key,突然在缓存中失效,然后这些请求到热点数据的请求会都请求到数据库。 缓存击穿一般是热key 在 Redis 中过期了导致的。 最直接的方法就是,对于热 key ,就不设置过期时间,那么如何发现热key呢,目前字节内部是,采用中间件来进行发现,只要请求次数大于500,就可以识别为热key。

  • 缓存穿透

    缓存穿透指的是,数据既不在 Redis 中,也不在数据库中。每次请求 Redis 发现没有对应的 key之后,再去请求数据库,发现数据库也没有。 那么这时, Redis 就相当于一个摆设,没有具体的作用了。如恶意攻击系统,故意使用空值或者其他不存在的值进行频繁请求,那么也会对数据库造成比较大的压力。为了避免缓存穿透,可以: 1、缓存空值或缺省值

    2、采用布隆过滤算法,提前判断是否有此数据。

三、布隆过滤

布隆过滤器实际上就是把 key 通过三次不同的哈希,计算出三个哈希值,然后在哈希表中把对应哈希值位置置为1。

当有新的请求过来时,先判断这个 key 经过N次哈希后,对应的哈希值位置是否为1,只要有一个不为1,就说明此 key 之前没有缓存过。

img实际上,布隆过滤器也是有缺陷的,它不能完全保证请求过来的 key ,通过布隆过滤器的校验,就一定有这个数据。 但是,只要没有通过布隆过滤器的校验,那么这个 key 就一定不存在。 其实这样就已经可以过滤掉大部分不存在的 key 请求了。 正如以上提到的布隆过滤器缺陷,如果布隆过滤器的哈希槽过短,很有可能导致大部分的位置都为 1 ,那么此时,布隆过滤器就失去了它的意义。 所以,当我们发现布隆过滤器大部分位置都为1了,应该要扩宽哈希槽。

在实际业务中,我们对于请求的参数应该要先进行校验,请求的参数应该要在规定范围内。实际上,在工程应用中,主要也是依赖于参数的校验,过滤掉很多无效请求。

如果布隆过滤器失效了,或者说误判了,应该考虑reset或者扩展哈希槽。下面针对不同场景进行考虑,如果是考生注册与登录系统,具体做法是判断当前考生是否注册,通过布隆过滤器,判断出:

  • 已注册,不一定已注册,需要进入数据库或者redis里面查询一次,如果出现误判,则需要进行扩展哈希槽,或者申请更大的哈希数组(位图),这时也需要rehash,建议是直接reset后再进行重建。
  • 未注册,一定是未注册,直接去数据库CRUD。

还有一种用途是只查找,不插入,说白了就像是一层防护栏,一般用于恶意的用户登录,这也是用的最多的场景,一般是将用户的个人信息进行hash,然后存入位图中,用于检索当前的用户。

三、具体实现

 #include <string>
 #include <vector>
 #include <type_traits>
 using namespace std;
 class BitMap
 {
 public:
     //位图的内存大小和数据范围有关
     BitMap(size_t range)
         :_bit(range / 32 + 1)
     {}
 ​
     void set(const size_t num)
     {
         //计算数组中的下标
         int idx = num / 32;
         //计算num在对应下标整数中的下标位置
         int bitIdx = num % 32;
         //将对应的比特位置1
         _bit[idx] |= 1 << bitIdx;
     }
 ​
     bool find(const size_t num)
     {
         int idx = num / 32;
         int bitIdx = num % 32;
         return (_bit[idx] >> bitIdx) & 1;
     }
 ​
     void reset()
     {
         _bit.assign(_bit.size(),0);//清除内容,但不回收空间,这里不能用 _bit.swap(vector<int>());
     }
 private:
     vector<int> _bit;
 };
 template<class T>
 struct HashFun1
 {
     //将字符串的每个字符通过计算得到一个hash值
     //仅支持数字与字符串
     size_t operator()(const T& val)
     {
         size_t hash = 0;
         string str;
         if( is_same<int,T>::value){
             str = to_string(val);
         }else{
             str = val;
         }
         for (const auto& ch : str)
         {
             hash = hash * 131 + ch;
         }
         return hash;
     }
 };
 template<class T>
 struct HashFun2
 {
     size_t operator()(const T& val)
     {
         size_t hash = 0;
         string str;
         if(is_same<int,T>::value){
             str = to_string(val);
         }else{
             str = val;
         }
         for (const auto& ch : str)
         {
             hash = hash * 65599 + ch;
         }
         return hash;
     }
 };
 template<class T>
 struct HashFun3
 {
     size_t operator()(const T& val)
     {
         size_t hash = 0;
         string str;
         if(is_same<int,T>::value){
             str = to_string(val);
         }else{
             str = val;
         }
         for (const auto& ch : str)
         {
             hash = hash * 1313131 + ch;
         }
         return hash;
     }
 };
 // TODO: 定义一个布隆过滤器类
 // TODO:模板类,包含过滤器的元素类型、哈希函数类型
 template<class T,class HashFun1,class HashFun2,class HashFun3>
 class BloomFilter
 {
 public:
     BloomFilter(const size_t num)
         :_bit(5 * num)
         , _bitTotCount(5 * num),
         _bitCnt(0)
     {}
 ​
     void set(const T& val)
     {
         HashFun1 h1;
         HashFun2 h2;
         HashFun3 h3;
         int idx1 = h1(val) % _bitTotCount;
         int idx2 = h2(val) % _bitTotCount;
         int idx3 = h3(val) % _bitTotCount;
         _bit.set(idx1);
         _bit.set(idx2);
         _bit.set(idx3);
         _bitCnt+=3;
     }
 ​
     bool find(const T& val)
     {
         HashFun1 h1;
         HashFun2 h2;
         HashFun3 h3;
         int idx1 = h1(val) % _bitTotCount;
         int idx2 = h2(val) % _bitTotCount;
         int idx3 = h3(val) % _bitTotCount;
 ​
         if (!_bit.find(idx1))
             return false;
         if (!_bit.find(idx2))
             return false;
         if (!_bit.find(idx3))
             return false;
 ​
         return true;//可能存在
     }
     // 误判率达到50%即可进行位图reset
     bool FP(){
         if (_bitCnt>_bitTotCount/2) return true;
         return false;
     }
     void reset(){
         _bit.reset();
         _bitCnt = 0;
     }
 private:
     BitMap _bit;//位图
     size_t _bitTotCount;//位图的大小
     size_t _bitCnt;//当前位图的元素数目
 };