github.com/asynkron/protoactor-go@v0.0.0-20240308120642-ef91a6abee75/cluster/rendezvous.go (about)

     1  package cluster
     2  
     3  // Rendezvous.go
     4  // A revised FNV1A32 version of
     5  // https://github.com/tysonmote/rendezvous/blob/master/rendezvous.go
     6  
     7  import (
     8  	"hash"
     9  	"hash/fnv"
    10  	"strings"
    11  	"sync"
    12  )
    13  
    14  type memberData struct {
    15  	member    *Member
    16  	hashBytes []byte
    17  }
    18  type Rendezvous struct {
    19  	mutex      sync.RWMutex
    20  	hasher     hash.Hash32
    21  	hasherLock sync.Mutex
    22  	members    []*memberData
    23  }
    24  
    25  func NewRendezvous() *Rendezvous {
    26  	return &Rendezvous{
    27  		hasher:  fnv.New32a(),
    28  		members: make([]*memberData, 0),
    29  	}
    30  }
    31  
    32  func (r *Rendezvous) GetByClusterIdentity(ci *ClusterIdentity) string {
    33  	r.mutex.RLock()
    34  	defer r.mutex.RUnlock()
    35  
    36  	identity := ci.Identity
    37  	m := r.memberDataByKind(ci.Kind)
    38  
    39  	l := len(m)
    40  
    41  	if l == 0 {
    42  		return ""
    43  	}
    44  
    45  	if l == 1 {
    46  		return m[0].member.Address()
    47  	}
    48  
    49  	keyBytes := []byte(identity)
    50  
    51  	var maxScore uint32
    52  	var maxMember *memberData
    53  	var score uint32
    54  
    55  	for _, node := range m {
    56  		score = r.hash(node.hashBytes, keyBytes)
    57  		if score > maxScore {
    58  			maxScore = score
    59  			maxMember = node
    60  		}
    61  	}
    62  
    63  	if maxMember == nil {
    64  		return ""
    65  	}
    66  	return maxMember.member.Address()
    67  }
    68  
    69  func (r *Rendezvous) GetByIdentity(identity string) string {
    70  	parts := strings.SplitN(identity, "/", 2)
    71  
    72  	return r.GetByClusterIdentity(&ClusterIdentity{
    73  		Kind:     parts[0],
    74  		Identity: parts[1],
    75  	})
    76  }
    77  
    78  func (r *Rendezvous) memberDataByKind(kind string) []*memberData {
    79  	m := make([]*memberData, 0)
    80  	for _, md := range r.members {
    81  		if md.member.HasKind(kind) {
    82  			m = append(m, md)
    83  		}
    84  	}
    85  	return m
    86  }
    87  
    88  func (r *Rendezvous) UpdateMembers(members Members) {
    89  	r.mutex.Lock()
    90  	defer r.mutex.Unlock()
    91  
    92  	tmp := members.ToSet()
    93  	r.members = make([]*memberData, 0)
    94  
    95  	for _, m := range tmp.Members() {
    96  		keyBytes := []byte(m.Address()) // TODO: should be utf8 to match .net
    97  		r.members = append(r.members, &memberData{
    98  			member:    m,
    99  			hashBytes: keyBytes,
   100  		})
   101  	}
   102  }
   103  
   104  func (r *Rendezvous) hash(node, key []byte) uint32 {
   105  	r.hasherLock.Lock()
   106  	defer r.hasherLock.Unlock()
   107  
   108  	r.hasher.Reset()
   109  	r.hasher.Write(key)
   110  	r.hasher.Write(node)
   111  	return r.hasher.Sum32()
   112  }