[翻译]Play2 整合akka

来源:互联网 发布:ssm项目源码 编辑:程序博客网 时间:2024/06/10 20:03

源https://www.playframework.com/documentation/2.5.x/JavaAkka#Scheduling-asynchronous-tasks
Akka使用Actor Model提供抽象层,提供更好的平台以建立正确的多线程和可扩展应用。容错方面,采用‘Let it crash‘模型,此模型已在通信方面获得了很成功的应用,主要用于构建永不停工的自健康系统。Actors为传输分布提供了抽象,为可扩展和容错应用提供了基础。

The application actor system

Akka能够与几个actor system容器一起工作。一个actor system管理资源,这部分资源配置用于运行系统包含的actors。

Play application(就是正常运行的Play实例,以后我们都不再进行翻译)定义了和使用一个特殊的actor system(也许是叫做演员系统或者别的什么,这个我们也不做翻译,又感兴趣的可以查阅相关资料)。这个actor system与application有着相同的生命周期,随application重启而自动重启。

Writing actors

想要使用Akka,你首先需要写一个actor。下面是一个简单的actor,无论谁询问它,它都会简单地回答hello。

package actors;import akka.actor.*;import actors.HelloActorProtocol.*;public class HelloActor extends UntypedActor {    public static Props props = Props.create(HelloActor.class);    public void onReceive(Object msg) throws Exception {        if (msg instanceof SayHello) {            sender().tell("Hello, " + ((SayHello) msg).name, self());        }    }}

注意,这里HelloActor定义了一个静态域props,它返回一个Props对象,此对象描述怎样创建一个actor。这是一个好的Akka惯例,将实例化的逻辑从创建actor的代码中分离出来。

这里展示的另一个好点是HelloActor发送和接受的信息被定为另一个叫做HelloActorProtocol的类的静态内部类。

package actors;public class HelloActorProtocol {    public static class SayHello {        public final String name;        public SayHello(String name) {            this.name = name;        }    }}

创建和使用actors

要想创建(和/或)使用actor,你需要一个ActorSystem(这类专有名词我们也不做翻译)。可以通过声明一个对ActorSystem的依赖获取ActorSystem,然后,使用actorOf方法创建新actor。

你可以用actor做的最基本的事情是发送一条消息。当给向ctor发送消息时,消息被消费和遗忘,actor不作应答。这就是所谓的tell(这个也不作翻译)模式。

然而,在web apllication中,tell模式一般来说没啥用,因为HTTP是包含请求和应答的协议。在这种情况下,我们更倾向于使用ask模式。ask模式返回一个Scala Future,你可以使用使用scala.compat.java8.FutureConverts.toJava方法将Scala Future转化为Java CompletionStage,然后映射到你自己的结果类型。

下面是用用ask模式使用我们的HelloActor一个例子:

import akka.actor.*;import play.mvc.*;import scala.compat.java8.FutureConverters;import javax.inject.*;import java.util.concurrent.CompletionStage;import static akka.pattern.Patterns.ask;@Singletonpublic class Application extends Controller {    final ActorRef helloActor;    @Inject public Application(ActorSystem system) {        helloActor = system.actorOf(HelloActor.props);    }    public CompletionStage<Result> sayHello(String name) {        return FutureConverters.toJava(ask(helloActor, new SayHello(name), 1000))                .thenApply(response -> ok((String) response));    }}

有几点需要注意:

ask模式需要引入(import),静态引入ask方法通常是最方便的。
返回的future转化为CompletionStage。resulting promise是一个CompletionStage,因此当想要获取这个值的时候,你需要将它转化为actor的实际返回类型。
ask模式需要一个timeout(时间限制,以后不做翻译),我们使用1000毫秒。如果actor应答超时,retuened promise会抛出timeout error。
由于我们在构造器中创建actor,我们需要将我们的controller限定为单例(Singleton),防止每次使用这个controller都会创建新的actor。

依赖注入actors

有些人可能更喜欢用Guice实例化actors,然后在cotrollers和组件(components)中给actors绑定具体的actor refs(对象引用,不作翻译)。

例如,如果你想要一个Play配置的actor,你可以这么做:

import akka.actor.UntypedActor;import play.Configuration;import javax.inject.Inject;public class ConfiguredActor extends UntypedActor {    private Configuration configuration;    @Inject    public ConfiguredActor(Configuration configuration) {        this.configuration = configuration;    }    @Override    public void onReceive(Object message) throws Exception {        if (message instanceof ConfiguredActorProtocol.GetConfig) {            sender().tell(configuration.getString("my.config"), self());        }    }}

Play provides some helpers to help providing actor bindings. These allow the actor itself to be dependency injected, and allows the actor ref for the actor to be injected into other components. To bind an actor using these helpers, create a module as described in the dependency injection documentation, then mix in the AkkaGuiceSupport interface and use the bindActor method to bind the actor:
Play提供一些帮助来提供actor绑定。这些允许actor自己作为依赖被注入,并且允许将此actor的actor ref(引用,不作翻译)注入进其他components。想要使用这些helpers绑定actor,首先根据依赖注入文档的描述创建模块(module),然后混合AkkaGuiceSupport接口,使用bindActor方法来绑定actor。

import com.google.inject.AbstractModule;import play.libs.akka.AkkaGuiceSupport;public class MyModule extends AbstractModule implements AkkaGuiceSupport {    @Override    protected void configure() {        bindActor(ConfiguredActor.class, "configured-actor");    }}

This actor will both be named configured-actor, and will also be qualified with the configured-actor name for injection. You can now depend on the actor in your controllers and other components:
这个actor既会被命名为configured-actor,又需要根据configured-actor的名称被注入(一种注入方式具体可以参考Guice依赖注入的相关内容)。现在你可以在你的controllers和其他components中依赖这个actor了:

import akka.actor.ActorRef;import play.mvc.*;import scala.compat.java8.FutureConverters;import javax.inject.Inject;import javax.inject.Named;import java.util.concurrent.CompletionStage;import static akka.pattern.Patterns.ask;public class Application extends Controller {    private ActorRef configuredActor;    @Inject    public Application(@Named("configured-actor") ActorRef configuredActor) {       this.configuredActor = configuredActor;    }    public CompletionStage<Result> getConfig() {        return FutureConverters.toJava(ask(configuredActor,                        new ConfiguredActorProtocol.GetConfig(), 1000)        ).thenApply(response -> ok((String) response));    }}

依赖注入child(子,后面不作翻译) actors

以上有利于注入root(根) actors,但是有很多你创建的actors是child actors,这些actors并不会与Play app保持相同的生命周期(lifecycle),并且可能还会有传递给它们附加状态。

为了有助于依赖注入child actors,Play利用了Guice的AssistedInject surpport(支持)。

假如你有下面的actor,它依赖注入的配置,加一个key:

import akka.actor.UntypedActor;import com.google.inject.assistedinject.Assisted;import play.Configuration;import javax.inject.Inject;public class ConfiguredChildActor extends UntypedActor {    private final Configuration configuration;    private final String key;    @Inject    public ConfiguredChildActor(Configuration configuration, @Assisted String key) {        this.configuration = configuration;        this.key = key;    }    @Override    public void onReceive(Object message) throws Exception {        if (message instanceof ConfiguredChildActorProtocol.GetConfig) {            sender().tell(configuration.getString(key), self());        }    }}

在这种情况下,我们已经使用了构造器诸如-Guice的 assisted inject support只是兼容构造器注入。由于key参数会在创建时提供,不是通过容器,我们使用@Assisted注解它。

现在,在针对child的协议中,我们定义一个Factory接口,它接收key,返回Actor:

import akka.actor.Actor;public class ConfiguredChildActorProtocol {    public static class GetConfig {}    public interface Factory {        public Actor create(String key);    }}

我们不会实现它(Factory),Guice会提供一个实现。这个实现不仅要传递我们的key参数,还要确定Configuration依赖并且注入它(Configuration)。因为这个特点(trait,也就是Factory)只是返回一个Actor,当测试这个actor的时候,我们可以注入返回任何actor的因子(factor),例如我们可以注入mocked child actor而不是一个真是actor。

Now, the actor that depends on this can extend InjectedActorSupport, and it can depend on the factory we created:
现在,依赖这个(Factory)的actor可以实现InjectedActorSupport,并且它依赖于我们创建的工厂。

import akka.actor.ActorRef;import akka.actor.UntypedActor;import play.libs.akka.InjectedActorSupport;import javax.inject.Inject;public class ParentActor extends UntypedActor implements InjectedActorSupport {    private ConfiguredChildActorProtocol.Factory childFactory;    @Inject    public ParentActor(ConfiguredChildActorProtocol.Factory childFactory) {        this.childFactory = childFactory;    }    @Override    public void onReceive(Object message) throws Exception {        if (message instanceof ParentActorProtocol.GetChild) {            String key = ((ParentActorProtocol.GetChild) message).key;            ActorRef child = injectedChild(() -> childFactory.create(key), key);            sender().tell(child, self());        }    }}

它使用injectedChild去纯碱并且获取一个child actor的引用,传递key。第二个参数(本例中是key)会被用作child actor的名称。

最后,我们需要绑定我们的actors。在我们的module中,我们使用bindActorFactory方法绑定parenta ctor,并且也将achild factory绑定到child implementation:

import com.google.inject.AbstractModule;import play.libs.akka.AkkaGuiceSupport;public class MyModule extends AbstractModule implements AkkaGuiceSupport {    @Override    protected void configure() {        bindActor(ParentActor.class, "parent-actor");        bindActorFactory(ConfiguredChildActor.class,            ConfiguredChildActorProtocol.Factory.class);    }}

本例中使用Guice自动绑定一个ConfiguredChildActorProtocol.Factory实例,这个实例在ConfiguredChildActor实例化的时候提供一个Configuration实例。

Configuration

默认的actor system configuration从Play application配置文件中读取。例如,配置application actor system默认的dispatcher(派遣?),将下面几行添加到conf/application.conf文件:

akka.actor.default-dispatcher.fork-join-executor.pool-size-max = 64akka.actor.debug.receive = on

关于Akka日志配置,看配置日志。

修改配置前缀

如果你想要针对另一个Akka actor system使用akka.*开头的settings(设置,以后不作翻译),你可以告诉Play从另一个位置加载Akka settings。

play.akka.config = "my-akka"

现在从my-akka前缀读取settings,而不是从akka前缀。

my-akka.actor.default-dispatcher.fork-join-executor.pool-size-max = 64my-akka.actor.debug.receive = on

内建actor system名称

默认情况下,Play actor system的名称是application。你可以通过conf/application.conf中的入口改变它:

play.akka.actor-system = "custom-name"

注意:这个特性是在你想要把你的play application ActorSystem加入akka集群(cluster)时非常有用。

异步执行阻塞代码

Akka的常用情况是在不使用额外Actor的情况下并发执行计算。如果你因为并行执行某个并行计算的特殊原因正在创建Actors pool(池),有一个非常简单(并且快速)的方法:

import play.mvc.*;import java.util.concurrent.CompletableFuture;import java.util.concurrent.CompletionStage;public class Application extends Controller {    public CompletionStage<Result> index() {        return CompletableFuture.supplyAsync(this::longComputation)                .thenApply((Integer i) -> ok("Got " + i));    }}

Scheduling asynchronous tasks
You can schedule sending messages to actors and executing tasks (functions or Runnable instances). You will get a Cancellable back that you can call cancel on to cancel the execution of the scheduled operation.

调度异步任务

你可以调度发送信息到actors并且执行任务(方法或者Runnable实例)。你会获得一个Cancellable(可取消)反馈,这个反馈可以调用cancel取消调度操作的执行。

例如,每30分钟发送消息给testActor。

system.scheduler().schedule(    Duration.create(0, TimeUnit.MILLISECONDS), //Initial delay 0 milliseconds    Duration.create(30, TimeUnit.MINUTES),     //Frequency 30 minutes    testActor,    "tick",    system.dispatcher(),    null);

或者,从现在开始10ms后运行代码块:

system.scheduler().scheduleOnce(    Duration.create(10, TimeUnit.MILLISECONDS),    () -> file.delete(),    system.dispatcher());