本文为实现分布式任务调度系统中用到的一些关键技术点分享——Consistent Hashing算法原理和Java实现,以及效果测试。
背景介绍
一致性Hashing在分布式系统中经常会被用到, 用于尽可能地降低节点变动带来的数据迁移开销。Consistent Hashing算法在1997年就在论文
先来简单理解下Hash是解决什么问题。假设一个分布式任务调度系统,执行任务的节点有n台机器,现有m个job在这n台机器上运行,这m个Job需要逐一映射到n个节点中一个,这时候可以选择一种简单的Hash算法来让m个Job可以均匀分布到n个节点中,比如 hash(Job)%n ,看上去很完美,但考虑如下两种情形:
n个节点中有一个宕掉了,这时候节点数量变更为n-1,此时的映射公式变成 hash(Job)%(n-1)
由于Job数量增加,需要新增机器,此时的映射公式变成 hash(Job)%(n+1)
1、2两种情形可以看到,基本上所有的Job会被重新分配到跟节点变动前不同的节点上,意味着需要迁移几乎所有正在运行的Job,想想这样会给系统带来多大的复杂性和性能损耗。
另外还有一种情况,假设节点的硬件处理性能不完全一致,想让性能高的节点多被分配一些Job,这时候上述简单的Hash映射算法更是很难做到。
如何解决这种节点变动带来的大量数据迁移和数据不均匀分配问题呢?一致性哈希算法就很巧妙的解决了这些问题。
Consistent Hashing是一种Hashing算法,典型的特征是:在减少或者添加节点时,可以尽可能地保证已经存在Key映射关系不变,尽可能地减少Key的迁移。
Consistent Hashing算法原理,如何处理Job->Node映射过程
确定hashing值空间
给定值空间2^32,[0,2^32]是所有hash值的取值空间,形象地描述为如下一个环(ring):
2. 节点向值空间映射
将节点Node向这个值空间映射,取Node的Hash值,选取一个可以固定标识一个Node的属性值进行Hashing,假设以字符串形式输入,算法如下:
可以取Node标识的md5值,然后截取其中32位作为映射值。md5取值如下:
private byte[] md5(String value) {
MessageDigest md5;try{
md5= MessageDigest.getInstance("MD5");
}catch(NoSuchAlgorithmException e) {throw newIllegalStateException(e.getMessage(), e);
}
md5.reset();byte[] bytes;try{
bytes= value.getBytes("UTF-8");
}catch(UnsupportedEncodingException e) {throw newIllegalStateException(e.getMessage(), e);
}
md5.update(bytes);returnmd5.digest();
}
因为映射值只需要32位即可,所以可以利用以下方式计算最终值(number取0即可):
private long hash(byte[] digest, intnumber) {return (((long) (digest[3 + number * 4] & 0xFF) << 24)| ((long) (digest[2 + number * 4] & 0xFF) << 16)|