golang consistent hash 菜鸟分析

     一直找集群的算法,刚好golang上面有一个适合。下面作为菜鸟来分析一下

// Copyright (C) 2012 Numerotron Inc.
// Use of this source code is governed by an MIT-style license
// that can be found in the LICENSE file.

// Package consistent provides a consistent hashing function.
//
// Consistent hashing is often used to distribute requests to a changing set of servers.  For example,
// say you have some cache servers cacheA, cacheB, and cacheC.  You want to decide which cache server
// to use to look up information on a user.
//
// You could use a typical hash table and hash the user id
// to one of cacheA, cacheB, or cacheC.  But with a typical hash table, if you add or remove a server,
// almost all keys will get remapped to different results, which basically could bring your service
// to a grinding halt while the caches get rebuilt.
//
// With a consistent hash, adding or removing a server drastically reduces the number of keys that
// get remapped.
//
// Read more about consistent hashing on wikipedia:  http://en.wikipedia.org/wiki/Consistent_hashing
//
package main

import (
	"errors"
	"fmt"
	"hash/crc32"
	"log"
	"sort"
	"strconv"
	"sync"
)

type uints []uint32

// Len returns the length of the uints array.
func (x uints) Len() int { return len(x) }

// Less returns true if element i is less than element j.
func (x uints) Less(i, j int) bool { return x[i] < x[j] }

// Swap exchanges elements i and j.
func (x uints) Swap(i, j int) { x[i], x[j] = x[j], x[i] }

// ErrEmptyCircle is the error returned when trying to get an element when nothing has been added to hash.
var ErrEmptyCircle = errors.New("empty circle")

// Consistent holds the information about the members of the consistent hash circle.
type Consistent struct {
	circle           map[uint32]string
	members          map[string]bool
	sortedHashes     uints  // 已经排好序的hashes slice , 主要有力搜索 (存储的内容是全部虚拟hashes值)
	NumberOfReplicas int
	count            int64
	scratch          [64]byte
	sync.RWMutex
}

// New creates a new Consistent object with a default setting of 20 replicas for each entry.
//
// To change the number of replicas, set NumberOfReplicas before adding entries.
func New() *Consistent {
	c := new(Consistent)
	c.NumberOfReplicas = 20
	c.circle = make(map[uint32]string)
	c.members = make(map[string]bool)
	//log.Printf("%p", c)
	return c
}

// eltKey generates a string key for an element with an index.
func (c *Consistent) eltKey(elt string, idx int) string {
	return elt + "|" + strconv.Itoa(idx)
}

// Add inserts a string element in the consistent hash.
func (c *Consistent) Add(elt string) {
	c.Lock()
	defer c.Unlock()
	for i := 0; i < c.NumberOfReplicas; i++ {
		fmt.Println("i:",i,c.hashKey(c.eltKey(elt, i)))
		c.circle[c.hashKey(c.eltKey(elt, i))] = elt
	}
	//log.Fatal(len(c.circle))
	//log.Println(len(c.members), elt)
	c.members[elt] = true

	c.updateSortedHashes()
	c.count++
}

// Remove removes an element from the hash.
func (c *Consistent) Remove(elt string) {
	c.Lock()
	defer c.Unlock()
	for i := 0; i < c.NumberOfReplicas; i++ {
		delete(c.circle, c.hashKey(c.eltKey(elt, i)))
	}
	delete(c.members, elt)
	c.updateSortedHashes()
	c.count--
}

// Set sets all the elements in the hash.  If there are existing elements not present in elts, they will be removed.
func (c *Consistent) Set(elts []string) {
	mems := c.Members()
	for _, k := range mems {
		found := false
		for _, v := range elts {
			if k == v {
				found = true
				break
			}
		}
		if !found {
			c.Remove(k)
		}
	}
	for _, v := range elts {
		c.RLock()
		_, exists := c.members[v]
		c.RUnlock()
		if exists {
			continue
		}
		c.Add(v)
	}
}

func (c *Consistent) Members() []string {
	c.RLock()
	defer c.RUnlock()
	var m []string
	for k := range c.members {
		m = append(m, k)
	}
	return m
}

// Get returns an element close to where name hashes to in the circle.
func (c *Consistent) Get(name string) (string, error) {
	c.RLock()
	defer c.RUnlock()
	if len(c.circle) == 0 {
		return "", ErrEmptyCircle
	}
	key := c.hashKey(name)
	log.Println("need search --> key:",key,"servername:",name)
	i := c.search(key)
	fmt.Println(c.sortedHashes[i],c.circle[c.sortedHashes[i]])
	return c.circle[c.sortedHashes[i]], nil
}

func (c *Consistent) search(key uint32) (i int) {
	f := func(x int) bool {
		log.Println("i",i)
		// 拿不到相等的
		return c.sortedHashes[x] > key
	}
	i = sort.Search(len(c.sortedHashes), f)
	log.Println("I:",i)
	if i >= len(c.sortedHashes) {
		i = 0
	}
	return
}

// GetTwo returns the two closest distinct elements to the name input in the circle.
func (c *Consistent) GetTwo(name string) (string, string, error) {
	c.RLock()
	defer c.RUnlock()
	if len(c.circle) == 0 {
		return "", "", ErrEmptyCircle
	}
	//得到hashesw 值
	key := c.hashKey(name)
	//搜索hashes
	i := c.search(key)
	//获取值
	a := c.circle[c.sortedHashes[i]]
	//如果节点只有一个时,直接返回
	if c.count == 1 {
		return a, "", nil
	}

	start := i
	var b string
	for i = start + 1; i != start; i++ {
		if i >= len(c.sortedHashes) {
			i = 0
		}
		b = c.circle[c.sortedHashes[i]]
		//两个时候否为相同的节点,不是就返回
		if b != a {
			break
		}
	}
	return a, b, nil
}

// GetN returns the N closest distinct elements to the name input in the circle.
func (c *Consistent) GetN(name string, n int) ([]string, error) {
	c.RLock()
	defer c.RUnlock()

	if len(c.circle) == 0 {
		return nil, ErrEmptyCircle
	}

	if c.count < int64(n) {
		n = int(c.count)
	}

	var (
		key   = c.hashKey(name)
		i     = c.search(key)
		start = i
		res   = make([]string, 0, n)
		elem  = c.circle[c.sortedHashes[i]]
	)

	res = append(res, elem)

	if len(res) == n {
		return res, nil
	}

	for i = start + 1; i != start; i++ {
		if i >= len(c.sortedHashes) {
			i = 0
		}
		elem = c.circle[c.sortedHashes[i]]
		if !sliceContainsMember(res, elem) {
			res = append(res, elem)
		}
		if len(res) == n {
			break
		}
	}

	return res, nil
}

func (c *Consistent) hashKey(key string) uint32 {
	//
	log.Println("key string:",key)
	if len(key) < 64 {
		var scratch [64]byte
		copy(scratch[:], key)
		//log.Fatal(len(key), scratch)
		return crc32.ChecksumIEEE(scratch[:len(key)])
	}
	return crc32.ChecksumIEEE([]byte(key))
}
// 对hash 进行排序
func (c *Consistent) updateSortedHashes() {
	hashes := c.sortedHashes[:0]
	//reallocate if we're holding on to too much (1/4th)
	//log.Fatal("exit test:",cap(c.sortedHashes))
	if cap(c.sortedHashes)/(c.NumberOfReplicas*4) > len(c.circle) {
		hashes = nil
	}
	for k := range c.circle {
		hashes = append(hashes, k)
		log.Println(k)
	}
	sort.Sort(hashes)
	c.sortedHashes = hashes
	log.Println("tem hashes size :",len(hashes),len(c.sortedHashes))
}

func sliceContainsMember(set []string, member string) bool {
	for _, m := range set {
		if m == member {
			return true
		}
	}
	return false
}

func main() {
	c := New()
	//fmt.Printf("%T", D)
	c.Add("redis-1")
	c.Add("redis-2")
	c.Add("redis-3")
	log.Fatal(c.GetN("redis-2",1))
	v, ok := c.Get("redis-one")
	if ok == nil {
		for i, vv := range v {
			fmt.Println(i, vv)
		}
	}
	log.Println("members size:",len(c.members),"\tcircle size :",len(c.circle),"sortHashes:",len(c.sortedHashes),"scratch:",c.scratch)
	log.Println("sortHashes value:",c.sortedHashes)
	//log.Fatal("...")
}
 

       其中有几点不是很理解,scratch 这个东西好像没用到,还有就是在计算虚拟节点时,他是使用'>'来计算的,假设我们设置一个节点redis,那满默认回事redis|1,redis|2..,这样进行节点分布,如果获取redis时,使用redis|1进行搜索,搜索出来就不是redis|1这个虚拟节点了,可能是其他节点。还有在求近距离节点是它是按升排序进行搜索的,而不考虑左右这个方式找最近节点。


  1 type Consistent struct {    
  2 »···circle           map[uint32]string // 用来存储node(string) 和 vnode的对应关系,  vnode 是一个hash出来的uint32的整数,也就是最大分区数为4294967296
  3 »···members          map[string]bool   // string 为 node, bool表示实体节点是否存活
  4 »···sortedHashes     uints // 已经排好序的hashes slice , 主要有力搜索 (存储的内容是全部vnode hashes值)
  5 »···NumberOfReplicas int   // node 的权重, 也就是node对应的vnode的个数
  6 »···count            int64 // 物理节点
  7 »···scratch          [64]byte
  8 »···sync.RWMutex
  9 }             


这种一致性hash和 Dynamo算法的一致性hash是有很大区别的,这种hash排序不是全有序的;

测试例子:

func main() {
    c := New()
    c.Set([]string{"redisA", "redisB"})
    fmt.Println(c.NumberOfReplicas)
    fmt.Println(c.Members())
    for k, v := range c.sortedHashes {
        fmt.Println(k, c.circle[v])
    }
}

输出:

▶ go run consistent.go
20
[redisB redisA]
0 redisA
1 redisB
2 redisA
3 redisB
4 redisA
5 redisB
6 redisA
7 redisB
8 redisA
9 redisA
10 redisB
11 redisA
12 redisA
13 redisB
14 redisA
15 redisB
16 redisB
17 redisA
18 redisB
19 redisB
20 redisA
21 redisB
22 redisA
23 redisB
24 redisA
25 redisB
26 redisA
27 redisB
28 redisA
29 redisB
30 redisB
31 redisA
32 redisB
33 redisB
34 redisA
35 redisA
36 redisB
37 redisA
38 redisA
39 redisB


31 A -> 32B -> 33B ,如果是Dynamo,那么应该是31A -> 32B -> 33A这样循环下去,所以如果想使用这种一致性hash算法来做备份容灾,是不行的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值