cshash(consistent hashing)

Thread-safe consistent hashing implementation with virtual nodes support, ideal for distributed systems and load balancing scenarios.

The cshash module provides a thread-safe consistent hashing implementation with virtual nodes support, ideal for distributed systems and load balancing scenarios.

Features

  • Thread-safe operations using read-write mutex for concurrent access
  • Virtual nodes support for better load distribution
  • Custom hash functions with default FNV-1a implementation
  • Dynamic node management with batch update operations
  • Efficient key lookup using binary search on sorted hash ring

Basic Usage

 1package main
 2
 3import (
 4    "fmt"
 5    "hash/fnv"
 6    
 7    "github.com/crazyfrankie/frx/cshash"
 8)
 9
10func main() {
11    // Create a new consistent hash map with 3 virtual nodes per physical node
12    hashMap := cshash.NewMap(3, nil) // nil uses default FNV-1a hash function
13    
14    // Add initial nodes
15    nodes := []string{"server1", "server2", "server3"}
16    hashMap.Update(nil, nodes)
17    
18    // Get node for a key
19    node := hashMap.Get("user123")
20    fmt.Printf("Key 'user123' maps to: %s\n", node)
21    
22    node = hashMap.Get("session456")
23    fmt.Printf("Key 'session456' maps to: %s\n", node)
24}

Custom Hash Function

 1// Using a custom hash function
 2func customHash(data []byte) uint64 {
 3    h := fnv.New64()
 4    h.Write(data)
 5    return h.Sum64()
 6}
 7
 8func main() {
 9    // Create hash map with custom hash function
10    hashMap := cshash.NewMap(5, customHash)
11    
12    // Add nodes
13    hashMap.Update(nil, []string{"node1", "node2", "node3"})
14    
15    // Test key distribution
16    keys := []string{"key1", "key2", "key3", "key4", "key5"}
17    for _, key := range keys {
18        node := hashMap.Get(key)
19        fmt.Printf("Key '%s' -> Node '%s'\n", key, node)
20    }
21}

Dynamic Node Management

 1func main() {
 2    hashMap := cshash.NewMap(3, nil)
 3    
 4    // Initial setup
 5    initialNodes := []string{"server1", "server2", "server3"}
 6    hashMap.Update(nil, initialNodes)
 7    
 8    fmt.Println("Initial mapping:")
 9    testKey := "user123"
10    fmt.Printf("Key '%s' -> %s\n", testKey, hashMap.Get(testKey))
11    
12    // Add new nodes and remove old ones
13    toRemove := []string{"server1"}
14    toAdd := []string{"server4", "server5"}
15    
16    hashMap.Update(toRemove, toAdd)
17    
18    fmt.Println("After update:")
19    fmt.Printf("Key '%s' -> %s\n", testKey, hashMap.Get(testKey))
20    
21    // Add more nodes
22    hashMap.Update(nil, []string{"server6", "server7"})
23    
24    fmt.Println("After adding more nodes:")
25    fmt.Printf("Key '%s' -> %s\n", testKey, hashMap.Get(testKey))
26}

Load Balancing Example

 1package main
 2
 3import (
 4    "fmt"
 5    "math/rand"
 6    "time"
 7    
 8    "github.com/crazyfrankie/frx/cshash"
 9)
10
11func main() {
12    // Create hash map for load balancing
13    hashMap := cshash.NewMap(10, nil) // More virtual nodes for better distribution
14    
15    // Available servers
16    servers := []string{
17        "web-server-1:8080",
18        "web-server-2:8080", 
19        "web-server-3:8080",
20        "web-server-4:8080",
21    }
22    
23    hashMap.Update(nil, servers)
24    
25    // Simulate client requests
26    rand.Seed(time.Now().UnixNano())
27    
28    serverCounts := make(map[string]int)
29    
30    // Generate 1000 random client IDs and see distribution
31    for i := 0; i < 1000; i++ {
32        clientID := fmt.Sprintf("client_%d", rand.Intn(10000))
33        server := hashMap.Get(clientID)
34        serverCounts[server]++
35    }
36    
37    fmt.Println("Request distribution:")
38    for server, count := range serverCounts {
39        fmt.Printf("%s: %d requests (%.1f%%)\n", 
40            server, count, float64(count)/10.0)
41    }
42    
43    // Simulate server failure and recovery
44    fmt.Println("\nSimulating server failure...")
45    hashMap.Update([]string{"web-server-2:8080"}, nil)
46    
47    // Test same keys after server removal
48    fmt.Println("Key mapping after server failure:")
49    testKeys := []string{"client_1234", "client_5678", "client_9999"}
50    for _, key := range testKeys {
51        server := hashMap.Get(key)
52        fmt.Printf("Key '%s' -> %s\n", key, server)
53    }
54}

Cache Sharding Example

 1package main
 2
 3import (
 4    "fmt"
 5    "strconv"
 6    
 7    "github.com/crazyfrankie/frx/cshash"
 8)
 9
10type CacheCluster struct {
11    hashMap *cshash.HashMap
12    caches  map[string]*Cache
13}
14
15type Cache struct {
16    name string
17    data map[string]interface{}
18}
19
20func NewCacheCluster(cacheNodes []string) *CacheCluster {
21    cluster := &CacheCluster{
22        hashMap: cshash.NewMap(5, nil),
23        caches:  make(map[string]*Cache),
24    }
25    
26    // Initialize cache instances
27    for _, node := range cacheNodes {
28        cluster.caches[node] = &Cache{
29            name: node,
30            data: make(map[string]interface{}),
31        }
32    }
33    
34    // Update hash map
35    cluster.hashMap.Update(nil, cacheNodes)
36    
37    return cluster
38}
39
40func (cc *CacheCluster) Set(key string, value interface{}) {
41    node := cc.hashMap.Get(key)
42    if cache, exists := cc.caches[node]; exists {
43        cache.data[key] = value
44        fmt.Printf("Stored '%s' in cache '%s'\n", key, node)
45    }
46}
47
48func (cc *CacheCluster) Get(key string) (interface{}, bool) {
49    node := cc.hashMap.Get(key)
50    if cache, exists := cc.caches[node]; exists {
51        value, found := cache.data[key]
52        return value, found
53    }
54    return nil, false
55}
56
57func main() {
58    // Create cache cluster
59    cacheNodes := []string{"cache-1", "cache-2", "cache-3"}
60    cluster := NewCacheCluster(cacheNodes)
61    
62    // Store some data
63    for i := 0; i < 10; i++ {
64        key := "key" + strconv.Itoa(i)
65        value := "value" + strconv.Itoa(i)
66        cluster.Set(key, value)
67    }
68    
69    // Retrieve data
70    fmt.Println("\nRetrieving data:")
71    for i := 0; i < 10; i++ {
72        key := "key" + strconv.Itoa(i)
73        if value, found := cluster.Get(key); found {
74            fmt.Printf("Found '%s' = '%s'\n", key, value)
75        }
76    }
77}