github.com/asynkron/protoactor-go@v0.0.0-20240308120642-ef91a6abee75/cluster/informer.go (about) 1 // Copyright (C) 2015-2022 Asynkron AB All rights reserved 2 3 package cluster 4 5 import ( 6 "fmt" 7 "log/slog" 8 "math/rand" 9 "reflect" 10 "time" 11 12 "github.com/asynkron/gofun/set" 13 "github.com/asynkron/protoactor-go/actor" 14 "google.golang.org/protobuf/proto" 15 ) 16 17 const ( 18 TopologyKey string = "topology" 19 HearthbeatKey string = "heathbeat" 20 GracefullyLeftKey string = "left" 21 ) 22 23 // create and seed a pseudo random numbers generator 24 var rnd = rand.New(rand.NewSource(time.Now().UnixMicro())) 25 26 // The Informer data structure implements the Gossip interface 27 type Informer struct { 28 myID string 29 localSeqNumber int64 30 state *GossipState 31 committedOffsets map[string]int64 32 activeMemberIDs map[string]empty 33 otherMembers []*Member 34 consensusChecks *ConsensusChecks 35 getBlockedMembers func() set.Set[string] 36 gossipFanOut int 37 gossipMaxSend int 38 throttler actor.ShouldThrottle 39 logger *slog.Logger 40 } 41 42 // makes sure Informer complies with the Gossip interface 43 var _ Gossip = (*Informer)(nil) 44 45 // Creates a new Informer value with the given properties and returns 46 // back a pointer to its memory location in the heap 47 func newInformer(myID string, getBlockedMembers func() set.Set[string], fanOut int, maxSend int, logger *slog.Logger) *Informer { 48 informer := Informer{ 49 myID: myID, 50 state: &GossipState{ 51 Members: map[string]*GossipState_GossipMemberState{}, 52 }, 53 committedOffsets: map[string]int64{}, 54 activeMemberIDs: map[string]empty{}, 55 otherMembers: []*Member{}, 56 consensusChecks: NewConsensusChecks(), 57 getBlockedMembers: getBlockedMembers, 58 gossipFanOut: fanOut, 59 gossipMaxSend: maxSend, 60 logger: logger, 61 } 62 informer.throttler = actor.NewThrottle(3, 60*time.Second, informer.throttledLog) 63 return &informer 64 } 65 66 // called when there is a cluster topology update 67 func (inf *Informer) UpdateClusterTopology(topology *ClusterTopology) { 68 var others []*Member 69 for _, member := range topology.Members { 70 if member.Id != inf.myID { 71 others = append(others, member) 72 } 73 } 74 inf.otherMembers = others 75 76 active := make(map[string]empty) 77 for _, member := range topology.Members { 78 active[member.Id] = empty{} 79 } 80 81 inf.SetState(TopologyKey, topology) 82 } 83 84 // sets new update key state using the given proto message 85 func (inf *Informer) SetState(key string, message proto.Message) { 86 inf.localSeqNumber = setKey(inf.state, key, message, inf.myID, inf.localSeqNumber) 87 88 //if inf.throttler() == actor.Open { 89 // sequenceNumbers := map[string]uint64{} 90 // 91 // for _, memberState := range inf.state.Members { 92 // for key, value := range memberState.Values { 93 // sequenceNumbers[key] = uint64(value.SequenceNumber) 94 // } 95 // } 96 // 97 // // plog.Debug("Setting state", log.String("key", key), log.String("value", message.String()), log.Object("state", sequenceNumbers)) 98 //} 99 100 if _, ok := inf.state.Members[inf.myID]; !ok { 101 inf.logger.Error("State corrupt") 102 } 103 104 inf.checkConsensusKey(key) 105 } 106 107 // sends this informer local state to remote informers chosen randomly 108 // from the slice of other members known by this informer until gossipFanOut 109 // number of sent has been reached 110 func (inf *Informer) SendState(sendStateToMember LocalStateSender) { 111 // inf.purgeBannedMembers() // TODO 112 for _, member := range inf.otherMembers { 113 ensureMemberStateExists(inf.state, member.Id) 114 } 115 116 // make a copy of the otherMembers so we can sort it randomly 117 otherMembers := make([]*Member, len(inf.otherMembers)) 118 copy(otherMembers, inf.otherMembers) 119 120 // shuffles the order of the slice elements 121 rnd.Shuffle(len(otherMembers), func(i, j int) { 122 otherMembers[i], otherMembers[j] = otherMembers[j], otherMembers[i] 123 }) 124 125 fanOutCount := 0 126 for _, member := range otherMembers { 127 memberState := inf.GetMemberStateDelta(member.Id) 128 if !memberState.HasState { 129 // nothing has change, skip it 130 continue 131 } 132 133 // fire and forget, we handle results in ReenterAfter 134 sendStateToMember(memberState, member) 135 fanOutCount++ 136 137 // we reached our limit, break 138 if fanOutCount >= inf.gossipFanOut { 139 break 140 } 141 } 142 } 143 144 func (inf *Informer) GetMemberStateDelta(targetMemberID string) *MemberStateDelta { 145 var count int 146 147 // newState will old the final new state to be sent 148 newState := GossipState{Members: make(map[string]*GossipState_GossipMemberState)} 149 150 // hashmaps in Go are random by nature so no need to randomize state.Members 151 pendingOffsets := inf.committedOffsets 152 153 // create a new map with gossipMaxSend entries max 154 members := make(map[string]*GossipState_GossipMemberState) 155 156 // add ourselves to the gossip list if we are in the members state 157 if member, ok := inf.state.Members[inf.myID]; ok { 158 members[inf.myID] = member 159 count++ 160 } 161 162 // Go hash maps are unordered by nature so we don't need to randomize them 163 // iterate over our state members skipping ourselves and add them to the 164 // local `newState` variable until gossipMaxSend is reached 165 for id, member := range inf.state.Members { 166 if id == inf.myID { 167 continue 168 } 169 170 count++ 171 members[id] = member 172 173 if count > inf.gossipMaxSend { 174 break 175 } 176 } 177 178 // now we iterate over our subset of members and proceed to send them if applicable 179 for memberID, memberState := range members { 180 181 // create an empty state 182 newMemberState := GossipState_GossipMemberState{ 183 Values: make(map[string]*GossipKeyValue), 184 } 185 186 watermarkKey := fmt.Sprintf("%s.%s", targetMemberID, memberID) 187 188 // get the water mark 189 watermark := inf.committedOffsets[watermarkKey] 190 newWatermark := watermark 191 192 // for each value in member state 193 for key, value := range memberState.Values { 194 195 if value.SequenceNumber <= watermark { 196 continue 197 } 198 199 if value.SequenceNumber > newWatermark { 200 newWatermark = value.SequenceNumber 201 } 202 203 newMemberState.Values[key] = value 204 } 205 206 // do not send memberStates that we have no new data for 207 if len(newMemberState.Values) > 0 { 208 newState.Members[memberID] = &newMemberState 209 pendingOffsets[watermarkKey] = newWatermark 210 } 211 } 212 213 hasState := reflect.DeepEqual(inf.committedOffsets, pendingOffsets) 214 memberState := &MemberStateDelta{ 215 TargetMemberID: targetMemberID, 216 HasState: hasState, 217 State: &newState, 218 CommitOffsets: func() { 219 inf.commitPendingOffsets(pendingOffsets) 220 }, 221 } 222 223 return memberState 224 } 225 226 // adds a new consensus checker to this informer 227 func (inf *Informer) AddConsensusCheck(id string, check *ConsensusCheck) { 228 inf.consensusChecks.Add(id, check) 229 230 // check when adding, if we are already consistent 231 check.check(inf.state, inf.activeMemberIDs) 232 } 233 234 // removes a consensus checker from this informer 235 func (inf *Informer) RemoveConsensusCheck(id string) { 236 inf.consensusChecks.Remove(id) 237 } 238 239 // retrieves this informer current state for the given key 240 // returns map containing each known member id and their value 241 func (inf *Informer) GetState(key string) map[string]*GossipKeyValue { 242 entries := make(map[string]*GossipKeyValue) 243 244 for memberID, memberState := range inf.state.Members { 245 if value, ok := memberState.Values[key]; ok { 246 entries[memberID] = value 247 } 248 } 249 250 return entries 251 } 252 253 // receives a remote informer state 254 func (inf *Informer) ReceiveState(remoteState *GossipState) []*GossipUpdate { 255 updates, newState, updatedKeys := mergeState(inf.state, remoteState) 256 if len(updates) == 0 { 257 return nil 258 } 259 260 inf.state = newState 261 keys := make([]string, 0, len(updatedKeys)) 262 for k := range updatedKeys { 263 keys = append(keys, k) 264 } 265 266 inf.CheckConsensus(keys...) 267 return updates 268 } 269 270 // check consensus for the given keys 271 func (inf *Informer) CheckConsensus(updatedKeys ...string) { 272 for _, consensusCheck := range inf.consensusChecks.GetByUpdatedKeys(updatedKeys) { 273 consensusCheck.check(inf.state, inf.activeMemberIDs) 274 } 275 } 276 277 // runs checkers on key updates 278 func (inf *Informer) checkConsensusKey(updatedKey string) { 279 for _, consensusCheck := range inf.consensusChecks.GetByUpdatedKey(updatedKey) { 280 consensusCheck.check(inf.state, inf.activeMemberIDs) 281 } 282 } 283 284 func (inf *Informer) commitPendingOffsets(offsets map[string]int64) { 285 for key, seqNumber := range offsets { 286 if offset, ok := inf.committedOffsets[key]; !ok || offset < seqNumber { 287 inf.committedOffsets[key] = seqNumber 288 } 289 } 290 } 291 292 func (inf *Informer) throttledLog(counter int32) { 293 inf.logger.Debug("[Gossip] Setting State", slog.Int("throttled", int(counter))) 294 }