Reviewed and improved remote-actors doc

This commit is contained in:
Patrik Nordwall 2011-04-29 17:09:22 +02:00
parent 888af3479e
commit 1c29885f3d
4 changed files with 268 additions and 247 deletions

View file

@ -1,6 +1,10 @@
Remote Actors (Java)
====================
.. sidebar:: Contents
.. contents:: :local:
Module stability: **SOLID**
Akka supports starting interacting with UntypedActors and TypedActors on remote nodes using a very efficient and scalable NIO implementation built upon `JBoss Netty <http://jboss.org/netty>`_ and `Google Protocol Buffers <http://code.google.com/p/protobuf/>`_ .
@ -142,12 +146,6 @@ The default behavior is that the remote client will maintain a transaction log o
If you choose a capacity higher than 0, then a bounded queue will be used and if the limit of the queue is reached then a 'RemoteClientMessageBufferException' will be thrown.
You can also get an Array with all the messages that the remote client has failed to send. Since the remote client events passes you an instance of the RemoteClient you have an easy way to act upon failure and do something with these messages (while waiting for them to be retried).
.. code-block:: java
Object[] pending = Actors.remote().pendingMessages();
Running Remote Server in untrusted mode
---------------------------------------
@ -253,21 +251,13 @@ You can also generate the secure cookie by using the 'Crypt' object and its 'gen
The secure cookie is a cryptographically secure randomly generated byte array turned into a SHA-1 hash.
Remote Actors
-------------
Akka has two types of remote actors:
* Client-initiated and managed. Here it is the client that creates the remote actor and "moves it" to the server.
* Server-initiated and managed. Here it is the server that creates the remote actor and the client can ask for a handle to this actor.
They are good for different use-cases. The client-initiated are great when you want to monitor an actor on another node since it allows you to link to it and supervise it using the regular supervision semantics. They also make RPC completely transparent. The server-initiated, on the other hand, are great when you have a service running on the server that you want clients to connect to, and you want full control over the actor on the server side for security reasons etc.
Client-managed Remote UntypedActor
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
----------------------------------
DEPRECATED AS OF 1.1
The client creates the remote actor and "moves it" to the server.
When you define an actors as being remote it is instantiated as on the remote host and your local actor becomes a proxy, it works as a handle to the remote actor. The real execution is always happening on the remote node.
Here is an example:
@ -291,26 +281,31 @@ An UntypedActor can also start remote child Actors through one of the “spawn/l
.. code-block:: java
...
getContext().spawnRemote(MyActor.class, hostname, port);
getContext().spawnRemote(MyActor.class, hostname, port, timeoutInMsForFutures);
getContext().spawnLinkRemote(MyActor.class, hostname, port, timeoutInMsForFutures);
...
Server-managed Remote UntypedActor
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
----------------------------------
Here it is the server that creates the remote actor and the client can ask for a handle to this actor.
Server side setup
*****************
^^^^^^^^^^^^^^^^^
The API for server managed remote actors is really simple. 2 methods only:
.. code-block:: java
import akka.actor.Actors;
import akka.actor.UntypedActor;
class MyActor extends UntypedActor {
public void onReceive(Object message) throws Exception {
...
}
}
Actors.remote().start("localhost", 2552).register("hello-service", Actors.actorOf(HelloWorldActor.class);
Actors.remote().start("localhost", 2552).register("hello-service", Actors.actorOf(HelloWorldActor.class));
Actors created like this are automatically started.
@ -322,88 +317,6 @@ You can also register an actor by its UUID rather than ID or handle. This is don
server.unregister("uuid:" + actor.uuid);
Client side usage
*****************
.. code-block:: java
ActorRef actor = Actors.remote().actorFor("hello-service", "localhost", 2552);
actor.sendOneWay("Hello");
There are many variations on the 'remote()#actorFor' method. Here are some of them:
.. code-block:: java
... = actorFor(className, hostname, port);
... = actorFor(className, timeout, hostname, port);
... = actorFor(uuid, className, hostname, port);
... = actorFor(uuid, className, timeout, hostname, port);
... // etc
All of these also have variations where you can pass in an explicit 'ClassLoader' which can be used when deserializing messages sent from the remote actor.
Client-managed Remote TypedActor
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
DEPRECATED AS OF 1.1
Remote Typed Actors are created through the 'TypedActor.newRemoteInstance' factory method.
.. code-block:: java
MyPOJO remoteActor = (MyPOJO)TypedActor.newRemoteInstance(MyPOJO.class, MyPOJOImpl.class, , "localhost", 2552);
And if you want to specify the timeout:
.. code-block:: java
MyPOJO remoteActor = (MyPOJO)TypedActor.newRemoteInstance(MyPOJO.class, MyPOJOImpl.class, timeout, "localhost", 2552);
You can also define the Typed Actor to be a client-managed-remote service by adding the RemoteAddress configuration element in the declarative supervisor configuration:
.. code-block:: java
new Component(
Foo.class,
FooImpl.class,
new LifeCycle(new Permanent(), 1000),
1000,
new RemoteAddress("localhost", 2552))
Server-managed Remote TypedActor
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
WARNING: Remote TypedActors do not work with overloaded methods on your TypedActor, refrain from using overloading.
Server side setup
*****************
The API for server managed remote typed actors is nearly the same as for untyped actor:
.. code-block:: java
import static akka.actor.Actors.*;
remote().start("localhost", 2552);
RegistrationService typedActor = TypedActor.newInstance(RegistrationService.class, RegistrationServiceImpl.class, 2000);
remote().registerTypedActor("user-service", typedActor);
Client side usage
.. code-block:: java
import static akka.actor.Actors.*;
RegistrationService actor = remote().typedActorFor(RegistrationService.class, "user-service", 5000L, "localhost", 2552);
actor.registerUser(...);
There are variations on the 'remote()#typedActorFor' method. Here are some of them:
.. code-block:: java
... = typedActorFor(interfaceClazz, serviceIdOrClassName, hostname, port);
... = typedActorFor(interfaceClazz, serviceIdOrClassName, timeout, hostname, port);
... = typedActorFor(interfaceClazz, serviceIdOrClassName, timeout, hostname, port, classLoader);
Session bound server side setup
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -414,17 +327,19 @@ Session bound actors are useful if you need to keep state per session, e.g. user
.. code-block:: java
import static akka.actor.Actors.*;
import akka.japi.Creator;
class HelloWorldActor extends Actor {
...
}
remote().start("localhost", 2552);
remote().registerPerSession("hello-service", new Creator[ActorRef]() {
remote().registerPerSession("hello-service", new Creator<ActorRef>() {
public ActorRef create() {
return actorOf(HelloWorldActor.class);
}
})
});
Note that the second argument in registerPerSession is a Creator, it means that the create method will create a new ActorRef each invocation.
It will be called to create an actor every time a session is established.
@ -443,19 +358,22 @@ There are many variations on the 'remote()#actorFor' method. Here are some of th
.. code-block:: java
... = actorFor(className, hostname, port);
... = actorFor(className, timeout, hostname, port);
... = actorFor(uuid, className, hostname, port);
... = actorFor(uuid, className, timeout, hostname, port);
... = remote().actorFor(className, hostname, port);
... = remote().actorFor(className, timeout, hostname, port);
... = remote().actorFor(uuid, className, hostname, port);
... = remote().actorFor(uuid, className, timeout, hostname, port);
... // etc
Automatic remote 'sender' reference management
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
All of these also have variations where you can pass in an explicit 'ClassLoader' which can be used when deserializing messages sent from the remote actor.
Akka is automatically remote-enabling the sender Actor reference for you in order to allow the receiver to respond to the message using 'getContext().getSender().sendOneWay(msg);' or 'getContext().reply(msg);'. By default it is registering the sender reference in the remote server with the 'hostname' and 'port' from the akka.conf configuration file. The default is "localhost" and 2552 and if there is no remote server with this hostname and port then it creates and starts it.
Automatic remote 'sender' reference management
----------------------------------------------
The sender of a remote message will be reachable with a reply through the remote server on the node that the actor is residing, automatically.
Please note that firewalled clients won't work right now. [2011-01-05]
Identifying remote actors
^^^^^^^^^^^^^^^^^^^^^^^^^
-------------------------
The 'id' field in the 'Actor' class is of importance since it is used as identifier for the remote actor. If you want to create a brand new actor every time you instantiate a remote actor then you have to set the 'id' field to a unique 'String' for each instance. If you want to reuse the same remote actor instance for each new remote actor (of the same class) you create then you don't have to do anything since the 'id' field by default is equal to the name of the actor class.
@ -463,18 +381,83 @@ Here is an example of overriding the 'id' field:
.. code-block:: java
import akka.util.UUID;
import akka.actor.UntypedActor;
import com.eaio.uuid.UUID;
class MyActor extends UntypedActor {
public MyActor() {
getContext().setId(UUID.newUuid().toString());
getContext().setId(new UUID().toString());
}
public void onReceive(Object message) throws Exception {
...
// ...
}
}
Client-managed Remote Typed Actors
----------------------------------
DEPRECATED AS OF 1.1
Remote Typed Actors are created through the 'TypedActor.newRemoteInstance' factory method.
.. code-block:: java
MyPOJO remoteActor = (MyPOJO) TypedActor.newRemoteInstance(MyPOJO.class, MyPOJOImpl.class, "localhost", 2552);
And if you want to specify the timeout:
.. code-block:: java
MyPOJO remoteActor = (MyPOJO)TypedActor.newRemoteInstance(MyPOJO.class, MyPOJOImpl.class, timeout, "localhost", 2552);
You can also define the Typed Actor to be a client-managed-remote service by adding the RemoteAddress configuration element in the declarative supervisor configuration:
.. code-block:: java
new Component(
Foo.class,
FooImpl.class,
new LifeCycle(new Permanent(), 1000),
1000,
new RemoteAddress("localhost", 2552))
Server-managed Remote Typed Actors
----------------------------------
WARNING: Remote TypedActors do not work with overloaded methods on your TypedActor, refrain from using overloading.
Server side setup
^^^^^^^^^^^^^^^^^
The API for server managed remote typed actors is nearly the same as for untyped actor:
.. code-block:: java
import static akka.actor.Actors.*;
remote().start("localhost", 2552);
RegistrationService typedActor = TypedActor.newInstance(RegistrationService.class, RegistrationServiceImpl.class, 2000);
remote().registerTypedActor("user-service", typedActor);
Client side usage
^^^^^^^^^^^^^^^^^
.. code-block:: java
import static akka.actor.Actors.*;
RegistrationService actor = remote().typedActorFor(RegistrationService.class, "user-service", 5000L, "localhost", 2552);
actor.registerUser(...);
There are variations on the 'remote()#typedActorFor' method. Here are some of them:
.. code-block:: java
... = remote().typedActorFor(interfaceClazz, serviceIdOrClassName, hostname, port);
... = remote().typedActorFor(interfaceClazz, serviceIdOrClassName, timeout, hostname, port);
... = remote().typedActorFor(interfaceClazz, serviceIdOrClassName, timeout, hostname, port, classLoader);
Data Compression Configuration
------------------------------
@ -493,44 +476,55 @@ You can configure it like this:
}
}
Code provisioning
-----------------
Akka does currently not support automatic code provisioning but requires you to have the remote actor class files available on both the "client" the "server" nodes.
This is something that will be addressed soon. Until then, sorry for the inconvenience.
Subscribe to Remote Client events
---------------------------------
Akka has a subscription API for remote client events. You can register an Actor as a listener and this actor will have to be able to process these events:
RemoteClientError { Throwable cause; RemoteClientModule client; InetSocketAddress remoteAddress; }
RemoteClientDisconnected { RemoteClientModule client; InetSocketAddress remoteAddress; }
RemoteClientConnected { RemoteClientModule client; InetSocketAddress remoteAddress; }
RemoteClientStarted { RemoteClientModule client; InetSocketAddress remoteAddress; }
RemoteClientShutdown { RemoteClientModule client; InetSocketAddress remoteAddress; }
RemoteClientWriteFailed { Object message; Throwable cause; RemoteClientModule client; InetSocketAddress remoteAddress; }
.. code-block:: java
class RemoteClientError { Throwable cause; RemoteClientModule client; InetSocketAddress remoteAddress; }
class RemoteClientDisconnected { RemoteClientModule client; InetSocketAddress remoteAddress; }
class RemoteClientConnected { RemoteClientModule client; InetSocketAddress remoteAddress; }
class RemoteClientStarted { RemoteClientModule client; InetSocketAddress remoteAddress; }
class RemoteClientShutdown { RemoteClientModule client; InetSocketAddress remoteAddress; }
class RemoteClientWriteFailed { Object message; Throwable cause; RemoteClientModule client; InetSocketAddress remoteAddress; }
So a simple listener actor can look like this:
.. code-block:: java
import akka.actor.UntypedActor;
import akka.remoteinterface.*;
class Listener extends UntypedActor {
public void onReceive(Object message) throws Exception {
if (message instanceof RemoteClientError) {
RemoteClientError event = (RemoteClientError)message;
Exception cause = event.getCause();
...
RemoteClientError event = (RemoteClientError) message;
Throwable cause = event.getCause();
// ...
} else if (message instanceof RemoteClientConnected) {
RemoteClientConnected event = (RemoteClientConnected)message;
...
RemoteClientConnected event = (RemoteClientConnected) message;
// ...
} else if (message instanceof RemoteClientDisconnected) {
RemoteClientDisconnected event = (RemoteClientDisconnected)message;
...
RemoteClientDisconnected event = (RemoteClientDisconnected) message;
// ...
} else if (message instanceof RemoteClientStarted) {
RemoteClientStarted event = (RemoteClientStarted)message;
...
RemoteClientStarted event = (RemoteClientStarted) message;
// ...
} else if (message instanceof RemoteClientShutdown) {
RemoteClientShutdown event = (RemoteClientShutdown)message;
...
RemoteClientShutdown event = (RemoteClientShutdown) message;
// ...
} else if (message instanceof RemoteClientWriteFailed) {
RemoteClientWriteFailed event = (RemoteClientWriteFailed)message;
...
RemoteClientWriteFailed event = (RemoteClientWriteFailed) message;
// ...
}
}
}
@ -550,43 +544,45 @@ Subscribe to Remote Server events
Akka has a subscription API for the server events. You can register an Actor as a listener and this actor will have to be able to process these events:
RemoteServerStarted { RemoteServerModule server; }
RemoteServerShutdown { RemoteServerModule server; }
RemoteServerError { Throwable cause; RemoteServerModule server; }
RemoteServerClientConnected { RemoteServerModule server; Option<InetSocketAddress> clientAddress; }
RemoteServerClientDisconnected { RemoteServerModule server; Option<InetSocketAddress> clientAddress; }
RemoteServerClientClosed { RemoteServerModule server; Option<InetSocketAddress> clientAddress; }
RemoteServerWriteFailed { Object request; Throwable cause; RemoteServerModule server; Option<InetSocketAddress> clientAddress; }
.. code-block:: java
class RemoteServerStarted { RemoteServerModule server; }
class RemoteServerShutdown { RemoteServerModule server; }
class RemoteServerError { Throwable cause; RemoteServerModule server; }
class RemoteServerClientConnected { RemoteServerModule server; Option<InetSocketAddress> clientAddress; }
class RemoteServerClientDisconnected { RemoteServerModule server; Option<InetSocketAddress> clientAddress; }
class RemoteServerClientClosed { RemoteServerModule server; Option<InetSocketAddress> clientAddress; }
class RemoteServerWriteFailed { Object request; Throwable cause; RemoteServerModule server; Option<InetSocketAddress> clientAddress; }
So a simple listener actor can look like this:
.. code-block:: java
import akka.actor.UntypedActor;
import akka.remoteinterface.*;
class Listener extends UntypedActor {
public void onReceive(Object message) throws Exception {
if (message instanceof RemoteServerError) {
RemoteServerError event = (RemoteServerError)message;
Exception cause = event.getCause();
...
} else if (message instanceof RemoteServerStarted) {
RemoteServerStarted event = (RemoteServerStarted)message;
...
} else if (message instanceof RemoteServerShutdown) {
RemoteServerShutdown event = (RemoteServerShutdown)message;
...
} else if (message instanceof RemoteServerClientConnected) {
RemoteServerClientConnected event = (RemoteServerClientConnected)message;
...
} else if (message instanceof RemoteServerClientDisconnected) {
RemoteServerClientDisconnected event = (RemoteServerClientDisconnected)message;
...
} else if (message instanceof RemoteServerClientClosed) {
RemoteServerClientClosed event = (RemoteServerClientClosed)message;
...
} else if (message instanceof RemoteServerWriteFailed) {
RemoteServerWriteFailed event = (RemoteServerWriteFailed)message;
...
if (message instanceof RemoteClientError) {
RemoteClientError event = (RemoteClientError) message;
Throwable cause = event.getCause();
// ...
} else if (message instanceof RemoteClientConnected) {
RemoteClientConnected event = (RemoteClientConnected) message;
// ...
} else if (message instanceof RemoteClientDisconnected) {
RemoteClientDisconnected event = (RemoteClientDisconnected) message;
// ...
} else if (message instanceof RemoteClientStarted) {
RemoteClientStarted event = (RemoteClientStarted) message;
// ...
} else if (message instanceof RemoteClientShutdown) {
RemoteClientShutdown event = (RemoteClientShutdown) message;
// ...
} else if (message instanceof RemoteClientWriteFailed) {
RemoteClientWriteFailed event = (RemoteClientWriteFailed) message;
// ...
}
}
}
@ -608,10 +604,27 @@ Message Serialization
All messages that are sent to remote actors needs to be serialized to binary format to be able to travel over the wire to the remote node. This is done by letting your messages extend one of the traits in the 'akka.serialization.Serializable' object. If the messages don't implement any specific serialization trait then the runtime will try to use standard Java serialization.
Read more about that in the `Serialization section <serialization-java>`_.
Here is one example, but full documentation can be found in the :ref:`serialization-java`.
Code provisioning
-----------------
Protobuf
^^^^^^^^
Akka does currently not support automatic code provisioning but requires you to have the remote actor class files available on both the "client" the "server" nodes.
This is something that will be addressed soon. Until then, sorry for the inconvenience.
Protobuf message specification needs to be compiled with 'protoc' compiler.
::
message ProtobufPOJO {
required uint64 id = 1;
required string name = 2;
required bool status = 3;
}
Using the generated message builder to send the message to a remote actor:
.. code-block:: java
actor.sendOneWay(ProtobufPOJO.newBuilder()
.setId(11)
.setStatus(true)
.setName("Coltrane")
.build());