从Actor

时间:2017-05-05 11:40:50

标签: java spring akka

我正在使用Akka(2.5.1)在Java(8)Spring Boot(1.5.2.RELEASE)应用程序中使用反应模式。它很顺利,但现在我不得不试图从演员那里运行CompletableFuture。为了模拟这个,我创建了一个非常简单的服务,返回一个CompletableFuture。但是,当我尝试将结果返回给调用控制器时,我收到有关死信的错误,并且没有返回任何响应。

我得到的错误是:

  

[INFO] [05/05/2017 13:12:25.650] [akka-spring-demo-akka.actor.default-dispatcher-5] [akka:// akka-spring-demo / deadLetters]消息[从Actor [akka:// akka-spring-demo / user / $ a#-1561144664]到Actor [akka:// akka-spring-demo / deadLetters]的java.lang.String]未送达。 [1]遇到死信。可以使用配置设置关闭或调整此日志记录' akka.log-dead-letters'和' akka.log-dead-letters-during-shutdown'。

这是我的代码。这是调用actor的控制器:

@Component
@Produces(MediaType.TEXT_PLAIN)
@Path("/")
public class AsyncController {
    @Autowired
    private ActorSystem system;

    private ActorRef getGreetingActorRef() {
        ActorRef greeter = system.actorOf(SPRING_EXTENSION_PROVIDER.get(system)
                  .props("greetingActor"));

        return greeter;
    }

    @GET
    @Path("/foo")
    public void test(@Suspended AsyncResponse asyncResponse, @QueryParam("echo") String echo) {
        ask(getGreetingActorRef(), new Greet(echo), 1000)
            .thenApply((greet) -> asyncResponse.resume(Response.ok(greet).build()));
    }
}

这是服务:

@Component
public class GreetingService {
    public CompletableFuture<String> greetAsync(String name) {
        return CompletableFuture.supplyAsync(() -> "Hello, " + name);
    }
}

然后这是接到电话的演员。起初我有这个:

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class GreetingActor extends AbstractActor {
    @Autowired
    private GreetingService greetingService;

    @Autowired
    private ActorSystem system;

    @Override
    public Receive createReceive() {
        return receiveBuilder()
                .match(Greet.class, this::onGreet)
                .build();
    }

    private void onGreet(Greet greet) {
        greetingService.greetAsync(greet.getMessage())
            .thenAccept((greetingResponse) -> getSender().tell(greetingResponse, getSelf()));
    }

}

这导致正确处理了2个调用,但之后我会收到死信错误。然后我在这里读到可能导致我的问题的原因: http://doc.akka.io/docs/akka/2.5.1/java/actors.html

  

警告   当使用将来的回调时,你需要小心避免关闭包含actor的引用,即不要在回调中调用方法或访问封闭actor上的可变状态。这将破坏actor封装并可能引入同步错误和竞争条件,因为回调将同时调度到封闭的actor。遗憾的是,还没有办法在编译时检测这些非法​​访问。另请参见:Actors和共享可变状态

所以我认为你的想法是将结果传递给self(),之后你可以执行getSender()。tell(response,getSelf())。

所以我将代码改为:

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class GreetingActor extends AbstractActor {
    @Autowired
    private GreetingService greetingService;

    @Autowired
    private ActorSystem system;

    @Override
    public Receive createReceive() {
        return receiveBuilder()
                .match(Greet.class, this::onGreet)
                .match(String.class, this::onGreetingCompleted)
                .build();
    }

    private void onGreet(Greet greet) {
        pipe(greetingService.greetAsync(greet.getMessage()), system.dispatcher()).to(getSelf());
    }

    private void onGreetingCompleted(String greetingResponse) {
        getSender().tell(greetingResponse, getSelf());
    }

}

使用来自GreetingService的响应调用onGreetingCompleted方法,但此时我再次收到死信错误,因此由于某种原因它无法将响应发送回调用控制器。

请注意,如果我将服务更改为:

@Component
public class GreetingService {
    public String greet(String name) {
        return "Hello, " + name;
    }
}

演员中的onGreet:

private void onGreet(Greet greet) {
    getSender().tell(greetingService.greet(greet.getMessage()), getSelf());
}

然后一切正常。所以看起来我的基本Java / Spring / Akka设置正确,只是在尝试从我的演员调用CompletableFuture时才发现问题。

非常感谢任何帮助,谢谢!

1 个答案:

答案 0 :(得分:2)

getSender方法只能在消息的同步处理过程中可靠地返回发件人的引用

在你的第一个案例中,你有:

 greetingService.greetAsync(greet.getMessage())
        .thenAccept((greetingResponse) -> getSender().tell(greetingResponse, getSelf()));

这意味着一旦将来完成,getSender()将被调用async。不再可靠了。您可以将其更改为:

 ActorRef sender = getSender();
 greetingService.greetAsync(greet.getMessage())
        .thenAccept((greetingResponse) -> sender.tell(greetingResponse, getSelf()));

在你的第二个例子中,你有

pipe(greetingService.greetAsync(greet.getMessage()), system.dispatcher()).to(getSelf());

您正在将响应传递给“getSelf()”,即您的工作者。原始发件人永远不会得到任何东西(因此问题到期)。您可以将其修复为:

pipe(greetingService.greetAsync(greet.getMessage()), system.dispatcher()).to(getSender());

在第三种情况下,您在处理消息期间同步执行getSender(),因此它可以正常工作。