hash函数

文章介绍了哈希在不同场景下的应用,包括Word的拼写检查、网络爬虫避免重复URL、垃圾邮件过滤算法的设计以及公安办案中嫌疑人的筛查。接着详细讲解了布隆过滤器的工作原理、特点和应用场景,用于缓存穿透问题的解决方案。此外,还阐述了一致性哈希在分布式系统中的作用,如何解决数据分布不均的问题,并提到了虚拟节点的概念。
摘要由CSDN通过智能技术生成

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个节点的时候将链表结构转换为红黑树结构;

开放寻址法:

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

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

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

  3. 在2检测的槽位索引上加一定步长接着检查2;加一定步长分为以下几种

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

    2. 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;
      
    3. 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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值