一致哈希Go实现及应用(一致哈希学习笔记三)

一致哈希算法介绍参考本站文章《一致哈希算法理解(一致哈希学习笔记一)》

回顾一致哈希算法步骤:
0、将每一个服务器节点按照ip或者server_name, 通过hash函数(eg: crc32)得到对应的一组hash整数值,这里称做hash_servers,并对得到的整数值按由小到大进行排序。
1、将key也通过上面同一个hash函数计算得到对应hash整数值, 这里称为hash_key。

2、遍历hash_servers,用hash_key与hash_servers中的数逐个比较,当hash_key最后小于hash_servers中的那个数存在时,这个数对应的节点就是这个key所存储的节点,否则存在第一个节点。

一、Go实现
这里主要是一致哈希算法Go的一种实现笔记,PHP的实现参考本站文章《一致哈希PHP实现及应用(一致哈希学习笔记二)

package main

import (
	"fmt"
	"hash/crc32"
	"sort"
)

const (
	// 虚拟节点数
	VirualNodes = 32
)

// 定义存储所有slot的切片,实现sort.Sort()的接口用于排序
type slots []uint32

func (s slots) Len() int {
	return len(s)
}

func (s slots) Less(i, j int) bool {
	return s[i] < s[j]
}

func (s slots) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}

type ConsistentHashing struct {
	nodesMap        map[uint32]string
	nodesSlots      slots
}

// 自定义哈希函数,使用crc32
func (h *ConsistentHashing) myHash(key string) uint32 {
	return crc32.ChecksumIEEE([]byte(key))
}

// 添加节点
func (c *ConsistentHashing) AddNode(node string) {	
	for i := 0; i < VirualNodes; i++ {
		slot := c.myHash(fmt.Sprintf("%s-%d", node, i))
		//fmt.Println(node, slot)
		c.nodesMap[slot] = node
	}
	c.sortNodesSlots()
}

// 将slot保存到切片中并升序
func (c *ConsistentHashing) sortNodesSlots() {
	slotst := slots{}
	for slot := range c.nodesMap {
		slotst = append(slotst, slot)
	}
	sort.Sort(slotst)
	c.nodesSlots = slotst
}

// 删除节点
func (c *ConsistentHashing) DelNode(node string) {	
	for i := 0; i < VirualNodes; i++ {
		slot := c.myHash(fmt.Sprintf("%s-%d", node, i))
		delete(c.nodesMap, slot)
	}
	c.sortNodesSlots()
}

// 定位key所属节点
func (c *ConsistentHashing) Lookup(key string) string {
	slot := c.myHash(key)
	
	index := uint32(0)
	for _, nodesSlot := range c.nodesSlots {
		if nodesSlot >= slot {
			index = nodesSlot
			break
		}
	}
	if node, ok := c.nodesMap[index]; ok {
		return node
	} else {
		return "不存在"
	}
	
}


func main() {
	ch := ConsistentHashing {
		nodesMap: map[uint32]string{},
	}
	
	// test case
	ch.AddNode("N1")
	ch.AddNode("N2")
	ch.AddNode("N3")
	
	k1, k2 := "niliu_k1", "niliu_k2"
	
	fmt.Println(fmt.Sprintf("key: %s, 落在 %s 节点上", k1, ch.Lookup(k1)))
	fmt.Println(fmt.Sprintf("key: %s, 落在 %s 节点上", k2, ch.Lookup(k2)))
	
	ch.DelNode("N1")
	
	//fmt.Println(ch.nodesSlots)
	//fmt.Println(ch.nodesMap)
	
	fmt.Println("after delete node")
	fmt.Println(fmt.Sprintf("key: %s, 落在 %s 节点上", k1, ch.Lookup(k1)))
	fmt.Println(fmt.Sprintf("key: %s, 落在 %s 节点上", k2, ch.Lookup(k2)))
}

测试结果

key: niliu_k1, 落在 N1 节点上
key: niliu_k2, 落在 N3 节点上
after delete node
key: niliu_k1, 落在 N3 节点上
key: niliu_k2, 落在 N3 节点上

二、TODO
1、查找节点的时候使用的顺序查找,效率比较低,可以考虑二分查找
2、更新节点的方法未实现
3、实现一致哈希的方法放到单独的package中
4、优化使用读写锁
5、补充更多的测试case

三、参考
https://github.com/g4zhuj/hashring/blob/master/hashring.go

发表评论

电子邮件地址不会被公开。 必填项已用*标注