1. 一致性 hash 算法应用领域
单节点服务器,以缓存为例
- 使用缓存的目的:提升数据访问性能,缓解数据库压力!

单机缓存能扛起高并发吗?
海量数据对缓存有什么影响?(分片集群)
- 缓存的数据量很大,会超出单机内存容量。

方式一:hash(key) % 集群节点数

- 示例:3 个节点的集群,数据 zp:888
- 假设:
hash(zp) = 200 - 则数据放到服务器 2 上
200 % 3 = 2
- 假设:
- 搞促销活动,临时增加一台服务器来提高集群性能,方便扩展集群吗?
hash(zp) = 200;200 % 4 = 0
- 增加一个节点后,有多大比例的数据缓存命不中?
3 -> 4,3/499 -> 100,99%
- 对系统会有什么影响?(缓存雪崩)
- 大量缓存命不中,就会访问数据库。
- 瞬间失去了缓存的分压,数据库不堪重负,崩溃。
对程序员的影响
hash 值一个非负整数,把非负整数的值范围做成一个圆环。(0 - int.max)
- 对集群的节点的某个属性求 hash 值(如节点名称),根据 hash 值把节点放到环上。
- 对数据的 key 求 hash,一样的把数据也放到环上,按顺时针方向,找离它最近的节点,就存储到这个节点上。
- 新增节点能均衡缓解原有节点的压力吗?
- 不能。
- 假如节点 3 靠近节点 1,影响数据少,但是会导致节点 3 上数据少。
- 假如节点 3 靠近节点 2,影响数据多,并且导致节点 2 上数据很少。
- 集群的节点一定会均衡分布在环上吗?

- 一致性 Hash 算法,只是解决缩容、扩容问题(程序员加班问题)。
- 引入虚拟节点解决数据分布不均衡问题。
4. 一致性 Hash 算法需要考虑的问题
Hash 算法选择
- hashCode(),不够散列,会有负值(取绝对值即可)
其他 hash 算法:比如 CRC32_HASH、FNV1_32_HASH、KETAMA_HASH 等,其中 KETAMA_HASH 是默认的 memcached 推荐的一致性 Hash 算法。
虚拟节点放到环上,具体是要做什么?
根据 Hash 值排序存储。
- 排序存储要被快速查找。
- 这个排序存储还要能方便变更。
- 考虑:
FNV1_32_HASH
public class FNV1_32_HASH {/** 尽量让 hash 值散列开 */public static int getHash(String str) {final int p = 16777619;int hash = (int) 2166136261L;for (int i = 0; i < str.length(); i ++) {hash = (hash ^ str.charAt(i)) * p;}hash += hash << 13;hash ^= hash >> 7;hash += hash << 3;hash ^= hash >> 17;hash += hash << 5;// 如果算出来的值为负数则取其绝对值if (hash < 0) {hash = Math.abs(hash);}return hash;}}
测试 String.hashCode() 和 以上算法的均衡度
public class HashTest {public static void main(String[] args) {System.out.println("hashCode(): ");System.out.println("192.168.0.0:1111 的哈希值:" + "192.168.0.0:1111".hashCode());System.out.println("192.168.0.1:1111 的哈希值:" + "192.168.0.1:1111".hashCode());System.out.println("192.168.0.2:1111 的哈希值:" + "192.168.0.2:1111".hashCode());System.out.println("192.168.0.3:1111 的哈希值:" + "192.168.0.3:1111".hashCode());System.out.println("192.168.0.4:1111 的哈希值:" + "192.168.0.4:1111".hashCode());System.out.println("192.168.1.0:1111 的哈希值:" + "192.168.1.0:1111".hashCode());System.out.println();System.out.println("FNV1_32_HASH: ");System.out.println("192.168.0.0:1111 的哈希值:" + FNV1_32_HASH.getHash("192.168.0.0:1111"));System.out.println("192.168.0.1:1111 的哈希值:" + FNV1_32_HASH.getHash("192.168.0.1:1111"));System.out.println("192.168.0.2:1111 的哈希值:" + FNV1_32_HASH.getHash("192.168.0.2:1111"));System.out.println("192.168.0.3:1111 的哈希值:" + FNV1_32_HASH.getHash("192.168.0.3:1111"));System.out.println("192.168.0.4:1111 的哈希值:" + FNV1_32_HASH.getHash("192.168.0.4:1111"));System.out.println("192.168.1.0:1111 的哈希值:" + FNV1_32_HASH.getHash("192.168.1.0:1111"));}}
简单实现一个一致性算法
public class ConsistenceHash {/** 物理节点集合 */private List<String> realNodes = new ArrayList<>();/** 虚拟节点数,用户进行指定 */private int virtualNums = 100;/*** 物理节点和虚拟节点的对应关系* key:物理节点* value:虚拟 hash 值*/private Map<String, List<Integer>> real2VirtualMap = new HashMap<>();/*** 排序存储结构,红黑树* key:虚拟节点 hash 值* value:物理节点*/private SortedMap<Integer, String> sortedMap = new TreeMap<>();public ConsistenceHash() { }public ConsistenceHash(int virtualNums) {this.virtualNums = virtualNums;}/*** 添加服务节点*/public void addServer(String node) {realNodes.add(node);String vnode = null;// i:v-001,count:产生了虚拟节点的次数int i = 0, count = 0;List<Integer> virtualNodes = new ArrayList<>();real2VirtualMap.put(node, virtualNodes);// 创建虚拟节点,并且放到圆环上去(排序存储)while (count < this.virtualNums) {i++;vnode = node + "V-" + i;int hashValue = FNV1_32_HASH.getHash(vnode);// 解决 hash 碰撞if (! sortedMap.containsKey(hashValue)) {virtualNodes.add(hashValue);sortedMap.put(hashValue, node);count ++;}}}/*** 移除服务器*/public void removeServer(String node) {List<Integer> virtualNodes = real2VirtualMap.get(node);if (virtualNodes != null && virtualNodes.size() > 0) {for (Integer hashVal : virtualNodes) {sortedMap.remove(hashVal);}}realNodes.remove(node);real2VirtualMap.remove(node);}/*** 根据 key 查找物理节点*/public String getServer(String key) {// 根据 key 产生 hash 值int hashValue = FNV1_32_HASH.getHash(key);SortedMap<Integer, String> subMap = sortedMap.tailMap(hashValue);if (subMap == null || subMap.isEmpty()) {return sortedMap.get(sortedMap.firstKey());}return sortedMap.get(subMap.firstKey());}public static void main(String[] args) {ConsistenceHash ch = new ConsistenceHash();ch.addServer("192.168.1.10");ch.addServer("192.168.1.11");ch.addServer("192.168.1.12");for (int i = 0; i < 10; i ++) {System.out.println("a" + i + " 对应的服务器是:" + ch.getServer("a" + i));}}}

