简体   繁体   English

检索Akka actor或如果它不存在则创建它

[英]Retrieve an Akka actor or create it if it does not exist

I am developing an application that creates some Akka actors to manage and process messages coming from a Kafka topic. 我正在开发一个应用程序,它创建一些Akka actor来管理和处理来自Kafka主题的消息。 Messages with the same key are processed by the same actor. 具有相同键的消息由同一个actor处理。 I use the message key also to name the corresponding actor. 我也使用消息键来命名相应的actor。

When a new message is read from the topic, I don't know if the actor with the id equal to the message key was already created by the actor system or not. 当从主题中读取新消息时,我不知道具有等于消息密钥的id的actor是否已经由actor系统创建。 Therefore, I try to resolve the actor using its name, and if it does not exist yet, I create it. 因此,我尝试使用其名称解析actor,如果它还不存在,我创建它。 I need to manage concurrency in regard to actor resolution. 我需要管理关于actor解析的并发性。 So it is possible that more than one client asks the actor system if an actor exists. 因此,如果存在actor ,则可能有多个客户端询问actor系统

The code I am using right now is the following: 我现在使用的代码如下:

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);
                     }
                 });
}

The above code is not optimised, and the exception handling can be made better. 上述代码未经过优化,可以更好地处理异常。

However, is there in Akka a more idiomatic way to resolve an actor, or to create it if it does not exist? 但是,在Akka中是否有一种更惯用的解决方法,或者如果它不存在则创建它? Am I missing something? 我错过了什么吗?

Consider creating an actor that maintains as its state a map of message IDs to ActorRef s. 考虑创建一个actor,它将状态保存为ActorRef的消息ID映射。 This "receptionist" actor would handle all requests to obtain a message processing actor. 这个“接待员”演员将处理获得消息处理演员的所有请求。 When the receptionist receives a request for an actor (the request would include the message ID), it tries to look up an associated actor in its map: if such an actor is found, it returns the ActorRef to the sender; 当接待员收到对演员的请求(请求将包括消息ID)时,它会尝试在其地图中查找关联的演员:如果找到这样的演员,则将ActorRef返回给发件人; otherwise it creates a new processing actor, adds that actor to its map, and returns that actor reference to the sender. 否则它会创建一个新的处理actor,将该actor添加到其地图中,并将该actor引用返回给发送者。

The answer by Jeffrey Chung is indeed of Akka way. 杰弗里钟的回答确实是阿卡的方式。 The downside of such approach is its low performance. 这种方法的缺点是性能低下。 The most performant solution is to use Java's ConcurrentHashMap.computeIfAbsent() method. 性能最佳的解决方案是使用Java的ConcurrentHashMap.computeIfAbsent()方法。

I would consider using akka-cluster and akka-cluster-sharding . 我会考虑使用akka-clusterakka-cluster-sharding First, this gives you throughput , and as well, reliability. 首先,这为您提供了吞吐量以及可靠性。 However, it will also make the system manage the creation of the 'entity' actors. 但是,它还将使系统管理“实体”角色的创建。

But you have to change the way you talk to those actors. 但你必须改变与那些演员交谈的方式。 You create a ShardRegion actor which handles all the messages: 您创建一个处理所有消息的ShardRegion actor:

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();
    }
}

So this Actor MyEventReceiver runs on all nodes of your cluster, and encapsulates the shardRegion Actor. 所以这个Actor MyEventReceiver在你的集群的所有节点上运行,并封装了shardRegion Actor。 You no longer message your EventActor s directly, but, using the MyEventReceiver and deviceRegion Actors, you use the sharding system keep track of which node in the cluster the particular EventActor lives on. 你不再是你的消息EventActor小号直接,但是,使用MyEventReceiverdeviceRegion演员,你可以使用分片系统跟踪哪个节点集群中的特定 EventActor住上。 It will create one if none have been created before, or route it messages if it has. 如果之前没有创建过,它将创建一个,或者如果有消息则将其路由。 Every EventActor must have a unique id: which is extracted from the message (so a UUID is pretty good for that, but it could be some other id, like a customerID, or an orderID, or whatever, as long as its unique for the Actor instance you want to process it with). 每个EventActor必须有一个唯一的id:它是从消息中提取的(所以UUID非常适合它,但它可能是一些其他的id,比如customerID,orderID,或者其他什么,只要它的唯一性为你想用它来处理它的Actor实例。

(I'm omitting the EventActor code, it's otherwise a pretty normal Actor, depending what you are doing with it, the 'magic' is in the code above). (我省略了EventActor代码,否则它是一个非常普通的Actor,取决于你正在做什么,'魔术'在上面的代码中)。

The sharding system automatically knows to create the EventActor and allocate it to a shard, based on the algorithm you've chosen (in this particular case, it's based on the hashCode of the unique ID, which is all I've ever used). 分片系统自动知道创建EventActor并根据您选择的算法将其分配到分片(在这种特殊情况下,它基于唯一ID的hashCode ,这是我曾经使用过的)。 Furthermore, you're guaranteed only one Actor for any given unique ID. 此外,对于任何给定的唯一ID,您只能保证 一个 Actor。 The message is transparently routed to the correct Node and Shard wherever it is; 消息透明地路由到正确的节点和碎片,无论它在哪里; from whichever Node and Shard it's being sent. 从发送的Node和Shard中选择。

There's more info and sample code in the Akka site & documentation. Akka网站和文档中有更多信息和示例代码。

This is a pretty rad way to make sure that the same Entity/Actor always processes messages meant for it. 这是一种非常好的方法,可以确保相同的实体/ Actor始终处理对其有意义的消息。 The cluster and sharding takes automatic care of distributing the Actors properly, and failover and the like (you would have to add akka-persistence to get passivation, rehydration, and failover if the Actor has a bunch of strict state associated with it (that must be restored)). 集群和分片需要自动处理正确分配Actors和故障转移等(如果Actor有一堆与之关联的严格状态,你必须添加akka-persistence以获得钝化,补充和故障转移(必须被恢复))。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM