From cf1431a55451910380004f432e954ce6c4ce8564 Mon Sep 17 00:00:00 2001 From: Tochemey Date: Sat, 22 Jun 2024 18:35:54 +0100 Subject: [PATCH] docs: document cluster client --- README.md | 23 +++++++++++++++++++++ client/client.go | 54 ++++++++++++++++++++++-------------------------- 2 files changed, 48 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 359d605a..0654dc0b 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ The project adheres to [Semantic Versioning](https://semver.org) and [Convention - [Logging](#logging) - [Testkit](#testkit) - [API](#api) + - [Client](#client) - [Clustering](#clustering) - [Operations Guide](#operations-guide) - [Redeployment](#redeployment) @@ -342,6 +343,28 @@ The API interface helps interact with a Go-Akt actor system as kind of client. T - `RemoteStop`: to stop an actor on a remote machine - `RemoteSpawn`: to start an actor on a remote machine. The given actor implementation must be registered using the [`Register`](./actors/actor_system.go) method of the actor system on the remote machine for this call to succeed. +## Client + +The Go-Akt client facilitates interaction with a specified Go-Akt cluster, contingent upon the activation of cluster mode. +The client operates without knowledge of the specific node within the cluster that will process the request. +This feature is particularly beneficial when interfacing with a Go-Akt cluster from an external system. +Go-Akt client is equipped with a mini load-balancer that helps route requests to the appropriate node. + +### Balancer strategies + +- [Round Robin](./client/round_robin.go) - a given node is chosen using the round-robin strategy +- [Random](./client/random.go) - a given node is chosen randomly +- [Least Load](./client/least_load.go) - the node with the least number of actors is chosen + +### Features: + +- `Kinds` - to list all the actor kinds in the cluster +- `Spawn` - to spawn an actor in the cluster +- `Kill` - to kill/stop an actor in the cluster +- `Ask` - to send a message to a given actor in the cluster and expect a response +- `Tell` - to send a fire-forget message to a given actor in the cluster +- `Whereis` - to locate and get the address of a given actor + ## Clustering The cluster engine depends upon the [discovery](./discovery/provider.go) mechanism to find other nodes in the cluster. diff --git a/client/client.go b/client/client.go index d86f4e9a..e67c2408 100644 --- a/client/client.go +++ b/client/client.go @@ -165,30 +165,20 @@ func (x *Client) Kinds(ctx context.Context) ([]string, error) { // The actor name will be generated and returned when the request is successful func (x *Client) Spawn(ctx context.Context, actor *Actor) (err error) { x.locker.Lock() - defer x.locker.Unlock() - host, port := x.getNextRemotingHostAndPort() - return actors.RemoteSpawn(ctx, host, port, actor.Name(), actor.Kind()) + remoteHost, remotePort := x.getNextRemotingHostAndPort() + x.locker.Unlock() + return actors.RemoteSpawn(ctx, remoteHost, remotePort, actor.Name(), actor.Kind()) } // Tell sends a message to a given actor provided the actor name. // If the given actor does not exist it will be created automatically when // Client mode is enabled func (x *Client) Tell(ctx context.Context, actor *Actor, message proto.Message) error { - x.locker.Lock() - defer x.locker.Unlock() - - remoteHost, remotePort := x.getNextRemotingHostAndPort() // lookup the actor address - address, err := actors.RemoteLookup(ctx, remoteHost, remotePort, actor.Name()) + address, err := x.Whereis(ctx, actor) if err != nil { return err } - - // no address found - if address == nil || proto.Equal(address, new(goaktpb.Address)) { - return actors.ErrActorNotFound(actor.Name()) - } - return actors.RemoteTell(ctx, address, message) } @@ -196,35 +186,41 @@ func (x *Client) Tell(ctx context.Context, actor *Actor, message proto.Message) // If the given actor does not exist it will be created automatically when // Client mode is enabled. This will block until a response is received or timed out. func (x *Client) Ask(ctx context.Context, actor *Actor, message proto.Message) (reply proto.Message, err error) { - x.locker.Lock() - defer x.locker.Unlock() - - remoteHost, remotePort := x.getNextRemotingHostAndPort() // lookup the actor address - address, err := actors.RemoteLookup(ctx, remoteHost, remotePort, actor.Name()) + address, err := x.Whereis(ctx, actor) if err != nil { return nil, err } - - // no address found - if address == nil || proto.Equal(address, new(goaktpb.Address)) { - return nil, actors.ErrActorNotFound(actor.Name()) - } - response, err := actors.RemoteAsk(ctx, address, message) if err != nil { return nil, err } - return response.UnmarshalNew() } // Kill kills a given actor in the Client func (x *Client) Kill(ctx context.Context, actor *Actor) error { x.locker.Lock() - defer x.locker.Unlock() - host, port := x.getNextRemotingHostAndPort() - return actors.RemoteStop(ctx, host, port, actor.Name()) + remoteHost, remotePort := x.getNextRemotingHostAndPort() + x.locker.Unlock() + return actors.RemoteStop(ctx, remoteHost, remotePort, actor.Name()) +} + +// Whereis finds and returns the address of a given actor +func (x *Client) Whereis(ctx context.Context, actor *Actor) (*goaktpb.Address, error) { + x.locker.Lock() + remoteHost, remotePort := x.getNextRemotingHostAndPort() + x.locker.Unlock() + // lookup the actor address + address, err := actors.RemoteLookup(ctx, remoteHost, remotePort, actor.Name()) + if err != nil { + return nil, err + } + // no address found + if address == nil || proto.Equal(address, new(goaktpb.Address)) { + return nil, actors.ErrActorNotFound(actor.Name()) + } + return address, nil } // getNextRemotingHostAndPort returns the next node host and port