什么是每日分享?
饥人谷每天为大家带来一篇程序员分享!内容都来自于热爱编程、热爱生活的小伙伴们!分享的话题与编程、生活、兴趣、爱好、运动等相关!
想要每天都进步一点点的小伙伴们快点下关注吧!
今天的分享来自于饥人谷程序员交流群 冷夜同学 的分享,目前正在从事Java工作,分享哈希相关的知识!
本期分享内容
大家晚上好!很高兴加入 饥人谷大家庭,我现在从事Java后台开发工作,目前在学习和研究算法相关的内容。
今天的分享是哈希相关的知识,和大家一起回顾下相关的知识,以及在分布式场景下的应用。
HashMap 想必大家都非常熟悉了
HashMap 底层的数据结构主要是:数组 + 链表 + 红黑树。
数组的主要作用是根据下标随机访问,时间复杂度是 O(1),默认大小是 16,数
组的下标索引是通过 key 的 hashcode 计算出来的,数组元素叫做 Node,当多个 key 的 hashcode 一致,但 key 值不同时,发生哈希冲突通过链表法解决,单个 Node 就会转化成链表,链表的查询复杂度是 O(n),当链表的长度大于等于 8 并且数组的大小超过 64 时,链表就会转化成红黑树,红黑树的查询复杂度是 O(log(n)),简单来说,最坏的查询次数相当于红黑树的最大深度。
哈希算法的定义
将任意长度的二进制值串映射为固定长度的二进制值串,这个映射的规则就是哈希算法。
关键词
映射规则--->算法
固定长度的二进制值串--->哈希码
列举哈希在哪里被使用?
1.加密:散列算法 MD5计算一个唯一的id,具有不可逆性。
https://github.com/pod32g/MD5/blob/master/md5.c
2.网络传输中服务端使用的是散列表存储session
3.长链通过哈希算法将长链接映射为短链
4.布隆过滤器
5.LRU缓存
6.redis 中散列表存储结构
7.java虚拟机中java对象头的组成字段
8.java序列化使用的serialVersionUID
9.java hashmap/treemap/linkedhashmap/concurrentHashMap
应用场景总结
哈希算法的应用
1.加密(常用:MD5 消息摘要算法 ,SHA安全散列算法,区块链使用SHA256哈希算法)
2.唯一标识(由于MD5 值的唯一性,常用于判断数据是否发生过更新)
3.数据校验(BT协议,文件下载校验数据的完整性和正确性)
4.散列函数(hash表索引值计算)
5.负载均衡(如会话粘滞,同一个客户端在一次会话中的所有请求都路由到同一个服务器上)
6.数据分片(海量数据存储、查询)
以下是 常用的哈希算法
1.余数哈希算法
1.1 HashTable 取余数法:int index = (hash & 0x7FFFFFFF) % tab.length;
1.2 HashMap 取余数法
HashMap 本质还是取余数,但是使用位运算进行优化,获得分布更加均匀的hashCode
1)初始化时,hashmap对数组容量进行调整,一定是2的幂次。
2)index = key.hasCode() % n 当n等于2的幂次方时,可优化为index = key.hasCode() & (n-1)
取余操作的弊端:取余的计算结果对高位是无效的,只是对低位有效,当计算出来的hasCode()只有高位有变化时,取余的结果还是一样的。这样就会导致hash冲突。
举例:
int hashCode1 = 88
int hashCode2 = 72
int index1 = 88 % 16 -> 8
int index2 = 72 % 16 -> 8
尽可能利用32位(java中的hashcode是32位),将hashCode高16位与低16位进行异或运算,避免忽略容量以上的高位,有效避免hash碰撞
2.redis取余数法
redis使用性能更好的MurmurHash2计算哈希值,利用余数法计算数组索引值
https://github.com/aappleby/smhasher
使用链表法解决哈希冲突。redis支持散列表的动态扩容、缩容。
解决方式1:重新哈希(rehash)
jdk中hashmap/hashtable使用 rehash,redis使用渐进式rehash。
redis使用渐进式扩容、缩容策略,为了解决扩容缩容要做大量的数据搬移和哈希值的重新计算,非常耗时,采用将数据的搬移分批进行,避免大量数据一次性搬移导致的服务停顿。
解决方式2: 一致性哈希(ConsistentHash)
dubbo、nginx中发生扩容/缩容时通常使用一致性哈希算法来保证负载均衡。
一致性哈希算法
一致性哈希是指将存储节点和数据都映射到一个首尾相连的哈希环上,存储节点可以根据 IP 地址进行哈希,数据通常通过顺时针方向寻找的方式,来确定自己所属的存储节点,即从数据映射在环上的位置开始,顺时针方向找到的第一个存储节点
缺陷一:
hash偏移造成的节点分布不均
解决方式:
带虚拟节点的一致性哈希
构建过程:
一致性hash的虚拟节点采用了TreeMap对象,针对每个provider根据url的address字段加虚拟节点个数/4的偏移量生成128的digest字段,针对128位的digest字段(16个字节)按照每4个字节进行进行计算生成4个虚拟节点放到TreeMap当中。假设有160个虚拟节点,160/4=40个待处理虚拟节点,针对40个待处理虚拟节点中的每个节点生成16个字节的digest,16个字节中digest分为4份进行保存,最终依然是160个虚拟节点。
查询过程:
查询过程根据指定参数进行md5+hash()计算hash值,找到比hash值小的treeMap然后选择treeMap的根节点选择provider,如果找不到比hash小的treeMap就选择treeMap的最小根节点。
存储结构的选择
TreeMap 底层是红黑树,查询的时间复杂度是O(logN)
1.使用TreeMap实现hash环,将Invoker分布在环上,
2.java.util.NavigableMap#ceilingEntry 获取环上大于等于指定key的节点
代码见:
https://github.com/apache/dubbo/blob/master/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/loadbalance/ConsistentHashLoadBalance.java
缺陷二:
节点新增/删除时,造成的数据分布不均
解决方式:
带有限负载的一致性哈希
核心原理:
给每个存储节点设置了一个存储上限值来控制存储节点添加或移除造成的数据不均匀。当数据按照一致性哈希算法找到相应的存储节点时,要先判断该存储节点是否达到了存储上限;如果已经达到了上限,则需要继续寻找该存储节点顺时针方向之后的节点进行存储。
解决方式:
根据节点性能差异设置对应数量的虚拟节点
以上为今日分享 谢谢!
附上同学当时分享的截图:
有什么想要问冷夜同学的可以在评论区评论哦!觉得今天分享有用的请关注一下呀!
想要进程序员社群的小伙伴可以加小助手微信:xiedaimala04 (备注:社群)
扫码添加小助手微信