架构师训练营 week05 作业(hash 算法)

用户头像
FG佳
关注
发布于: 2020 年 10 月 25 日

这个算法,hash算法和测试用例,一下子写出来,对我来说比较难,copy一份吧,增强理解



2.4 实现

定义一致性哈希实体。

// 哈希函数签名
type Hash func(data []byte) uint32
// 一致性哈希实体
type Map struct {
hash Hash // 哈希函数
replicas int // 每个真实节点对应的虚拟节点个数
circle []int // 哈希环
hashMap map[int]string // 存储虚拟节点与真实节点的映射
}
// 创建实例
func New(replicas int, hashFunc Hash) *Map {
m := &Map{
hash: hashFunc,
replicas: replicas,
hashMap: make(map[int]string),
}
if m.hash == nil {
m.hash = crc32.ChecksumIEEE
}
return m
}


说明:

  • 定义了函数类型Hash,采取依赖注入的方式,允许用户自定义哈希函数,默认为crc32.ChecksumIEEE算法。

  • Map 是一致性哈希算法的主数据结构,包含 4 个成员变量:Hash 函数 hash;虚拟节点倍数 replicas,即每个真实节点对应replicas个虚拟节点;哈希环 circle;虚拟节点与真实节点的映射表 hashMap,键是虚拟节点的哈希值,值是真实节点的名称。

  • 构造函数 New() 允许自定义虚拟节点倍数和 Hash 函数。

接下来,实现添加真实节点的Add()方法。

// 添加真实节点
func (m *Map) Add(peers ...string) {
for _, peer := range peers {
for i := 0; i < m.replicas; i++ {
// 根据节点的名称+编号计算hash值
hashVal := int(m.hash([]byte(strconv.Itoa(i) + peer)))
//log.Printf("peer:[%s-%d], hash:[%d]\n", peer, i, hashVal)
m.circle = append(m.circle, hashVal)
m.hashMap[hashVal] = peer
}
}
// 排序是为了接下来方便查询
sort.Ints(m.circle)
//log.Printf("circle:\n %v\n",m.circle)
}


说明:

  • Add()方法允许传入0个或多个真实节点的名称。

  • 对每个真实节点peer,对应创建replicas个虚拟节点,我们令虚拟节点的名称为strconv.Itoa(i) + peer,即通过添加编号的方式区分不同虚拟节点。

  • 使用 m.hash() 计算虚拟节点的哈希值,使用 append(m.circle, hashVal) 把哈希值添加到环上。

  • 在 hashMap 中增加虚拟节点和真实节点的映射关系。

  • 最后,需要对换上的哈希值进行排序。排序是为了方便后续的查找。

最后,实现选择节点的Get()方法。

// 根据请求的key选择对应的真实节点
// 返回真实节点的名称
func (m *Map) Get(key string) string {
if len(m.circle) == 0 {
return ""
}
// 首先计算key对应的hash值
hashVal := int(m.hash([]byte(key)))
// 二分查找
index := sort.Search(len(m.circle), func(i int) bool {
return m.circle[i] >= hashVal
})
return m.hashMap[m.circle[index%len(m.circle)]]
}


说明:

  • Get()方法根据请求的key选择对应的真实节点,返回真实节点的名称。

  • 首先,对key计算对应的哈希值。

  • 然后,顺时针找到第一个匹配的虚拟节点的下标 index。所谓匹配,就是在哈希环m.circle中,找到第一个哈希值大于等于key的哈希值的虚拟节点。

  • 最后,通过hashMap映射得到真实的节点。

至此,一致性哈希算法就全部完成了。

2.5 测试

我们测试一致性哈希是否能正常的增加节点和选择节点,另外,我们也测试它在增加节点时数据的迁移率如何,是否针对比传统哈希算法要好。

func TestConsistentHash(t *testing.T) {
// 创建一个一致性哈希实例,并自定义hash函数
chash := New(3, func(data []byte) uint32 {
i, _ := strconv.Atoi(string(data))
return uint32(i)
})
// 添加真实节点,为了方便说明,这里的节点名称只用数字进行表示
chash.Add("4", "6", "2")

testCases := map[string]string{
"15": "6",
"11": "2",
"23": "4",
"27": "2",
}
for k, v := range testCases {
if chash.Get(k) != v {
t.Errorf("Asking for %s, should have yielded %s", k, v)
}
}

// 新增一个节点"8",对应增加3个虚拟节点,分别为8,18,28
chash.Add("8")

// 此时如果查询的key为27,将会对应到虚拟节点28,也就是映射到真实节点8
testCases["27"] = "8"

for k, v := range testCases {
if chash.Get(k) != v {
t.Errorf("Asking for %s, should have yielded %s", k, v)
}
}
}


为了方便说明,这里简化了节点的名称(仅用数字来表示),并且自定义哈希函数。

  • 一开始,有 2/4/6 三个真实节点,对应的虚拟节点的哈希值是 02/12/22、04/14/24、06/16/26。

  • 那么用例 15/11/23/27 选择的虚拟节点分别是 16/12/24/02,也就是真实节点6/2/4/2。

  • 新增一个节点"8",对应增加3个虚拟节点,分别为8,18,28,此时,用例 27 对应的虚拟节点从2变更为 28,即真实节点 8。

测试迁移率

var keysPtr = flag.Int("keys", 10000, "key number")
var nodesPtr = flag.Int("nodes", 3, "node number of old cluster")
var newNodesPtr = flag.Int("new-nodes", 4, "node number of new cluster")

// 测试一致性哈希的数据迁移率
func TestMigrateRatio(t *testing.T) {
flag.Parse()
var keys = *keysPtr
var nodes = *nodesPtr
var newNodes = *newNodesPtr
fmt.Printf("keys:%d, nodes:%d, newNodes:%d\n", keys, nodes, newNodes)

c := New(3, nil)
for i := 0; i < nodes; i++ {
c.Add(strconv.Itoa(i))
}

newC := New(3, nil)
for i := 0; i < newNodes; i++ {
newC.Add(strconv.Itoa(i))
}

migrate := 0
for i := 0; i < keys; i++ {
server := c.Get(strconv.Itoa(i))
newServer:= newC.Get(strconv.Itoa(i))
if server != newServer {
migrate++
}
}
migrateRatio := float64(migrate) / float64(keys)
fmt.Printf("%f%%\n", migrateRatio*100)
}


在测试迁移率的函数中,为了保证正确性,我们使用默认的hash函数来计算哈希值(前面的测试是为了方便说明所以自定义了hash函数,在这里就不能这样使用了)。通过执行如下命令,可以看到,相比于传统哈希算法,使用我们自己实现的一致哈希算法可以大大的降低数据迁移率。

$ go test -v -run=TestMigrateRatio -keys 1000000 -nodes 3 -new-nodes 4
=== RUN TestMigrateRatio
keys:1000000, nodes:3, newNodes:4
22.984500%
--- PASS: TestMigrateRatio (0.50s)



用户头像

FG佳

关注

还未添加个人签名 2019.11.13 加入

还未添加个人简介

评论

发布
暂无评论
架构师训练营week05作业(hash算法)