hash
背景
使用word文档时,word日和判断某个单词是否拼写正确?
网络爬虫程序,怎么然他不去爬相同的url页面
垃圾邮件过滤算法怎么设计
公安办案时,怎么判断某嫌疑人是否在网逃名单中
缓存穿透问题如何解决
需求
从海量数据中查询某个字符串是否存在
平衡二叉树
增删改查时间复杂度为O(log2n);
平衡的目的是增删改后,保证下次搜索能稳定排除一半的数据;
O(log2n)的直观理解:100万个节点,最多比较20次;10亿个节点最多比较30次;
总结:通过比较保证有序,通过每次排除一般的元素达到快速索引的目的。
散列表
根据key计算key在表中的位置的数据结构;是key和其所在存储地址的映射关系;
注意散列表中节点中kv是存储在一起的;
struct node{
void *key;
void *val;
struct node *next;
}
hash函数
映射函数Hash(key)=addr;hash函数可能会把两个或两个以上的不同key映射到同一地址,这种情况惩治为冲突(或hash碰撞);
选择hash
- 计算速度快
- 强随机分布(等概率、均匀的分布在整个地址空间)
- murmurhash1,murmurhash2,murmurhash3,siphash(redis6中使用,rust等大多数语言选用的hash语言实现hashmap),cityhash都具备强随机分布性值。https://github.com/aappleby/smhasher
- siphash主要解决字符串接近的强随机分布性;
负载因子
数组存储元素的个数/数据长度;用来形容散列表的存储密度;负载因子越小,冲突越小,负载因子越大,冲突越大;
冲突处理
链表法:
引用链表来处理哈希冲突;也就是将冲突元素用链表来链接起来;这也就是常用的处理冲突的方式;但是可能出现一种极端情况,冲突元素比较多,该冲突链表过长,这个时候可以将链表转换为红黑树;由原来链表时间复杂度O(n)转换为红黑树,时间复杂度O(log2n);那么判断该链表过长的依据是什么?可以采用超过256个节点的时候将链表结构转换为红黑树结构;
开放寻址法:
将所有的元素都存放在哈希表的数组中,不使用额外的数据结构;一般使用线性探查的思路解决;
-
当插入新元素时使用哈希函数在哈希表中定位元素位置;
-
检查数组中该槽位索引是否存在元素。如果该槽位为空,则插入,否则3
-
在2检测的槽位索引上加一定步长接着检查2;加一定步长分为以下几种
-
i+1,i+2,i+3,…,i+n
-
i+1,i+4,i+9,…,i+n*n
这两种都会导致同类hash聚集;也就是近似值他的hash值也近似,那么他的数组槽位也靠近,形成hash聚集;第一种同类聚集冲突在前,第二种只是将聚集冲突延后;另外可以使用双重哈希来解决上面出现的聚集现象:
在.net HashTable类的hash函数Hk定义如下: Hk(key) = [GetHash(key) + k * (1 + (((GetHash(key) >> 5) + 1) % (hashsize – 1)))] % hashsize 在此 (1 + (((GetHash(key) >> 5) + 1) % (hashsize – 1))) 与 hashsize 互为素数(两数互为素数表示两者没有共同的质因⼦); 执⾏了 hashsize 次探查后,哈希表中的每⼀个位置都有且只有⼀次被访问到,也就是说,对于给定的 key,对哈希表中的同⼀位置不会同时使⽤ Hi 和 Hj;
-
https://www.cnblogs.com/organic/p/6283476.html
-
布隆过滤器
背景
布隆过滤器是一种概率型数据结构,他的特点是高效的插入和查询,能确定缪个字符串一定不存在或者可能存在;
布隆过滤器不存储具体数据,所以占用空间小,查询结果存在误差,但是误差可控,同时不支持删除操作;
构成
位图(BIT数组)+n个hash函数
m %
2
n
2^n
2n = m & (
2
n
2^n
2n - 1) 位运算的取余运算
原理
当一个元素加入位图时,通过k个hash函数将这个元素映射到位图的k个点,并将它们置为1;当检索时,再通过k个hash函数运算检测位图的k个点是否都为1;如果有不为1的点,那么认为该key不存在;如果全部为1,则可能存在;
为什么不支持删除操作?
在位图中每个槽位只有两种状态(0或者1),一个槽位被设置为1状态,但不确定他被设置了多少次;也就是不知道被多少个key哈希映射而来以及是被具体哪个hash函数映射来;
应用场景
布隆过滤器通常用于判断某个key一定不存在的场景,同时允许判断存在时有误差的情况
常见处理场景:①缓存穿透的解决;②热key限流,判断是否为热key,布隆过滤器是一个黑名单,如果key不在布隆过滤器就可以直接访问
描述缓存场景,为了减轻数据库(mysql)的访问压力,在server端域数据库(mysql之间加入缓存用来存储热点数据);
描述缓存穿透,server端请求数据时,缓存和数据库都不包含该数据,最终请求压力全部涌向数据库;
数据请求步骤,如图中2所示;
发生原因:黑客利用漏洞伪造数据攻击或者内部业务bug造成大量重复请求不存在的数据;
解决方案:如图中3所示。
应用分析
在实际应用中,该选择多少个hash函数?要分配多少空间位图?预期存储多少元素?如何控制误差?
公式如下:
n —— 预期布隆过滤器中元素的个数,如上图 只有str1和str2 两个元素 那么 n=2
p —— 假阳率,在0 -1 之间
m —— 位图所占空间
k —— hash函数的个数
公式如下:
n = ceil(m / ( -k / log( 1 - exp(log(p) / k))))
p = pow(1 - exp(-k / (m / n)), k)
m = ceil((n * log(p)) / log(1 / pow(2 , log(2))))
k = round((m / n) * log(2))
变量关系
假定4个初始值:
n = 4000 预期存储的数量
p = 0.000000001 误判的概率
m = 172532 存储空间
k = 30 哈希函数的数量
hash函数实现过程中为什么会出现 i*31?
- i * 31 = i * (32 -1) = i * (1<<5 - 1) = i << 5 - i; 31可以转换为32-1也就是1<<5-1,转化为位运算
- 31是质数,hash随机分布更好;
确定n和p
在实际使用布隆过滤器时,首先需要确定n和p,通过上面的运算得出m和k;通常可以在下面这个网站上选出合适的值;
https://hur.st/bloomfilter
选择hash函数
选择一个hash函数,通过给hash传递给不同的种子偏移值,采用线性探索的方式构造多个hash函数;
#define MIX_UINT64(v) ((uint32_t)((uint32_t)(v>>32)^(v)))
uint64_t hash1 = Murmurhash2_x64(key,len,Seed);
uint64_t hash2 = Murmurhash2_x64(key,len,MIX_UINT64(hash1));
for(i = 0; i < k ;i++) //k是hash函数的个数
{
pos[i] = (hash1 + i*hash2) % m;//m是位图的大小
}
分布式一致性哈希
背景
分布式一致性哈希算法将哈希空间组织成一个虚拟的圆环,圆环的大小是 2 32 2^{32} 232;
算法为:hash(ip)% 2 32 2^{32} 232,最终都会得到一个[0, 2 32 2^{32} 232-1]之间的一个无符号整型,这个证书代表服务器的编号;多个服务器都通过这种方式在hash换上映射一个点来表示该服务器的位置;当用户操作某个key,通过同样的算法生成一个值,沿环顺时针定位某个服务器,那么该key就在该服务器中;
应用场景
分布式缓存;将数据均衡的分布在不同的服务器中,用来分摊缓存服务器的压力;解决服务器数量变化尽量不太影响缓存失效;
hash偏移
hash算法所得到的结果是随机的,不能保证服务器节点均匀分布在哈希环上;分布不均匀造成请求访问不均匀,服务器承受的压力不均匀;
虚拟节点
为了解决哈希偏移的问题,增加了虚拟节点的概念;理论上,哈希环上节点数越多,数据分布越均衡;
为每个服务节点计算多个哈希节点(虚拟节点);通常的做法是,hash(“IP:PORT:seqno”)%
2
32
2^{32}
232
果是随机的,不能保证服务器节点均匀分布在哈希环上;分布不均匀造成请求访问不均匀,服务器承受的压力不均匀;
虚拟节点
为了解决哈希偏移的问题,增加了虚拟节点的概念;理论上,哈希环上节点数越多,数据分布越均衡;
为每个服务节点计算多个哈希节点(虚拟节点);通常的做法是,hash(“IP:PORT:seqno”)% 2 32 2^{32} 232
https://github.com/metang326/consistent_hashing_cpp
原理
映射空间可抽象为一个环,长度为 2 32 2^{32} 232,范围为[0, 2 32 2^{32} 232-1],每个服务器节点根据自己的哈希值被映射到这个环上;
-
判断一条数据属于哪个服务器节点的方法:根据数据的哈希值,去哈希环找到第一个大于等于数据哈希值的机器(可以理解为离它最近)。如果数据的哈希值大于当前最大的机器哈希值,那么就把这个数据放在位置最靠前(哈希值最小)的机器上,因为是一个环;
-
为了解决实际机器过少导致的数据倾斜问题(例如目前一共3个机器,机器A、B的哈希值分别为1和2,而另一个机器C的哈希值为 2^32-1,那么大部分的数据都会被分给机器C),引入了虚拟节点概念,虚拟节点相当于真实节点的分身,一个真实节点可以有很多个虚拟节点,当数据被分配给这些虚拟节点时,本质上是分给这个真实节点的。由于数量变多了,数据分布的均衡性会有所提高;
-
新增节点时:例如原本的节点哈希值列表为[1,100,500,1000],新增节点800后,在501~799范围内的数据原本是分给哈希值为1000的节点的,现在要把这部分数据迁移到节点800;
-
删除节点:例如原本的节点哈希值列表为[1,100,500,800,1000],删除节点500后,原本范围是101~500的数据要迁移到节点800.
思考点
-
下面直接说数据存放在某个虚拟节点,其实是存在这个虚拟节点对应的实际节点上的,为了描述方便才这样说的。虚拟节点本身其实并不是实际存在的,只是为了让数据分布的更加均匀而设置的。
-
哈希值是怎么获得的?
使用Murmurhash算法,它是一种非加密型哈希函数,使用域一般的哈希检索操作。高运算性能,低碰撞率,由Austin Appleby创建于2008年,现已应用到Hadoop、libstdc++、nginx、libmemcached等开源系统。2011年Appleby被Google雇佣,随后Google推出其变种的CityHash算法。
unsigned int my_getMurMurHash(const void *key, int len)
- 如何找到一个大于等于当前数据哈希值的节点?
在哈希类中存储一个有序的虚拟节点哈希值列表this->sorted_node_hash_list,在这个列表中使用二分查找,返回值为节点在列表中的位置,从而方便在添加、删除节点时,复用这个函数然后找到当前位置后面的节点。
unsigned int consistent_hash::find_nearest_node(unsigned int hash_value)
- 新增一个实际节点
在新增一个实际节点后,会为它生成一些虚拟节点,每个虚拟节点有一个自己的哈希值,会对应到哈希环中的一个位置,插入新虚拟节点后可能会需要从后面位置的虚拟节点上“抢”一些数据。抢夺的数据可能与当前的虚拟节点是属于同一个实际节点的,例如:原本的虚拟节点列表为[1,100,500,1500],在新增实际节点A时,先生成了一个虚拟节点1000,那么它会从1500节点上“抢”走范围在501 ~ 1000的数据;然后节点A又生成了一个哈希值为800的虚拟节点,那么就会从节点1000上“抢”走范围在501 ~ 800的数据。
void consistent_hash::add_real_node(string ip, unsigned int virtual_node_num)
删除一个节点
删除一个节点时,属于它的虚拟节点也要删除。如果在删除过程中,某个虚拟节点有存放数据,那么就要从虚拟节点的位置向后遍历,找到第一个不属于要被删除实际节点的虚拟节点。例如当前的哈希值为1000、属于A实际节点的虚拟节点要被删掉了,1500节点也属于A,不要把数据迁移到这里;2000接地爱你属于B,因此把1000节点上的数据迁移到2000节点。
void consistent_hash::drop_real_node(string ip)
代码
#ifndef LEETCODE_MY_HASH_H
#define LEETCODE_MY_HASH_H
#include <string>
#include <map>
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
//虚拟节点抽象类
class virtual_node {
public:
string ip;
unsigned int hash_value;
map<unsigned int, string> data;
virtual_node();
virtual_node(string ip, unsigned int hash_value);
~virtual_node();
};
//实际节点抽象类
class real_node {
public:
string ip;
unsigned int virtual_node_num;
vector<unsigned int> virtual_node_hash_list;
unsigned int cur_max_port;
real_node();
real_node(string ip);
~real_node();
};
//一致性哈希抽象类
class consistent_hash {
public:
unsigned int real_node_sum; //实际节点和和
unsigned int virtual_node_sum;//虚拟节点的和
map<string, real_node> real_node_map;
map<unsigned int, virtual_node> virtual_node_map;
vector<unsigned int> sorted_node_hash_list;
consistent_hash();
~consistent_hash();
unsigned int find_nearest_node(unsigned int hash_value);
unsigned int put(string data_id);
void add_real_node(string ip, unsigned int virtual_node_num);
void drop_real_node(string ip);
void print_real_node(string ip);
void print();
};
#endif //LEETCODE_MY_HASH_H
#include "my_hash.h"
int HASH_LEN = 32;
unsigned int my_getMurMurHash(const void *key, int len) {
const unsigned int m = 0x5bd1e995;
const int r = 24;
const int seed = 97;
unsigned int h = seed ^len;
// Mix 4 bytes at a time into the hash
const unsigned char *data = (const unsigned char *) key;
while (len >= 4) {
unsigned int k = *(unsigned int *) data;
k *= m;
k ^= k >> r;
k *= m;
h *= m;
h ^= k;
data += 4;
len -= 4;
}
// Handle the last few bytes of the input array
switch (len) {
case 3:
h ^= data[2] << 16;
case 2:
h ^= data[1] << 8;
case 1:
h ^= data[0];
h *= m;
};
// Do a few final mixes of the hash to ensure the last few
// bytes are well-incorporated.
h ^= h >> 13;
h *= m;
h ^= h >> 15;
return h;
}
virtual_node::virtual_node(string ip, unsigned int hash_value) {
this->ip = ip;
this->hash_value = hash_value;
}
virtual_node::~virtual_node() {
data.clear();
}
virtual_node::virtual_node() {
this->ip = "";
this->hash_value = 0;
}
real_node::real_node(string ip) {
this->ip = ip;
this->cur_max_port = 0;
this->virtual_node_num = 0;
}
real_node::~real_node() {
this->virtual_node_hash_list.clear();
}
real_node::real_node() {
this->ip = "";
this->cur_max_port = 0;
this->virtual_node_num = 0;
}
consistent_hash::consistent_hash() {
this->real_node_sum = 0;
this->virtual_node_sum = 0;
}
consistent_hash::~consistent_hash() {
this->virtual_node_map.clear();
this->real_node_map.clear();
this->sorted_node_hash_list.clear();
}
unsigned int consistent_hash::find_nearest_node(unsigned int hash_value) {
int low = 0;
int high = this->sorted_node_hash_list.size() - 1;
int mid;
if (hash_value > this->sorted_node_hash_list[high]) {
return 0;
}
while (low < high) {
mid = (low + high) / 2;
if (this->sorted_node_hash_list[mid] == hash_value) {
return mid;
} else if (this->sorted_node_hash_list[mid] > hash_value) {
high = mid;
} else {//this->sorted_node_hash_list[mid]<data_hash
low = mid + 1;
}
}
return low;
}
void consistent_hash::add_real_node(string ip, unsigned int virtual_node_num) {
cout << "[add_real_node]\t" << ip << endl;
real_node *node;
if (this->real_node_map.find(ip) != this->real_node_map.end()) {
node = &real_node_map[ip];
} else {
real_node new_node = real_node(ip);
node = &new_node;
this->real_node_sum++;
}
int cur_port = node->cur_max_port;
int vir_node_num = 0;
string tmp_ip;
unsigned int tmp_hash;
while (vir_node_num < virtual_node_num) {
do {
cur_port++;
tmp_ip = ip + ":" + to_string(cur_port);
tmp_hash = my_getMurMurHash(tmp_ip.c_str(), HASH_LEN);
} while (this->virtual_node_map.find(tmp_hash) != this->virtual_node_map.end());
vir_node_num++;
this->virtual_node_map[tmp_hash] = virtual_node(tmp_ip, tmp_hash);
this->sorted_node_hash_list.push_back(tmp_hash);
sort(this->sorted_node_hash_list.begin(), this->sorted_node_hash_list.end());
unsigned int id = this->find_nearest_node(tmp_hash);
unsigned int next_id = id + 1;
if (next_id >= this->sorted_node_hash_list.size()) {
next_id = 0;
}
unsigned int next_hash = this->sorted_node_hash_list[next_id];
vector<unsigned int> tobe_deleted;
map<unsigned int, string> *tobe_robbed = &(this->virtual_node_map[next_hash].data);
for (auto data = tobe_robbed->begin(); data != tobe_robbed->end(); data++) {
if (data->first < tmp_hash) {
this->virtual_node_map[tmp_hash].data[data->first] = data->second;
tobe_deleted.push_back(data->first);
}
}
for (auto deleted:tobe_deleted) {
tobe_robbed->erase(deleted);
cout << "[move data]\t" << deleted << "\tfrom node:\t" << this->virtual_node_map[next_hash].ip << "("
<< next_hash << ")" << "\tto\t"
<< this->virtual_node_map[tmp_hash].ip << "(" << tmp_hash << ")" << endl;
}
node->virtual_node_hash_list.push_back(tmp_hash);
}
node->cur_max_port = cur_port;
node->virtual_node_num += virtual_node_num;
this->real_node_map[ip] = *node;
this->virtual_node_sum += virtual_node_num;
cout << "[add_real_node finished]\t" << ip << endl << endl;
}
void consistent_hash::print_real_node(string ip) {
cout << "------------consistent_hash.print_real_node------------" << endl;
cout << "real_node ip:" << ip << "\tvirtual_node_num=" << this->real_node_map[ip].virtual_node_num << endl;
for (auto tmp:this->real_node_map[ip].virtual_node_hash_list) {
if (this->virtual_node_map[tmp].data.size() > 0) {
cout << "virtual node:\t" << this->virtual_node_map[tmp].ip << "(" << tmp << ")" << "\thas data:";
for (auto data:this->virtual_node_map[tmp].data) {
cout << "(" << data.second << "," << data.first << ")\t";
}
cout << endl;
}
}
cout << endl;
}
void consistent_hash::print() {
cout << endl << "------------consistent_hash.print()------------" << endl;
cout << "real_node_sum:\t" << this->real_node_sum << "\tvirtual_node_sum:\t" << this->virtual_node_sum << endl;
cout << endl;
for (auto tmp = this->real_node_map.begin(); tmp != this->real_node_map.end(); tmp++) {
this->print_real_node(tmp->first);
}
}
unsigned int consistent_hash::put(string data_id) {
unsigned int data_hash = my_getMurMurHash(data_id.c_str(), HASH_LEN);
unsigned int id = this->find_nearest_node(data_hash);
unsigned int put_on_virnode_hash = this->sorted_node_hash_list[id];
this->virtual_node_map[put_on_virnode_hash].data.insert(make_pair(data_hash, data_id));
cout << "data:\t" << data_id << "(" << data_hash << ")\twas put on virtual node:"
<< this->virtual_node_map[put_on_virnode_hash].ip << "(" << put_on_virnode_hash << ")"
<< endl;
return 0;
}
void consistent_hash::drop_real_node(string ip) {
cout << "[drop_real_node]\t" << ip << endl;
vector<unsigned int> *virtual_hash_list_p = &this->real_node_map[ip].virtual_node_hash_list;
sort(virtual_hash_list_p->begin(), virtual_hash_list_p->end());
unsigned int next_id;
unsigned int next_hash;
unsigned int cur_id;
unsigned int cur_hash;
vector<unsigned int> tobe_delete;
for (int i = virtual_hash_list_p->size() - 1; i >= 0; i--) {
cur_hash = (*virtual_hash_list_p)[i];
tobe_delete.push_back(cur_hash);
if (this->virtual_node_map[cur_hash].data.size() > 0) {
cur_id = this->find_nearest_node(cur_hash);
next_id = cur_id;
string next_realnode_ip;
do {
next_id++;
if (next_id >= this->sorted_node_hash_list.size()) {
next_id = 0;
}
next_hash = this->sorted_node_hash_list[next_id];
next_realnode_ip = this->virtual_node_map[next_hash].ip;
} while (next_realnode_ip.find(ip) != -1);
map<unsigned int, string> *moveto = &(this->virtual_node_map[next_hash].data);
for (auto &data : this->virtual_node_map[cur_hash].data) {
(*moveto)[data.first] = data.second;
cout << "[move data]\t" << data.second << "\tfrom node:\t" << this->virtual_node_map[cur_hash].ip << "("
<< cur_hash << ")" << "\tto\t"
<< this->virtual_node_map[next_hash].ip << "(" << next_hash << ")" << endl;
}
}
}
for (auto hash:tobe_delete) {
this->virtual_node_map.erase(cur_hash);
this->virtual_node_sum--;
auto iter = find(this->sorted_node_hash_list.begin(),
this->sorted_node_hash_list.end(), hash);
if (iter != this->sorted_node_hash_list.end()) {
this->sorted_node_hash_list.erase(iter);
}
}
sort(this->sorted_node_hash_list.begin(), this->sorted_node_hash_list.end());
this->real_node_map[ip].virtual_node_hash_list.clear();
this->real_node_map.erase(ip);
this->real_node_sum--;
cout << "[drop_real_node finished]\t" << ip << endl << endl;
}