|
- package consistent
-
- import (
- "errors"
- "sort"
- "strconv"
- "sync"
-
- "github.com/spaolacci/murmur3"
- )
-
- 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
- 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)
- 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)
- return strconv.Itoa(idx) + elt
- }
-
- // Add inserts a string element in the consistent hash.
- func (c *Consistent) Add(elt string) {
- c.Lock()
- defer c.Unlock()
- c.add(elt)
- }
-
- // need c.Lock() before calling
- func (c *Consistent) add(elt string) {
- for i := 0; i < c.NumberOfReplicas; i++ {
- c.circle[c.hashKey(c.eltKey(elt, i))] = 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()
- c.remove(elt)
- }
-
- // need c.Lock() before calling
- func (c *Consistent) remove(elt string) {
- 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) {
- c.Lock()
- defer c.Unlock()
- for k := range c.members {
- found := false
- for _, v := range elts {
- if k == v {
- found = true
- break
- }
- }
- if !found {
- c.remove(k)
- }
- }
- for _, v := range elts {
- _, exists := c.members[v]
- 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)
- i := c.search(key)
- return c.circle[c.sortedHashes[i]], nil
- }
-
- func (c *Consistent) search(key uint32) (i int) {
- f := func(x int) bool {
- return c.sortedHashes[x] > key
- }
- i = sort.Search(len(c.sortedHashes), f)
- 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
- }
- key := c.hashKey(name)
- 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 {
- return murmur3.Sum32([]byte(key))
- }
-
- func (c *Consistent) updateSortedHashes() {
- hashes := c.sortedHashes[:0]
- //reallocate if we're holding on to too much (1/4th)
- if cap(c.sortedHashes)/(c.NumberOfReplicas*4) > len(c.circle) {
- hashes = nil
- }
- for k := range c.circle {
- hashes = append(hashes, k)
- }
- sort.Sort(hashes)
- c.sortedHashes = hashes
- }
-
- func sliceContainsMember(set []string, member string) bool {
- for _, m := range set {
- if m == member {
- return true
- }
- }
- return false
- }
|