Remote Actors #
Akka supports communication with actors on remote machines. Corresponding actor references can be used just like those for local actors. From a programming point of view the location of an actor is hardly visible which helps develop distributed systems.
We will use a low level API provided by Akka to demonstrrate remote actors. Although the documentation advises against its direct use, more advanced APIs for distributed actors are out of scope for this tutorial.
Configuration #
To enable remote communication,
we need to configure Akka in the application.conf
file.
akka {
actor {
provider = remote
serialization-bindings {
"sebfisch.actors.JsonSerializable" = jackson-json
}
}
remote {
artery {
transport = tcp
}
}
}
Apart from selecting a provider that supports remote communication
and selecting a transport protocol,
we define a marker interface JsonSerializable
for types of messages
that are exchanged via the remote mechanism
and register it for automatic conversion.
Remote echos #
We now extend the echo application to work with client and server
running in separate processes possibly on different computers.
The first step is to add a new type of message to the EchoClient
.
public static class SendRemote implements Request {
public final String text;
public final String path;
public SendRemote(String text, String path) {
this.text = text;
this.path = path;
}
}
This message wraps a text to be sent and a path to the remote echo server. Actors can be accessed via their path which is a URL with the following format.
akka://systemname@hostname:port/actorpath
The actorpath
is nested according to the hierarchical structure of the actor system.
The root of the actor system has the path /user
because there is also another root /system
used internally by Akka.
The SendRemote
message is handled as follows.
private EchoClient receive(SendRemote msg) {
final int timeout = 10;
final ActorContext<Request> ctx = getContext();
ctx.getSystem().classicSystem().actorSelection(msg.path) //
.resolveOne(Duration.ofSeconds(timeout)) //
.thenApply(this::toTyped) //
.thenAccept(server -> {
ctx.getSelf().tell(new Send(msg.text, server));
});
return this;
}
We use ActorSelection.resolveOne
to asynchronously retrieve an actor reference for the remote server
which we then use inside a Send
message to the client itself.
The methods classicSystem
and toTyped
are used
to convert between the typed und untyped API
because actor selection is only available in the untyped version.
Implementing a remote echo server #
The class RemoteEchoServer
can be run
to start an EchoServer
that is available for remote access.
public class RemoteEchoServer {
public static final String HOST = "127.0.0.1";
public static final int PORT = 25520;
public static final String NAME = "echo-server";
public static final String PATH =
"akka://" + NAME + "@" + HOST + ":" + PORT + "/user";
private static final BufferedReader STDIN =
new BufferedReader(new InputStreamReader(System.in));
public static void main(String[] args) {
ActorSystem<EchoServer.Request> echoServer =
ActorSystem.create(EchoServer.create(), NAME, remoteConf());
System.out.println("Type 'quit' to exit");
try (Stream<String> lines = STDIN.lines()) {
lines.filter("quit"::equals).findFirst();
} finally {
echoServer.terminate();
}
}
private static Config remoteConf() {
Map<String, Object> overrides = new HashMap<>();
overrides.put("akka.remote.artery.canonical.hostname", HOST);
overrides.put("akka.remote.artery.canonical.port", PORT);
return ConfigFactory
.parseMap(overrides)
.withFallback(ConfigFactory.load());
}
}
We override the default hostname and port with known values
and pass the resulting configuration to ActorSystem.create
.
The program terminates when quit
is read from standard input.
Task: Implement a remote echo client #
Complete the definition of RemoteEchoClient
which can be used as a counterpart for RemoteEchoServer
.
When started, the application should start an actor
with the behavior EchoClient
as root of the actor system.
Note that it is not necessary to override the default configuration
because echo clients do not need to be discoverable.
The application should read lines from standard input,
send them to the started EchoClient
actor via the SendRemote
message
using the path of the RemoteEchoServer
,
and exit when the line contains quit
.
Start RemoteEchoServer
and RemoteEchoClient
to test if messages are sent back to the client.