我正在开发一个应用程序,该应用程序创建一些Akka actor来管理和处理来自Kafka主题的消息。具有相同密钥的消息由同一参与者处理。我还使用消息键来命名相应的演员。
从该主题中读取新消息时,我不知道ID等于消息密钥的actor是否已经由 actor系统创建。因此,我尝试使用其名称来解析actor,如果尚不存在,则创建它。我需要在参与者解决方面管理并发性。因此,有可能有多个客户询问 actor系统一个actor是否存在。
我现在正在使用的代码如下:
private CompletableFuture<ActorRef> getActor(String uuid) {
return system.actorSelection(String.format("/user/%s", uuid))
.resolveOne(Duration.ofMillis(1000))
.toCompletableFuture()
.exceptionally(ex ->
system.actorOf(Props.create(MyActor.class, uuid), uuid))
.exceptionally(ex -> {
try {
return system.actorSelection(String.format("/user/%s",uuid)).resolveOne(Duration.ofMillis(1000)).toCompletableFuture().get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
});
}
上面的代码没有经过优化,可以使异常处理更好。
但是,在Akka中,有一种更惯用的方式来解决演员,或者如果演员不存在则创建它吗?我想念什么吗?
答案 0 :(得分:4)
请考虑创建一个参与者,该参与者将消息ID到ActorRef
的映射作为其状态。该“接收者”参与者将处理所有请求以获得消息处理参与者。当接待员收到对演员的请求时(该请求将包含消息ID),它将尝试在其映射中查找关联的演员:如果找到了这样的演员,它将ActorRef
返回给发送者;否则,它将创建一个新的处理actor,将该actor添加到其映射中,然后将该actor引用返回给发送者。
答案 1 :(得分:1)
Jeffrey Chung的回答确实是Akka的方式。这种方法的缺点是性能低下。最有效的解决方案是使用Java的ConcurrentHashMap.computeIfAbsent()方法。
答案 2 :(得分:1)
我会考虑使用akka-cluster
和akka-cluster-sharding
。首先,这为您提供了吞吐量以及可靠性。但是,这也将使系统管理“实体”参与者的创建。
但是您必须改变与这些演员交谈的方式。您创建一个ShardRegion
演员来处理所有消息:
import akka.actor.AbstractActor;
import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import akka.actor.Props;
import akka.cluster.sharding.ClusterSharding;
import akka.cluster.sharding.ClusterShardingSettings;
import akka.cluster.sharding.ShardRegion;
import akka.event.Logging;
import akka.event.LoggingAdapter;
public class MyEventReceiver extends AbstractActor {
private final ActorRef shardRegion;
public static Props props() {
return Props.create(MyEventReceiver.class, MyEventReceiver::new);
}
static ShardRegion.MessageExtractor messageExtractor
= new ShardRegion.HashCodeMessageExtractor(100) {
// using the supplied hash code extractor to shard
// the actors based on the hashcode of the entityid
@Override
public String entityId(Object message) {
if (message instanceof EventInput) {
return ((EventInput) message).uuid().toString();
}
return null;
}
@Override
public Object entityMessage(Object message) {
if (message instanceof EventInput) {
return message;
}
return message; // I don't know why they do this it's in the sample
}
};
public MyEventReceiver() {
ActorSystem system = getContext().getSystem();
ClusterShardingSettings settings =
ClusterShardingSettings.create(system);
// this is setup for the money shot
shardRegion = ClusterSharding.get(system)
.start("EventShardingSytem",
Props.create(EventActor.class),
settings,
messageExtractor);
}
@Override
public Receive createReceive() {
return receiveBuilder().match(
EventInput.class,
e -> {
log.info("Got an event with UUID {} forwarding ... ",
e.uuid());
// the money shot
deviceRegion.tell(e, getSender());
}
).build();
}
}
因此,该Actor MyEventReceiver
在群集的所有节点上运行,并封装了shardRegion
Actor。您不再直接向EventActor
发送消息,而是使用MyEventReceiver
和deviceRegion
Actor,使用分片系统跟踪特定群集中的哪个节点 EventActor
继续存在。如果以前没有创建过消息,它将创建一个消息;如果已经创建过消息,则将路由它。每个EventActor
必须有一个唯一的ID:它是从 message 中提取的(因此UUID
对此非常有用,但也可以是其他ID,例如customerID ,或orderID或其他任何形式,只要它对于您要用来处理它的Actor实例而言是唯一的即可。
(我省略了EventActor
代码,否则它是一个非常普通的Actor,具体取决于您使用的代码,上面的代码中是“魔术”)。
分片系统会根据您选择的算法自动知道创建EventActor
并将其分配给分片(在这种情况下,它基于唯一ID的hashCode
,这是我用过的全部)。此外,对于任何给定的唯一ID,您仅保证一个演员。消息被透明地路由到正确的节点和碎片,无论它在哪里;从哪个Node和Shard发送。
Akka网站和文档中有更多信息和示例代码。
这是确保相同的Entity / Actor始终处理针对它的消息的一种非常有效的方法。集群和分片会自动照顾到正确分配Actor以及故障转移之类的功能(如果Actor具有一堆严格的状态,则必须添加akka-persistence
才能进行钝化,补液和故障转移(必须恢复))。