我正在使用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时才发现问题。
非常感谢任何帮助,谢谢!
答案 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(),因此它可以正常工作。