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

文章目录

      • 知识框架
      • 背景
      • 例子
      • 平衡二叉树
      • 散列表
        • 介绍
        • hash函数
        • 选择hash的经验
        • 负载因子
        • 冲突处理
      • 布隆过滤器
        • 介绍
        • 组成
        • 原理
          • 为什么不支持删除操作?
        • 应用场景
        • 应用分析
          • 如何确定n和p?
          • 举例:
          • 选择hash函数
        • 面试题
      • 分布式一致性hash
        • 背景
        • 应用场景
        • hash偏移
          • 解决方法
      • 待补充

知识框架

海量数据去重的Hash与BloomFilter学习笔记_第1张图片

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 次;
总结:通过比较保证有序,通过每次排除一半的元素达到快速索引的目的;

海量数据去重的Hash与BloomFilter学习笔记_第2张图片

散列表

介绍

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

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

struct node{
	void *key;
	void *val;
    struct node *next;
};
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)

海量数据去重的Hash与BloomFilter学习笔记_第3张图片

原理

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

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

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

如果全为1,则可能存在

为什么不支持删除操作?

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

海量数据去重的Hash与BloomFilter学习笔记_第4张图片

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

海量数据去重的Hash与BloomFilter学习笔记_第5张图片

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

在实际应用中,该选择多少个 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. 预期元素个数n不断增大时,假阳率不断增大

海量数据去重的Hash与BloomFilter学习笔记_第6张图片

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

海量数据去重的Hash与BloomFilter学习笔记_第7张图片

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

海量数据去重的Hash与BloomFilter学习笔记_第8张图片

面试题

问: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与BloomFilter学习笔记_第9张图片

应用场景

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

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

hash偏移

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

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

海量数据去重的Hash与BloomFilter学习笔记_第10张图片

解决方法

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

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

待补充

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

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

你可能感兴趣的:(算法与数据结构,数据结构)