检索Akka演员或创建它(如果不存在)

时间:2019-03-18 20:15:51

标签: java scala akka actor

我正在开发一个应用程序,该应用程序创建一些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中,有一种更惯用的方式来解决演员,或者如果演员不存在则创建它吗?我想念什么吗?

3 个答案:

答案 0 :(得分:4)

请考虑创建一个参与者,该参与者将消息ID到ActorRef的映射作为其状态。该“接收者”参与者将处理所有请求以获得消息处理参与者。当接待员收到对演员的请求时(该请求将包含消息ID),它将尝试在其映射中查找关联的演员:如果找到了这样的演员,它将ActorRef返回给发送者;否则,它将创建一个新的处理actor,将该actor添加到其映射中,然后将该actor引用返回给发送者。

答案 1 :(得分:1)

Jeffrey Chung的回答确实是Akka的方式。这种方法的缺点是性能低下。最有效的解决方案是使用Java的ConcurrentHashMap.computeIfAbsent()方法。

答案 2 :(得分:1)

我会考虑使用akka-clusterakka-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发送消息,而是使用MyEventReceiverdeviceRegion 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才能进行钝化,补液和故障转移(必须恢复))。