• 海量数据去重的Hash与BloomFilter学习笔记


    知识框架

    在这里插入图片描述

    hyperloglog在redis中介绍

    背景

    • 使用 word 文档时,word 如何判断某个单词是否拼写正确
    • 网络爬虫程序,怎么让它不去爬相同的 url 页面?
    • 垃圾邮件过滤算法如何设计?
    • 公安办案时,如何判断某嫌疑人是否在网逃名单中?
    • 缓存穿透问题如何解决?

    例子

    从海量数据中查询某个字符串是否存在

    平衡二叉树

    增删改查时间复杂度为 O ( l o g 2 n ) O(log_2 n) O(log2n)
    平衡的目的是增删改后,保证下次搜索能稳定排除一半的数据;
    O ( l o g 2 n ) O(log_2 n) O(log2n)的直观理解:100万个节点,最多比较 20 次(n代入计算);10 亿个节点,最多比较 30 次;
    总结:通过比较保证有序,通过每次排除一半的元素达到快速索引的目的;

    在这里插入图片描述

    散列表

    介绍

    根据key 计算 key在表中的位置的数据结构;是key和其所在存储地址的映射关系;

    **注意:**散列表的节点中 key value是存储在一起的

    struct node{
    	void *key;
    	void *val;
        struct node *next;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    hash函数

    映射函数Hash(key) = addr;hash函数可能会把两个或两个以上的不同key映射到同一地址,这种情况称之为冲突(或哈希碰撞)。

    选择hash的经验
    • 计算速度快
    • 强随机分布(等概率、均匀的分布在整个地址空间)
    • murmurhash1, murmurhash2, murmurhash3, siphash (redis 6.0当中使用,rust等大多数语言选用的hash算法来实现hashmap), cityhash具备强随机分布性;
    • 要用就用 murmurhash2和 cityhash
    • siphash主要解决字符串接近的强随机分布性
    负载因子
    • 数组存储元素的个数 / 数据长度;
    • 用来形容散列表存储密度,负载因子越小,冲突越小,负载因子越大,冲突越大。
    • redis中负载因子为1, 而C++ map中采用0.6。
    冲突处理
    • 链表法(最常用)
      • 引用链表来处理哈希冲突,即将冲突元素用链表连接起来。
      • 可能出现极端情况,冲突元素比较多,该冲突链表过长,这时可将这个链表转换为红黑树,复杂度由 O ( n ) O(n) O(n) 转换为 O ( l o g 2 n ) O(log_2 n) O(log2n)
      • 通常我们认为链表长度超过256的时候,则链表过长,要将链表转换为红黑树。
      • redismemcached中 链表法采用头插法,因为局部性原理。
      • redismap都采用链表法
    • 开放寻址法

    将所有的元素都放在哈希表的数组中,不使用额外的数据结构;一般使用线性探查的思路解决:

    1. 当插入新元素时,使用哈希函数再哈希表中定位元素位置;

    2. 检查数组中该槽位索引是否存在元素。如果槽位为空则插入,否则转3

    3. 在2检测的槽位索引上加上一定的步长后 转2. 其中步长有如下选择:

      1. i+1, i+2, i+3, i+4…,i+n

      2. i − 1 2 i-1^2 i12, i + 2 2 i+2^2 i+22, i − 3 2 i-3^2 i32, i + 4 2 i+4^2 i+42,…

        这两种方式都会导致同类hash聚集。第一种同类聚集冲突在前, 第二种知识将聚集冲突延后

    还可以采用双重hash 来解决上面出现的hash聚集现象:

    Hk(key) = [GetHash(key) + k * (1 + (((GetHash(key) >> 5) + 1) % (hashsize – 1)))] % hashsize

    其中,(1 + (((GetHash(key) >> 5) + 1) % (hashsize – 1))) 与 hashsize 互为素数(表示两者除1之外没有共同的质因子)

    执行了 hashsize 次探查后,哈希表中的每一个位置都有且只有一次被访问,即,对于给定的key,对哈希表中的同一位置不会同时用 Hi 和 Hj

    • 这里有点困难,实际只要知道怎么用即可。

    布隆过滤器

    介绍

    布隆过滤器是一种概率型数据结构,它的特点是高效的插入和查询,能确定某个字符串一定不存在或者可能存在

    布隆过滤器不存储具体元素,故占用空间小,查询结果存在误差,但误差可控;

    不支持删除操作

    组成
    • 位图(BIT数组) + n个hash函数
    • 计算优化:m % 2 n 2^n 2n = m & ( 2 n − 1 ) (2^n -1) (2n1)

    在这里插入图片描述

    原理

    当一个元素加入位图时, 通过k个hash函数将这个元素映射到位图的k个点, 并把它们置为1

    当检索时,再通过k个hash函数运算检测位图的k个点是否都为1:

    如果有不为1的点,那么该key一定不存在。

    如果全为1,则可能存在

    为什么不支持删除操作?

    答:位图中每个位只有两种状态(0或1) ,一个槽位被置为1,但无法确定被设置几次;即无法知道被多少个key哈希映射而来,如果置为0,可能导致判断别的key是存在时出现误差。

    在这里插入图片描述

    应用场景
    • 布隆过滤器通常用于判断某个key一定不存在的情况,同时允许判断存在时有误差的情况;
    • 常见场景: 1. 缓存穿透的解决 2. 热key(**一直查询的key加入过滤器,可能存在的时候直接放弃此次查询)**限流
    • 缓存穿透描述方法:

    在这里插入图片描述

    1. **描述缓存场景**,为了减轻数据库(mysql)的访问压力,**在server端与数据库之间加入缓存来存储热点数据**
    1. **描述缓存穿透**,server端请求数据时,缓存和数据库都不包含该数据,最终请求压力全涌向数据库
    1. 数据请求步骤如图中2所示
    1. **发生原因:**黑客利用**伪造数据攻击**或者**内部业务bug造成大量重复请求不存在的数据**
    
    • 1
    • 2
    • 3
    • 4
    应用分析

    在实际应用中,该选择多少个 hash 函数?要分配多少空间的位图?预期存储多少元素?如何控制误差

    公式如下:

    n – 预期布隆过滤器中元素的个数(Number of items in the filter),如上图 只有str1和str2 两个元素 那么 n=2
    p – 假阳率(判断存在错误的概率),在0-1之间 0.000000
    m – 位图所占空间
    k – hash函数的个数
    公式如下:
    n = ceil(m / (-k / log(1 - exp(log§ / k))))
    p = pow(1 - exp(-k / (m / n)), k)
    m = ceil((n * log§) / log(1 / pow(2, log(2))));
    k = round((m / n) * log(2));

    如何确定n和p?

    使用布隆过滤器时,首先要确定n和p,通过上面公式算出m和k,通常可以在下面网站选出合适值:

    https://hur.st/bloomfilter

    举例:

    n = 4000

    p = 0.000000001(1.0E-9)

    m = 172532

    k = 30

    选择hash函数

    选一个hash函数,通过给hash传递不同的种子偏移值,采用线性探寻的方式构造多个hash函数

    #define MIX_UINT64(v) ((uint32_t)((v>>32) ^ (v)))
    uint64_t hash1 = MurmurHash2_x64(key, len, Seed);
    uint64_t hash2 = MurmurHash2_x64(key, len, MIX_UINT64(hash1));
    for(int i = 0; i < k;++i) //k是哈希函数个数
    {
    	Pos[i] = (hash1 + i * hash2) % m; //m是位图的大小
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. 预期元素个数n不断增大时,假阳率不断增大

    在这里插入图片描述

    1. 位图空间m越大,假阳率p就越小

    在这里插入图片描述

    1. 假阳率p和哈希函数个数k的关系

    在这里插入图片描述

    面试题

    问:hash函数实现过程中为什么出现 i * 31

    答: i * 31 = i * (32 - 1) = i * (1<<5 - 1) = i<<5 - i; 且31是质数,hash随机分布性比较好。

    分布式一致性hash

    背景

    分布式一致性hash算法将hash空间组织成一个虚拟圆环,大小为 2 32 2^{32} 232;

    算法为:hash(ip) % 2 32 2^{32} 232 ,计算后得到一个[0, 2 32 − 1 2^{32}-1 2321]之间的一个无符号整型,这个数代表服务器编号;

    多个服务器通过这种方式在hash环上映射一个点来标识该服务器的位置;当用户操作某个key,通过同样的算法生成一个值,沿环顺时针定位某个服务器,那么该key就在该服务器中。

    在这里插入图片描述

    应用场景

    分布式缓存;将数据均衡地分散在不同的服务器中,用来分摊缓存服务器压力

    解决缓存服务器数量变化尽量不影响缓存失效

    hash偏移

    hash算法得到的结果是随机的,不能保证服务器解决均匀分布在哈希环上;分布不均匀导致请求访问不均匀,使得服务器承受压力不均匀。

    造成的主要原因还是因为节点数量不够

    在这里插入图片描述

    解决方法

    采用增加虚拟节点的方法,理论上,哈希环上的节点数据越多,数据分布越均衡;

    为每个服务节点计算多个哈希节点(虚拟节点),通常做法是:hash(“IP:PORT:seqno”) % 2 32 2^{32} 232

    待补充

    • 分布式一致性hash增加或删除节点怎么进行数据迁移

      参考https://github.com/metang326/consistent_hashing_cpp

  • 相关阅读:
    Day119.尚医通:取消预约(微信退款)、就医提醒(定时任务)、预约统计
    新鲜出炉:appium2.0+ 单点触控和多点触控新的解决方案
    vue项目如何防范XSS攻击?
    CSS基础(超详解)
    MYSQL 高可用集群搭建 ---MHA
    Java 21 新功能展示(含示例)
    基于Netty的高性能API网关设计
    语义推理的功能组件动态绑定研究
    nginx进行资源动静分离
    基于ssm的智慧餐厅点餐管理系统-计算机毕业设计源码
  • 原文地址:https://blog.csdn.net/qq_42120843/article/details/126819206