以前看过了解过一致性哈希算法的实现原理,现在真正要用代码实现时,还是有好几个细节需要确定的。
假设我们的目标是:有N个节点,新建的资源要均匀分配到这些节点上。另外为了在节点少的情况下让分配更均衡,需要加上虚拟节点的概念。
那大概的实现思路是:定义一个哈希算法,定义虚拟节点的副本数为K。首先计算好这N个节点的K个虚拟节点的哈希值,这N*K个哈希值从小到大排序,当新建一个资源,就需要使用相同的哈希算法计算出这个资源的哈希值H。然后根据哈希值H找到是在N*K个哈希值中排在哪个虚拟节点所在位置(即从小到大排序后,大于且最靠近哈希值H的那个哈希值),然后确定这个虚拟节点是属于哪个节点,则资源就分配给这个节点。
思路明确后,下面有几个待确定点:
1. 哈希算法用啥实现?
2. 虚拟节点的副本数K取多少?
3. 拿节点的什么信息来算哈希值?
这里应该是拿虚拟节点的信息来计算
4. 拿新资源的什么信息来算哈希值?
5. 怎么确定新资源是应该分配给哪个节点?
6. 虚拟节点计算好的哈希值,是在哪里存储?数据库表?redis?内存?
之前也没有实现过一致性哈希算法,当然。。也是先从网上搜一堆然后各种比较一番,结合自己的需要,实现如下:
代码是python实现的
1. 哈希算法的实现
有的说用md5、FNV1_32_HASH、KETAMA_HASH
这里用的是CRC32校验,返回值范围是 [-2^31, 2^31-1],再取abs绝对值,则范围为 [0, 2^31-1],跟理论上说的长度为2^32的整数环相匹配
from zlib import crc32
def hash(hash_name):
hash_bytes = bytes(hash_name)
return abs(crc32(hash_bytes))
这里有个疑惑,abs()会返回绝对值,那对于计算出负数的hash值会不会影响了哈希的均衡性?
2. 虚拟节点副本数的设置
理论上,虚拟节点副本数设置地越多,则对应节点分配地越均衡。但虚拟节点数过多,会导致新资源在查找需要分配到哪个虚拟节点的这个过程会很久。所以这里存在着均衡性和查找效率上的一个权衡。
这里我们简单的设置副本数为3,即一个节点对应3个虚拟节点。后面也通过一个简单测试来验证这个设置。
3和4 节点和新资源的哈希值的计算
(1)这里其实是确定1中hash函数的哈希名hash_name该怎么设置,设置的原则是能够唯一标识这个节点或新资源,使得不同的节点或新资源计算出来的哈希值肯定是不一致的。如果节点或新资源有唯一的IP,取hash_name = ip是可以的,如果没有,则利用其它的信息的组合,来进行唯一标识,比如节点名+节点uuid的组合。
(2)假设节点有个唯一的IP,那它K个虚拟节点的哈希名该怎么设置?
是取IP+递增数字,比如IP_0、IP_1、IP_2...,或者取IP_随机数
这里是采用IP+递增数字,也是通过后面一个简单测试的结果来决定的。
5. 查找新资源所分配的节点
在原理上说,是从一个整数环中顺时针找到离新资源最近(第一个大于或等于新资源的哈希值H)的一个虚拟节点,从而决定是分配到哪一个节点。
转化成代码实现,就是在一个有序集合中进行查找某个值,实现思路有很多,这里简单的实现:二分查找法
先对所有的虚拟节点的哈希值从小到大排序,形成一个数组hash_arr,然后再二分查找到所分配到的虚拟节点
6. 虚拟节点的哈希值的存储
采用数据库表来预先存储好虚拟节点的哈希值
以上各个细节设置完,还是有点担心这个算法实际的均衡性,只能做个初步的测试进行验证:
可用来调整的变化参数有:哈希算法、虚拟节点的副本数、哈希名的设置规则,这几个参数采用不同的方法或值进行组合,都会对最终的均衡性产生影响。
由于对哈希算法没啥研究,就还是采用CRC32校验。
测试样例:调整虚拟节点的副本数和哈希名的设置规则
测试数据:新资源6000个,节点6个
理想结果:每个节点分配到1000个新资源
测试结果:
(1)哈希名的设置规则为:IP+递增数字
调整虚拟节点的副本数为3、5、10,从测试结果来看,没有绝对的均衡,分配最少的节点有近600个新资源,分配最多的有1500多个。而副本数增大,均衡性并没有明显变好,有可能是副本数还不够大,比如设置为1000、10000。
(2)哈希名的设置规则为:IP+10位随机字符串
调整虚拟节点的副本数为3、5、10,从测试结果来看,均衡性有波动,可能是跟这个随机的字符串有关。最差的情况下,分配最少的节点有400多个新资源,分配最多的有1700多个。副本数增大,没有显示出线性增长的关系。
测试结论:
分配的均衡性,与哈希算法+虚拟节点的副本数+哈希名的设置规则这几个组合在一起有关,有兴趣的可以研究研究。
最终采取的组合还是:哈希算法为CRC32校验,虚拟节点的副本数为3,哈希名的设置规则为唯一标识+递增数字