SatanDaddy
10/12/2019 - 9:14 AM

一致性哈希

package storedproxy

/*
  一致性哈希原理: http://www.zsythink.net/archives/1182
	
	一致性HASH负载,采用CRC32算法,
	单调性:环形空间,0 ~ 2^32-1次方的数值空间
	平衡性: 哈希算法并不能保证100%的平衡性,引入虚拟节点
	分散性
	负载
*/
import (
	"fmt"
	"hash/crc32"
	"sort"
	"strconv"
	"sync"
)

type UInt32Slice []uint32

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

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

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

type Hash func(data []byte) uint32

type HashRing struct {
	hash     Hash
	replicas int               // 复制因子,虚拟节点
	keys     UInt32Slice       // 已排序的节点哈希切片
	Nodes    map[uint32]string // 节点哈希和KEY的HashRing,键是哈希值,值是节点Key
	mutex    sync.RWMutex
}

func NewHashRing() *HashRing {
	hashRing := &HashRing{
		replicas: 400,                // 复制因子,虚拟节点200个,改动此处会影响已有key的hash的节点
		hash:     crc32.ChecksumIEEE, //使用CRC32算法
		Nodes:    make(map[uint32]string),
	}
	return hashRing
}

func (hashRing *HashRing) IsEmpty() bool {
	return len(hashRing.keys) == 0
}

// Add 方法用来添加缓存节点,参数为节点key,比如使用IP
func (hashRing *HashRing) AddNodes(nodes ...string) {
	hashRing.mutex.Lock()
	defer hashRing.mutex.Unlock()
	for _, node := range nodes {
		// 结合复制因子计算所有虚拟节点的hash值,并存入m.keys中,同时在m.Nodes中保存哈希值和key的映射
		for i := 0; i < hashRing.replicas; i++ {
			hash := hashRing.hash([]byte(node + strconv.Itoa(i)))
			if _, ok := hashRing.Nodes[hash]; !ok {
				hashRing.keys = append(hashRing.keys, hash)
				hashRing.Nodes[hash] = node
			}
		}
	}
	// 对所有虚拟节点的哈希值进行排序,方便之后进行二分查找
	sort.Sort(hashRing.keys)
}

// Get 方法根据给定的对象获取最靠近它的那个节点key
func (hashRing *HashRing) GetNode(key string) string {
	if hashRing.IsEmpty() {
		return ""
	}
	hashRing.mutex.RLock()
	defer hashRing.mutex.RUnlock()
	hash := hashRing.hash([]byte(key))
	// 通过二分查找获取最优节点,第一个节点hash值大于对象hash值的就是最优节点
	idx := sort.Search(len(hashRing.keys), func(i int) bool { return hashRing.keys[i] >= hash })

	// 如果查找结果大于节点哈希数组的最大索引,表示此时该对象哈希值位于最后一个节点之后,那么放入第一个节点中
	if idx == len(hashRing.keys) {
		idx = 0
	}
	return hashRing.Nodes[hashRing.keys[idx]]
}

func (hashRing *HashRing) printKeys() {
	fmt.Println("keys:", hashRing.keys)
}