mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-09-06 16:45:55 +00:00
This commit moves dependency management to use godep and the new experimental vendoring support in Go 1.5. NOTE: You must set `GO15VENDOREXPERIMENT` in your bash environment for vendoring to work properly! See the updated `README` for details.
203 lines
5 KiB
Go
203 lines
5 KiB
Go
// 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 consistent
|
|
|
|
import (
|
|
"errors"
|
|
"hash/crc32"
|
|
"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
|
|
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)
|
|
}
|
|
|
|
// 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++ {
|
|
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()
|
|
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)
|
|
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
|
|
}
|
|
|
|
func (c *Consistent) hashKey(key string) uint32 {
|
|
if len(key) < 64 {
|
|
copy(c.scratch[:], key)
|
|
return crc32.ChecksumIEEE(c.scratch[:len(key)])
|
|
}
|
|
return crc32.ChecksumIEEE([]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
|
|
}
|