hyperloglog在redis中介绍
从海量数据中查询某个字符串是否存在
增删改查时间复杂度为 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;
};
映射函数Hash(key) = addr
;hash函数可能会把两个或两个以上的不同key映射到同一地址,这种情况称之为冲突(或哈希碰撞)。
redis
和 memcached
中 链表法采用头插法,因为局部性原理。redis
和map
都采用链表法将所有的元素都放在哈希表的数组中,不使用额外的数据结构;一般使用线性探查的思路解决:
当插入新元素时,使用哈希函数再哈希表中定位元素位置;
检查数组中该槽位索引是否存在元素。如果槽位为空则插入,否则转3
在2检测的槽位索引上加上一定的步长后 转2. 其中步长有如下选择:
i+1, i+2, i+3, i+4…,i+n
i − 1 2 i-1^2 i−12, i + 2 2 i+2^2 i+22, i − 3 2 i-3^2 i−32, 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
- 这里有点困难,实际只要知道怎么用即可。
布隆过滤器是一种概率型数据结构,它的特点是高效的插入和查询,能确定某个字符串一定不存在或者可能存在;
布隆过滤器不存储具体元素,故占用空间小,查询结果存在误差,但误差可控;
不支持删除操作。
当一个元素加入位图时, 通过k个hash函数将这个元素映射到位图的k个点, 并把它们置为1;
当检索时,再通过k个hash函数运算检测位图的k个点是否都为1:
如果有不为1的点,那么该key一定不存在。
如果全为1,则可能存在。
答:位图中每个位只有两种状态(0或1) ,一个槽位被置为1,但无法确定被设置几次;即无法知道被多少个key哈希映射而来,如果置为0,可能导致判断别的key是存在时出现误差。
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,通过上面公式算出m和k,通常可以在下面网站选出合适值:
https://hur.st/bloomfilter
n = 4000
p = 0.000000001(1.0E-9)
m = 172532
k = 30
选一个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是位图的大小
}
问:hash函数实现过程中为什么出现 i * 31
答: i * 31 = i * (32 - 1) = i * (1<<5 - 1) = i<<5 - i; 且31是质数,hash随机分布性比较好。
分布式一致性hash算法将hash空间组织成一个虚拟圆环,大小为 2 32 2^{32} 232;
算法为:hash(ip) % 2 32 2^{32} 232 ,计算后得到一个[0, 2 32 − 1 2^{32}-1 232−1]之间的一个无符号整型,这个数代表服务器编号;
多个服务器通过这种方式在hash环上映射一个点来标识该服务器的位置;当用户操作某个key,通过同样的算法生成一个值,沿环顺时针定位某个服务器,那么该key就在该服务器中。
分布式缓存;将数据均衡地分散在不同的服务器中,用来分摊缓存服务器压力
解决缓存服务器数量变化尽量不影响缓存失效。
hash算法得到的结果是随机的,不能保证服务器解决均匀分布在哈希环上;分布不均匀导致请求访问不均匀,使得服务器承受压力不均匀。
造成的主要原因还是因为节点数量不够
采用增加虚拟节点的方法,理论上,哈希环上的节点数据越多,数据分布越均衡;
为每个服务节点计算多个哈希节点(虚拟节点),通常做法是:hash(“IP:PORT:seqno”) % 2 32 2^{32} 232
分布式一致性hash增加或删除节点怎么进行数据迁移
参考https://github.com/metang326/consistent_hashing_cpp