Skip to content

Commit

Permalink
refactor(exp/container/hashring): rename NodeLocator to HashRing.
Browse files Browse the repository at this point in the history
  • Loading branch information
searKing committed Nov 4, 2024
1 parent 5ffbcdf commit 65801dd
Show file tree
Hide file tree
Showing 7 changed files with 231 additions and 231 deletions.
4 changes: 2 additions & 2 deletions go/exp/container/hashring/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func ExampleNew() {
// Dave => NodeA
}

func ExampleAdd() {
func ExampleHashRing_AddNodes() {
c := hashring.New[string]()
c.AddNodes("NodeA")
c.AddNodes("NodeB")
Expand Down Expand Up @@ -84,7 +84,7 @@ func ExampleAdd() {
// Dave => NodeA
}

func ExampleRemove() {
func ExampleHashRing_RemoveNodes() {
c := hashring.New[string]()
c.AddNodes("NodeA")
c.AddNodes("NodeB")
Expand Down
64 changes: 32 additions & 32 deletions go/exp/container/hashring/hashring.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

// Package hashring provides a consistent hashing function.
//
// NodeLocator hashing is often used to distribute requests to a changing set of servers. For example,
// HashRing 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.
//
Expand All @@ -28,14 +28,14 @@ import (

const defaultNumReps = 160

// NodeLocator holds the information about the allNodes of the consistent hash nodes.
// HashRing holds the information about the allNodes of the consistent hash nodes.
//
// Node represents a node in the consistent hash ring.
// {} -> 127.0.0.1:11311 -> 127.0.0.1:11311-0 -> 1234
// Node -> Key -> IterateKey -> HashKey
//
//go:generate go-option -type "NodeLocator"
type NodeLocator[Node comparable] struct {
//go:generate go-option -type "HashRing"
type HashRing[Node comparable] struct {
// The List of nodes to use in the Ketama consistent hash continuum
//
// This simulates the structure of keys used in the Ketama consistent hash ring,
Expand Down Expand Up @@ -66,8 +66,8 @@ type NodeLocator[Node comparable] struct {
}

// New creates a hash ring of n replicas for each entry.
func New[Node comparable](opts ...NodeLocatorOption[Node]) *NodeLocator[Node] {
r := &NodeLocator[Node]{
func New[Node comparable](opts ...HashRingOption[Node]) *HashRing[Node] {
r := &HashRing[Node]{
nodeByKey: make(map[uint32]Node),
allNodes: make(map[Node]struct{}),
hashAlg: KetamaHash,
Expand All @@ -84,19 +84,19 @@ func New[Node comparable](opts ...NodeLocatorOption[Node]) *NodeLocator[Node] {
}

// AddNodes inserts nodes into the consistent hash cycle.
func (c *NodeLocator[Node]) AddNodes(nodes ...Node) {
func (c *HashRing[Node]) AddNodes(nodes ...Node) {
if c.isWeighted {
c.addWeightNodes(nodes...)
return
}
c.addNoWeightNodes(nodes...)
}

// SetNodes setups the NodeLocator with the list of nodes it should use.
// SetNodes setups the HashRing with the list of nodes it should use.
// If there are existing nodes not present in nodes, they will be removed.
// @param nodes a List of Nodes for this NodeLocator to use in
// @param nodes a List of Nodes for this HashRing to use in
// its continuum
func (c *NodeLocator[Node]) SetNodes(nodes ...Node) {
func (c *HashRing[Node]) SetNodes(nodes ...Node) {
if c.isWeighted {
c.setWeightNodes(nodes...)
return
Expand All @@ -105,14 +105,14 @@ func (c *NodeLocator[Node]) SetNodes(nodes ...Node) {
}

// RemoveAllNodes removes all nodes in the continuum.
func (c *NodeLocator[Node]) RemoveAllNodes() {
func (c *HashRing[Node]) RemoveAllNodes() {
c.sortedKeys = nil
c.nodeByKey = make(map[uint32]Node)
c.allNodes = make(map[Node]struct{})
}

// Get returns an element close to where name hashes to in the nodes.
func (c *NodeLocator[Node]) Get(name string) (Node, bool) {
func (c *HashRing[Node]) Get(name string) (Node, bool) {
if len(c.nodeByKey) == 0 {
var zeroN Node
return zeroN, false
Expand All @@ -121,7 +121,7 @@ func (c *NodeLocator[Node]) Get(name string) (Node, bool) {
}

// GetSince returns an iterator over distinct nodes in hashring, start from where name hashes to in the nodes.
func (c *NodeLocator[Node]) GetSince(name string) iter.Seq[Node] {
func (c *HashRing[Node]) GetSince(name string) iter.Seq[Node] {
return func(yield func(Node) bool) {
if len(c.nodeByKey) == 0 {
return
Expand Down Expand Up @@ -162,31 +162,31 @@ func (c *NodeLocator[Node]) GetSince(name string) iter.Seq[Node] {

// All returns an iterator over all nodes in hashring.
// If c is empty, the sequence is empty: there is no empty element in the sequence.
func (c *NodeLocator[Node]) All() iter.Seq[Node] {
func (c *HashRing[Node]) All() iter.Seq[Node] {
return maps.Keys(c.allNodes)
}

// getAllNodes returns all available nodes
func (c *NodeLocator[Node]) getAllNodes() []Node {
func (c *HashRing[Node]) getAllNodes() []Node {
return slices.Collect(maps.Keys(c.allNodes))
}

// getPrimaryNode returns the first available node for a name, such as “127.0.0.1:11311-0” for "Alice"
func (c *NodeLocator[Node]) getPrimaryNode(name string) (Node, bool) {
func (c *HashRing[Node]) getPrimaryNode(name string) (Node, bool) {
return c.getNodeByHashKey(c.getHashKey(name))
}

// getMaxHashKey returns the last available node's HashKey
// that is, Maximum HashKey in the Hash Cycle
func (c *NodeLocator[Node]) getMaxHashKey() (key uint32, ok bool) {
func (c *HashRing[Node]) getMaxHashKey() (key uint32, ok bool) {
if len(c.sortedKeys) == 0 {
return 0, false
}
return c.sortedKeys[len(c.sortedKeys)-1], true
}

// getNodeByHashKey returns the first available node since iterateHashKey, such as HASH(“127.0.0.1:11311-0”)
func (c *NodeLocator[Node]) getNodeByHashKey(hash uint32) (Node, bool) {
func (c *HashRing[Node]) getNodeByHashKey(hash uint32) (Node, bool) {
if len(c.sortedKeys) == 0 {
var zeroN Node
return zeroN, false
Expand All @@ -204,23 +204,23 @@ func (c *NodeLocator[Node]) getNodeByHashKey(hash uint32) (Node, bool) {
}

// getNodeByHashKeyIndex returns the node by index of sorted hash keys.
func (c *NodeLocator[Node]) getNodeByHashKeyIndex(keyIndex int) (node Node) {
func (c *HashRing[Node]) getNodeByHashKeyIndex(keyIndex int) (node Node) {
return c.nodeByKey[c.sortedKeys[keyIndex]]
}

// updateLocator reconstructs the hash ring with the input nodes
func (c *NodeLocator[Node]) updateLocator(nodes ...Node) {
func (c *HashRing[Node]) updateLocator(nodes ...Node) {
c.SetNodes(nodes...)
}

// getNodeRepetitions returns the number of discrete hashes that should be defined for each node
// in the continuum.
func (c *NodeLocator[Node]) getNodeRepetitions() int {
func (c *HashRing[Node]) getNodeRepetitions() int {
return c.numReps
}

// setNoWeightNodes sets all the elements in the hash.
func (c *NodeLocator[Node]) setNoWeightNodes(nodes ...Node) {
func (c *HashRing[Node]) setNoWeightNodes(nodes ...Node) {
// Set sets all the elements in the hash.
// If there are existing elements not present in nodes, they will be removed.
var nodesToBeRemoved []Node
Expand Down Expand Up @@ -261,7 +261,7 @@ func (c *NodeLocator[Node]) setNoWeightNodes(nodes ...Node) {
}

// setWeightNodes sets all the elements in the hash.
func (c *NodeLocator[Node]) setWeightNodes(nodes ...Node) {
func (c *HashRing[Node]) setWeightNodes(nodes ...Node) {
c.RemoveAllNodes()
numReps := c.getNodeRepetitions()
nodeCount := len(nodes)
Expand All @@ -285,12 +285,12 @@ func (c *NodeLocator[Node]) setWeightNodes(nodes ...Node) {
}

// addWeightNodes adds a node to the hash without sorting the keys.
func (c *NodeLocator[Node]) addWeightNodes(nodes ...Node) {
func (c *HashRing[Node]) addWeightNodes(nodes ...Node) {
c.setWeightNodes(append(c.getAllNodes(), nodes...)...)
}

// addNoWeightNodes adds a node to the hash without sorting the keys.
func (c *NodeLocator[Node]) addNoWeightNodes(nodes ...Node) {
func (c *HashRing[Node]) addNoWeightNodes(nodes ...Node) {
numReps := c.getNodeRepetitions()

for _, node := range nodes {
Expand All @@ -301,7 +301,7 @@ func (c *NodeLocator[Node]) addNoWeightNodes(nodes ...Node) {
}

// addNodeWithoutSort adds a node to the hash without sorting the keys.
func (c *NodeLocator[Node]) addNodeWithoutSort(node Node, numReps int) {
func (c *HashRing[Node]) addNodeWithoutSort(node Node, numReps int) {
// Ketama does some special work with md5 where it reuses chunks.
// Check to be backwards compatible, the hash algorithm does not
// matter for Ketama, just the placement should always be done using
Expand Down Expand Up @@ -335,7 +335,7 @@ func (c *NodeLocator[Node]) addNodeWithoutSort(node Node, numReps int) {
}

// RemoveNodes removes nodes from the consistent hash cycle
func (c *NodeLocator[Node]) RemoveNodes(nodes ...Node) {
func (c *HashRing[Node]) RemoveNodes(nodes ...Node) {
if c.isWeighted {
c.removeWeightNodes(nodes...)
return
Expand All @@ -344,14 +344,14 @@ func (c *NodeLocator[Node]) RemoveNodes(nodes ...Node) {
}

// removeWeightNodes removes nodes from the consistent hash cycle
func (c *NodeLocator[Node]) removeWeightNodes(nodes ...Node) {
func (c *HashRing[Node]) removeWeightNodes(nodes ...Node) {
for _, node := range nodes {
delete(c.allNodes, node)
}
c.setWeightNodes(c.getAllNodes()...)
}

func (c *NodeLocator[Node]) removeNoWeightNodes(nodes ...Node) {
func (c *HashRing[Node]) removeNoWeightNodes(nodes ...Node) {
numReps := c.getNodeRepetitions()

for _, node := range nodes {
Expand Down Expand Up @@ -384,7 +384,7 @@ func (c *NodeLocator[Node]) removeNoWeightNodes(nodes ...Node) {
}

// tailSearch returns the first available node since iterateHashKey's Index, such as Index(HASH(“127.0.0.1:11311-0”))
func (c *NodeLocator[Node]) tailSearch(key uint32) (i int, found bool) {
func (c *HashRing[Node]) tailSearch(key uint32) (i int, found bool) {
// Search uses binary search to find and return the smallest index since iterateHashKey's Index
return slices.BinarySearchFunc(c.sortedKeys, key, func(v uint32, key uint32) int {
if v >= key {
Expand All @@ -395,7 +395,7 @@ func (c *NodeLocator[Node]) tailSearch(key uint32) (i int, found bool) {
}

// updateSortedNodes sorts the keys in ascending order.
func (c *NodeLocator[Node]) updateSortedNodes() {
func (c *HashRing[Node]) updateSortedNodes() {
hashes := c.sortedKeys[:0]
// reallocate if we're holding on to too much (1/4th)
// len(nodes) * replicas < cap / 4
Expand All @@ -411,6 +411,6 @@ func (c *NodeLocator[Node]) updateSortedNodes() {
}

// isSameNode checks if two nodes are the same by the key.
func (c *NodeLocator[Node]) isSameNode(n1, n2 Node) bool {
func (c *HashRing[Node]) isSameNode(n1, n2 Node) bool {
return c.nodeKeyFormatter.FormatNodeKey(n1, 0) == c.nodeKeyFormatter.FormatNodeKey(n2, 0)
}
8 changes: 4 additions & 4 deletions go/exp/container/hashring/hashring.key.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,23 @@
package hashring

// Returns a uniquely identifying key, suitable for hashing by the
// NodeLocator algorithm.
// HashRing algorithm.
// @param node The Node to use to form the unique identifier
// @param repetition The repetition number for the particular node in question
//
// (0 is the first repetition)
//
// @return The key that represents the specific repetition of the node, such as “127.0.0.1:11311-0”
func (c *NodeLocator[Node]) getIterateKeyForNode(node Node, repetition int) string {
func (c *HashRing[Node]) getIterateKeyForNode(node Node, repetition int) string {
return c.nodeKeyFormatter.FormatNodeKey(node, repetition)
}

func (c *NodeLocator[Node]) getIterateHashKeyForNode(node Node, repetition int) []uint32 {
func (c *HashRing[Node]) getIterateHashKeyForNode(node Node, repetition int) []uint32 {
return c.hashAlg.Hash(c.getIterateKeyForNode(node, repetition))
}

// 127.0.0.1:11311-0 -> 1122334455
// IterateKey -> IterateHashKey
func (c *NodeLocator[Node]) getHashKey(iterateKey string) uint32 {
func (c *HashRing[Node]) getHashKey(iterateKey string) uint32 {
return c.hashAlg.Hash(iterateKey)[0]
}
10 changes: 5 additions & 5 deletions go/exp/container/hashring/hashring.nodekeyformater.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func (f FormatterFunc[Node]) FormatNodeKey(node Node, repetition int) string {
// Formatter is used to format node for assigning nodes around the ring
type Formatter[Node comparable] interface {
// FormatNodeKey returns a uniquely identifying key, suitable for hashing by the
// NodeLocator algorithm.
// HashRing algorithm.
FormatNodeKey(node Node, repetition int) string
}

Expand Down Expand Up @@ -51,9 +51,9 @@ const (
type KetamaNodeKeyFormatter[Node comparable] struct {
format Format

// Carried over from the DefaultKetamaNodeLocatorConfiguration:
// Carried over from the DefaultKetamaHashRingConfiguration:
// Internal lookup map to try to carry forward the optimisation that was
// previously in NodeLocator
// previously in HashRing
keyByNode map[Node]string
}

Expand All @@ -69,7 +69,7 @@ func NewKetamaNodeKeyFormatter[Node comparable](format Format) *KetamaNodeKeyFor
}

// FormatNodeKey returns a uniquely identifying key, suitable for hashing by the
// NodeLocator algorithm.
// HashRing algorithm.
//
// @param node The Node to use to form the unique identifier
// @param repetition The repetition number for the particular node in question
Expand All @@ -78,7 +78,7 @@ func NewKetamaNodeKeyFormatter[Node comparable](format Format) *KetamaNodeKeyFor
//
// @return The key that represents the specific repetition of the node
func (f KetamaNodeKeyFormatter[Node]) FormatNodeKey(node Node, repetition int) string {
// Carried over from the DefaultKetamaNodeLocatorConfiguration:
// Carried over from the DefaultKetamaHashRingConfiguration:
// Internal Using the internal map retrieve the socket addresses
// for given nodes.
// I'm aware that this code is inherently thread-unsafe as
Expand Down
Loading

0 comments on commit 65801dd

Please sign in to comment.