海量数据查找与布隆过滤器

背景:
  1. 在使用word文档时,word如何判断某个单词是否拼写正确?
  2. 网络爬虫程序,怎么让它不去爬相同的url页面?
  3. 垃圾邮件(短信)过滤算法如何设计?
  4. 公安办案时,如何判断嫌疑人是否在网逃名单中?
  5. 缓存穿透问题如何解决?
 
 
缓存穿透
 
 
mysql数据库,为了快速索引,减少磁盘IO,使用b+树;mysql中可能有缓冲池,查询缓存。但是,server向mysql请求数据过于频繁的话,mysql可能会承受不住。这时候需要引入缓存数据库redis,redis为了减少server访问db的压力。
 
缓存穿透:
  1. 请求数据的时候:server redis mysql。
  2. 数据访问步骤:
    1. 先访问redis,若存在,直接返回;如果不存在
    2. 访问mysql,如果不存在,直接返回;如果存在,返回数据,且
    3. 将mysql存在的key写回到redis。
  3. 所谓穿透,就是redis和mysql都没有数据,黑客利用这个漏洞,不断请求这个数据,导致数据压力堆积在mysql中,最终导致系统崩溃。
 
解决方案
  1. 在redis端设置<key, null>键值对,以此避免访问mysql;缺点是<key, null>过多的话,会占用过多的内存
    1. 可以设置key过期,这样redis就不会存在过多的数据,黑客停止攻击之后,这些key会被淘汰。
  2. 在server端存储一个布隆过滤器,将mysql包含的key放入布隆过滤器中;布隆过滤器一定能过滤不存在的数据。即利用这个数据结构快速查询数据是否在mysql中。
 
主题:用字符串去海量字符串库查询是否存在?
  1. set,map和unordered_map
  2. set和map用红黑树实现的,unordered_map用哈希表实现。
    1. set和map关键区别,set不存储val字段。
  3. 红黑树全称为平衡二叉搜索树,不是严格的平衡二叉搜索树
    1. 红黑树时间复杂度是O(logn)
    2. 查找的时候,最差的情况是树的高度
    3. 红黑树的最长路径是最短路径的两倍
    4. 红黑树能不能插入相同的节点,看实现,可以实现为插入或者更新,或者直接丢弃
    5. 红黑树优点:存储效率高,访问速度高效。例如当int作为key时。
    6. 缺点:对于数据量大且查询字符串比较长且查询字符串相似时将会是噩梦。邮件或短信作为key,比较慢;字符串相似时,耗时间。
  4. AVL树:是严格的平衡二叉树
    1. 时间复杂度接近O(logn)
 
例子
  • 100w条数据组成的红黑树,只需要比较20次就能找到该值;对于10亿条数据,只需要比较30次,就能找到该数据。
  • 问题:比较20,30次,如果是垃圾邮件,垃圾短信过滤,这种长字符串,比较二三十次,效率也很低,所以不适用于红黑树。
 
unordered_map
  1. STL中的unordered_map使用hashtable实现的。
  2. 构成:数组+hash函数
  3. 它是将字符串通过hash函数生成一个整数,再映射到数组当中;增删改查的时间复杂度是O(1);
  4. hash函数的作用:避免插入的时候字符串的比较
  5. 如何选取hash函数:
    1. 计算速度快
    2. 相似字符串能保持强随机分布(放碰撞);
    3. 例如Murmurhash2, siphash, cityhash函数。
  6. 负载因子:数组存储元素的个数:负载因子越小,冲突越大;负载因子越大,冲突越小;
    1. 负载因子<1最好
  7. hash冲突解决方案:
    1. 链表法:将冲突的元素,用链表的方式连接起来;极端情况,链表过长,为了减少比较次数,可以将这个链表转换为红黑树(JAVA 8)。经验值:超过256或512的时候转换为红黑树。redis 6.0,对内存有严格的限制,使用链表发,并且是头插法。
    2. 开放地址法:所有元素都存在于哈希表中,不使用额外的数据结构。一般使用线性探测的思路解决冲突:
      1. 当插入元素时,使用hash函数在哈希表中定位元素位置;
      2. 检查数组中该槽位是否存储元素。如果该槽位为空,则插入,否则3;
      3. 在2检测的槽位索引上加上一定的步长,接着检查2;加步长有如下方法:
        1. i + 1, i + 2, i + 3, ...
        2. i - 1^2, i + 2^2, i - 3^3, ...
        3. 这两种都会导致hash聚集,也就是近似值它的哈希值也相似。第二种只是将hash冲突延后,没有根治。
        4. 解决办法:使用双重hash(.net里面)。利用互质传递性,减少碰撞。
  8. hash表同样存储了key和value,key没有顺序。同样可以修改代码,让插入数据变成修改操作。
  9. 如果数组长度过小,导致链表长度过长,可以一:将链表改为红黑树 二:rehash
  10. 优点:访问速度更快;不需要进程字符串比较
  11. 缺点:需要引入策略避免冲突,存储效率不高;空间换时间。
 
问题:set,map和unordered_map都需要存储key值。如果是垃圾邮件,key过大,需要存储key。海量数据下,没有那么大内存。
解决访问:使用布隆过滤器。布隆过滤器不用存储key就可以知道数据是否存在。
 
布隆过滤器
  1. 定义:布隆过滤器是一种概率型数据结构(跳表也是),它的特点是高效的插入和查询,能明确告知某个字符串一定不存在或可能存在。适用于误差不是零容忍的情况。
  2. 布隆过滤器相比于传统的查询结构(hash, set, map等结构)更加高效,占用空间更小;但缺点是它的结果是概率性的,也就是说结果存在误差,虽然这个误差是可控的;同时它不支持删除操作。
  3. 组成:位图(bit) + n个hash函数。在C++中可以使用byte b[8]来构建64bit的位图。
  4. 原理:当一个元素加入位图时,通过k个hash函数将这个元素映射到位图的k个点,并把它们置为1;当检索时,再通过k个hash函数检测位图的k个点是否都为1;如果有不为1的点,那么认为不存在;如果全部为1,则可能存在(存在误差)。
  5. 在位图中每个槽位只有两种状态(0或1),一个槽位被设置为1状态,但不明确它被设置了多少次;也就是不知道被多少个str哈希映射以及是被哪个hash函数映射过来的;所以不支持删除操作。
  6. 在实际应用过程中,布隆过滤器如何使用?要选择多少个hash函数,要分配多少空间的位图,存储多少元素?另外如何控制假阳率?Bloom Filter Calculator
  1. 已知k,如何选择k个hash函数?
// 采用一个hash函数,给hash传不同的种子偏移 // #define MIX_UNIT64(v) ((uint32_t)((v>>32)^(32))) uint64_t hash1 = MurmurHash2_64(key, len, Seed); uint64_t hash2 = MurmurHash2_x64(key, MIX_UINT64(hash1)); for (i = 0; i < k; i++) // k是hash函数的个数 { Pos[i] = (hash1 + i * hash2) %m; // m是位图的大小 } //通过这种方式模拟k个hash函数 和前面开放寻址法 双重hash是一样的思路
 
附:
 

posted @ 2021-11-03 14:26  去伪存真  阅读(116)  评论(0编辑  收藏  举报