Actor简介(一)

       在开发初期,业务单一、系统简单,一台机器或许就能支撑,这个时候,单机上的任务大多直接调用API的方式就能完成。然而当业务需求日益增多,功能逐渐复杂化,我们就需要考虑把项目拆分成分布式环境,这时,多系统通信就需要通过网络实现。JDK内置的并发包在单机上游刃有余,在分布式环境中,就不是那么完美了。Akka通过Actor很好的解决了远程通信问题。

     Actor初识

        Akka中,Actor使用消息传递进行通信,完成任务。Actor编程,不同于之前通过对象调用完成某个功能,它采用“问答”式API进行通信,这种方式更加贴近日常生活,易于理解。另外,使用Actor,不是操作Actor对象,而是操作它的引用对象(ActorRef),在Akka中,ActorRef对Actor做了很好的封装,防止外界对Actor的状态进行修改。

       下面,来一个小示例认识一下actor:

        首先,我们创建一个maven project,并在pom.xml中引入Akka依赖:

<dependency>

    <groupId>com.typesafe.akka</groupId>

    <artifactId>akka-actor_2.11</artifactId>

    <version>2.5.16</version>

</dependency>

        定义Actor:

public class ActorDemo extends AbstractActor {

private final LoggingAdapter logger = Logging.getLogger(getContext().getSystem(), this);

@Override

public Receive createReceive() {

    return receiveBuilder().match(String.class, s -> {

        logger.info("小明:" + s);

      }).matchAny(other -> {

        logger.info("其它未知消息:" + other);

      }).build();

}


public static void main(String[] args) {

    ActorSystem system = ActorSystem.create("system");

    ActorRef demo = system.actorOf(Props.create(ActorDemo.class), "actorDemo");
    
    demo.tell("hello world",ActorRef.noSender());

    final Integer other=20;

    demo.tell(other,ActorRef.noSender());

  }

}

         执行结果:

[INFO] [09/22/2018 10:35:54.295] [system-akka.actor.default-dispatcher-4] [akka://system/user/actorDemo] 小明:hello world

[INFO] [09/22/2018 10:35:54.296] [system-akka.actor.default-dispatcher-4] [akka://system/user/actorDemo] 其它未知消息:20

       我们通过extends AbstractActor 类并且重写createReceive方法创建Actor。重写createReceive(),我们可以决定自己的actor可以接受何种类型的消息,以及如何处理这些消息。receiveBuilder工厂定义了许多API,可以方便我们处理。上述代码,actorDemo被允许接受String类型消息,当不属于String类型的消息,我们当做未知消息进行处理。

     Actor创建方式

       在Akka中,我们一般会使用ActorSystem或ActorContext来创建Actor。在上述示例中,我们使用的是ActorSystem来创建,它是一个比较重量级的对象,通常一个应用程序我们只会创建一个该对象。在创建Actor时,最好给出相应名字,用于业务识别。特别注意,在同一个ActorSystem中,actor不能重名。在实际项目中,我们更希望创建具有层级的actor,让父级对子级进行管理和监控,这时,我们就会使用ActorContext来创建。例如: 

       ActorRef actorDemo = getContext().actorOf(Props.create(ActorDemo.class), "actorDemo");

     Actor构建工具Props

       通过上述介绍发现,无论是ActorSystem,还是ActorContext,它们都是通过接受一个Props对象来创建Actor。Props是一个配置类,Akka为我们提供三种方式,如下:

Props props1 = Props.create(ActorDemo.class); 

Props props2 = Props.create(ActorDemo.class,() -> new ActorWithArgs("arg")); 

Props props3 = Props.create(ActorDemo.class, "arg");

     消息

        在actor网络中,actor与actor进行通信都是通过消息传递。这就好比现实中的写信,A和B两个人只知道各自的地址,没有其它联系方式,这时他们通过信件来进行沟通,A给B发完信件之后,又可以去做其它的事情,B也是一样。于此同时,A和B也可以接受其他小伙伴发送的信件。这里的信件就类似actor之间的消息,通过异步的方式发送。

        消息的发送分为两种:

类别

特点

tell

异步发送消息并立即返回。

ask

异步发送消息,返回Future表示可能的结果,可以设置超时时间,超时未得到结果,返回超时异常。

     tell方法

       当我们仅仅只需要异步发送消息的时候,tell方法是一个不错的选择。

       demo.tell("hello world",ActorRef.noSender());

       tell方法有两个参数,第一个参数表示消息,它可以接受任何类型的数据,第二个参数表示发送者,也就是另外一个actor的引用对象。上述使用的ActorRef.noSender(),表示没有发送者(其实是一个叫做deadLetters的Actor)。在项目中,如果我们想得到发送者,可以调用getSender()方法。

     ask方法

       这个方法类似于java中的Future模式,也是异步的得到一个返回对象,典型的“请求-响应”模式。通常我们采用异步回调的方式获取Future对象。

       ask方法不能通过actorRef对象调用,而是 通过akka.pattern.Patterns包下的Patterns.ask方式来操作,ask方法有三个参数,第一个参数表示接受消息的ActorRef,第二个参数表示发送的消息,第三个参数表示超时时间。ask方法会返回一个Future对象,它提供了三个回调方法,供我们处理返回数据和异常—onSuccess、onFailure、onComplete。例如:

Future<Object> future = Patterns.ask(demo, "hello world", new Timeout(Duration.create(1, TimeUnit.SECONDS)));

future.onSuccess(new OnSuccess<Object>() {

@Override

public void onSuccess(Object o) throws Throwable {

    System.out.println("返回结果:" + o);

}

}, system.dispatcher());

       通过forward转发消息,发送给TargetActor消息的sender仍然是ForwardActor的发送者,而不是ForwardActor。

     消息转发

       在Actor通信中,我们除了可以给某个Actor直接发送消息之外,还可以通过forward转发的方式。forward表明Akka会保留消息原始发件人的地址及引用,当我们在实现消息路由或者负载均衡时,非常有用。示例:

public class ForwardActor extends AbstractActor {

    private ActorRef actorRef = getContext().actorOf(Props.create(TargetActor.class), "targetActor");

    public static void main(String[] args) {

        ActorSystem actorSystem = ActorSystem.create();

        ActorRef forwardActor = actorSystem.actorOf(Props.create(ForwardActor.class), "forwardActor");

        forwardActor.tell("hello world", ActorRef.noSender());

    }

    @Override

    public Receive createReceive() {

        return receiveBuilder().match(String.class, s -> {

            actorRef.forward(s, getContext());

        }).build();

    }

}

class TargetActor extends AbstractActor {

    @Override

    public Receive createReceive() {

        return receiveBuilder().match(String.class, s -> {

            System.out.println("forward message:" + s);

        }).build();

    }

}

   

     查找

       在介绍actor查找之前,我们需要先了解一下actor路径问题。

       之前我们已经谈到,创建actor可以使用ActorSystem或ActorContext,使用ActorSystem创建的actor是我们所能创建的最高级别actor,也就是在/user/目录下。在上述第一个示例中,输出日志中,有这样一段akka://system/user/actorDemo,这就代表actorDemo的路径,其中system是我们定义的ActorSystem名称,user是我们创建actor所在的父目录(我们创建的actor都在它之下),actorDemo是我们创建的actor名称,它们共同组成一个actor路径。假如我们在ActorDemo中,使用ActorContext创建一个Actor—otherActor,那么otherActor的路径就是akka://system/user/actorDemo/otherActor,以此类推。

       在实际项目中,当我们需要调用某一个Actor时,我们也可以使用路径去定位到它,相关API:

 ActorSelection=[ActorSystem/ActorContext].actorSelection([path]);

       其中path,我们可以使用相对或者是绝对路径,例如:

       /user/actorDemo

       ../actorDemo

       akka.tcp://[email protected]:8080/user/parentActor/childActor  (远程actor路径)

       调用actorSelection会返回一个ActorSelection对象,当我们想得到目标ActorRef时,可以向ActorSelection发送一个Identify对象消息,然后对方会自动返回一个ActorIdentity对象,该对象包含了目标ActorRef信息,示例:

class TargetActor extends AbstractActor {
    @Override
    public Receive createReceive() {
        return receiveBuilder().match(Object.class, o -> {
            System.out.println("receive message:" + o);
        }).build();
    }
}


class LookupActor extends AbstractActor {
    private ActorRef actorRef = getContext().actorOf(Props.create(TargetActor.class), "targetActor");

    public static void main(String[] args) {
        ActorSystem actorSystem = ActorSystem.create("system");
        ActorRef actor = actorSystem.actorOf(Props.create(LookupActor.class), "lookupActor");
        actor.tell("find", ActorRef.noSender());
    }

    @Override
    public Receive createReceive() {
        return receiveBuilder().match(String.class, s -> {
            if ("find".equals(s)) {
                ActorSelection targetActor = getContext().actorSelection("targetActor");
                targetActor.tell(new Identify("001"), getSelf());
            }
        }).match(ActorIdentity.class, i -> {
            if (i.correlationId().equals("001")) {
                Optional<ActorRef> ref = i.getActorRef();
                if (ref != null) {
                    ActorRef actorRef = ref.get();
                    System.out.println("id:" + i.correlationId() + " " + actorRef);
                    actorRef.tell("hello targetActor", getSelf());
                }
            }
        }).build();
    }
}

      执行结果:

id:001 Actor[akka://system/user/lookupActor/targetActor#1717439239]

receive message:hello targetActor

      上述代码中,由于TargetActor是LookupActor的子集,所以我们可以指定为“targetActor”。由于我们指定actor,有可能找不到,所以在获取目标ActorRef时,需要判断是否为空,以免出现异常。

       Actor查找,不仅可以使用相对或绝对路径,而且还可以使用通配符进行匹配,例如:

       ../*     查找所有同级的actor

       /user/parentActor/*    查找parentActor下面的所有子actor

       /user/parentActor/child*   查找parentActor下面所有以child开头的子actor

       另外,我们还可以使用ActorSelection的resolveOne得到ActorRef,该方法会返回一个Future对象,和ask返回Future对象相同,也具有onSuccess和onFailure回调方法。我们可以使用onSuccess回调方法获取ActorRef对象,使用onFailure方法处理异常信息。这里不做过多描述,大家有兴趣可以自行研究研究。

        在这里对Actor的基本用法做了大致讲解,下一节,对Actor的生命周期做详细描述,由于本人才疏学浅,有不对的地方,望大家批评指正,共同进步,

猜你喜欢

转载自blog.csdn.net/p_programmer/article/details/82860353
今日推荐