Akka java 入门【二】 Actor详解

目录

1.基本概念

2.Actor的创建

前置例子:

1):AbstractActor UntypedActor TypedActor的区别

2):Receive createReceive()的含义

3):newReceiveBuilder() .onSignal .onMessage .build();Behaviors.same()分别都是什么意思

2.ActorSystem的创建

前置例子:

1):ActorRef的含义

2):Actor中的tell()和ask()详解


1.基本概念

Akka框架的核心就是Actor模型,因此理解Actor是理解Akka框架的关键。Actor是一种并发编程模型,它将计算任务分解为独立的Actor,每个Actor都有自己的状态和行为,并通过异步消息传递来实现彼此之间的通信。

在Akka框架中,每个Actor都是一个独立的实体,可以通过创建ActorSystem来创建和管理Actor。ActorSystem是一个全局的、线程安全的Actor容器,每个Actor都是ActorSystem中的子Actor。

在Actor模型中,Actor之间的通信通过异步的消息传递来实现。一个Actor可以向另一个Actor发送消息,也可以将消息发送给自己。当一个Actor收到消息时,它会根据消息内容调用相应的方法来处理消息。

每个Actor都有一个状态和行为,称为Actor的内部状态和行为。Actor的内部状态是封闭的,只能通过Actor自己提供的方法进行修改。这种封闭的状态和行为使得Actor模型天然地支持并发编程,因为每个Actor都是独立的实体,不会受到其他Actor的干扰。

Actor模型还提供了一些额外的特性,如监督机制和容错机制。每个Actor都有一个监督者Actor,当一个Actor抛出异常时,它的监督者Actor会接收到异常信息并决定如何处理异常。容错机制则指当一个Actor出现异常时,系统会根据事先设定的策略来恢复或重启该Actor,从而保证系统的稳定性和可靠性。

在Akka框架中,Actor还可以通过路由器来实现负载均衡和并行处理。路由器将消息发送给多个Actor,并决定哪个Actor来处理该消息。通过路由机制,可以轻松构建高可用、高并发的分布式系统。

2.Actor的创建

前置例子:

当我们使用Akka框架创建一个Actor时,需要定义一个继承自AbstractActor的子类,并实现createReceive方法来定义Actor接收消息后的行为。例如:

public class MyActor extends AbstractActor {
    @Override
    public Receive createReceive() {
        return receiveBuilder()
                // 处理字符串类型的消息
                .match(String.class, msg -> {
                    System.out.println("Received message: " + msg);
                })
                .build();
    }
}

上述代码定义了一个名为MyActor的Actor,它可以接收类型为String的消息,并在控制台上打印收到的消息内容。继承自AbstractActor,注意如下:

PS:

1):AbstractActor UntypedActor TypedActor的区别

Akka是一个基于Actor模型的并发编程框架,它支持三种Actor类型:AbstractActor、UntypedActor和TypedActor。

AbstractActor是所有Akka Actor的基类,它提供了一些Actor的通用功能,如生命周期管理、消息处理等。但是,AbstractActor本身不能被实例化,必须通过扩展该类来创建自定义Actor。

UntypedActor是一种基于Java API的Actor类型,它需要手动处理消息类型并进行类型转换。这种Actor类型已经被标记为过时,推荐使用更安全和类型安全的TypedActor。

TypedActor是一种类型安全的Actor类型,它是基于Scala和Java API的。与UntypedActor不同,TypedActor需要指定Actor接收的消息类型,并且可以保证在编译时检测到类型错误。此外,TypedActor还提供了一些便利的功能,例如更简单的Actor创建和使用等。

因此,三种Actor类型的主要区别在于类型安全和易用性方面。在Akka 2.6及以后的版本中,UntypedActor已经被标记为过时,因此建议使用TypedActor来编写更安全和可维护的Actor应用程序。

2):Receive createReceive()的含义

在Akka中,每个Actor都需要实现一个名为Receive的方法,该方法定义了Actor如何处理接收到的消息。Receive方法通常是一个部分函数,它接受一个消息并返回一个处理结果。在上面的例子中,MyActor实现了一个名为createReceive()的方法,该方法返回一个部分函数对象,该函数对象定义了如何处理不同类型的消息。使用ReceiveBuilder对象,我们可以轻松地定义匹配不同类型消息的处理程序。

在Akka中,newReceiveBuilder()是用于创建Actor接收处理器的构建器对象。这个构建器对象可以用于定义Actor接收到不同类型的消息时要执行的处理程序。通过这个构建器对象,我们可以使用一组函数式的API定义一个部分函数对象,然后使用build()方法构建Actor接收处理器。

3):newReceiveBuilder() .onSignal .onMessage .build();Behaviors.same()分别都是什么意思

举个例子:

ActorRef<MyMessage> actorRef = context.spawn(MyActor.create(), "my-actor");
Actor<Command> actor = Actor.deferred(ctx -> {
  return Behaviors.receive(Command.class)
    .onMessage(CommandA.class, command -> {
      // 处理 CommandA
      return Behaviors.same();
    })
    .onMessage(CommandB.class, command -> {
      // 处理 CommandB
      return Behaviors.same();
    })
    .onSignal(PostStop.class, signal -> {
      // 处理 Actor 停止信号
      return Behaviors.same();
    })
    .build();
});

onMessage():

在这个构建器对象中,我们可以使用onMessage()方法来定义Actor接收到消息时执行的处理程序。例如:

ReceiveBuilder builder = ReceiveBuilder.create()
        .onMessage(String.class, msg -> {
            System.out.println("Received message: " + msg);
            return akka.actor.typed.javadsl.Behaviors.same();
        });

在上面的例子中,我们定义了一个处理字符串类型消息的处理程序,并在接收到消息时打印消息内容。

onSignal():

除了onMessage()方法之外,我们还可以使用onSignal()方法来定义Actor接收到特定类型信号时要执行的处理程序。例如:

ReceiveBuilder builder = ReceiveBuilder.create()
        .onSignal(PostStop.class, signal -> {
            System.out.println("Actor is stopping...");
            return akka.actor.typed.javadsl.Behaviors.same();
        });

在上面的例子中,我们定义了一个Actor停止时要执行的处理程序,并在Actor接收到停止信号时打印一条消息。

build():

我们使用build()方法构建了一个Actor接收处理器对象,并将其返回给Actor系统,以便接收并处理来自其他Actor的消息。

总的来说,Akka中的newReceiveBuilder()、onMessage()、onSignal()和build()方法用于定义Actor接收到不同类型消息时的处理程序。具体来说,它们的含义如下:

  1. newReceiveBuilder(): 创建Actor接收处理器的构建器对象。

  2. onMessage(): 定义Actor接收到特定类型消息时要执行的处理程序。可以定义多个onMessage()方法,每个方法接收不同类型的消息,并定义不同的处理程序。

  3. onSignal(): 定义Actor接收到特定类型信号时要执行的处理程序。可以定义多个onSignal()方法,每个方法接收不同类型的信号,并定义不同的处理程序。

  4. build(): 使用构建器对象创建Actor接收处理器对象。Actor接收处理器对象是一个部分函数,用于接收来自其他Actor的消息和信号,并执行相应的处理程序。

在定义Actor接收处理器时,我们可以根据具体的业务需求,使用不同的onMessage()和onSignal()方法来定义不同类型消息和信号的处理程序。同时,我们也可以使用匿名函数或方法引用来定义处理程序。这样可以使代码更加简洁和易于阅读。

Behaviors.same():

在Akka中,Behaviors.same()是一个用于构建Actor行为的静态方法。它表示Actor在处理完当前消息后不会发生状态变化,因此返回当前状态。

在Akka中,Actor的行为可以看作是Actor状态机的一个状态,当Actor接收到消息时,它会执行与当前状态相关联的行为。因此,在Actor的行为中,我们通常需要根据当前状态来定义处理逻辑。

当Actor处理完当前消息后,如果没有任何状态变化,那么我们可以使用Behaviors.same()来返回当前状态。这个方法表示Actor状态不会发生变化,会继续执行与当前状态相关联的行为。例如:

import akka.actor.typed.Behavior;
import akka.actor.typed.javadsl.Behaviors;

public class MyActor {

    public static Behavior<String> create() {
        return Behaviors.receive(String.class)
                .onMessage(String.class, msg -> {
                    System.out.println("Received message: " + msg);
                    return Behaviors.same();
                })
                .build();
    }
}

在上面的例子中,我们定义了一个Actor,它处理字符串类型的消息。当Actor接收到消息时,它会打印消息内容,并使用Behaviors.same()返回当前状态。

在实际开发中,使用Behaviors.same()可以让Actor更加灵活地处理消息。如果Actor的状态没有发生变化,那么它就不需要执行任何更新操作,直接返回当前状态即可。这样可以避免不必要的状态变更,提高Actor的性能和可靠性。

2.ActorSystem的创建

前置例子:

在Akka中,ActorSystem是整个Actor应用程序的核心,是创建和管理Actor的主要接口。它是一个重量级的对象,负责管理Actor的生命周期、线程池、消息队列和Actor的创建和销毁等。

ActorSystem包含以下几个主要组件:

  1. ActorRef: Actor的引用,用于向Actor发送消息。

  2. ActorPath: Actor的路径,表示Actor在Actor系统中的位置。

  3. Dispatcher: 线程池和调度器,用于管理Actor执行的线程和调度执行顺序。

  4. Mailbox: 消息队列,用于存储Actor接收到的消息,并根据调度器的调度顺序将消息发送给Actor执行。

  5. SupervisorStrategy: 监管策略,用于定义Actor遇到异常时的处理方式。

使用ActorSystem可以创建和管理整个Actor应用程序的生命周期。当我们需要创建Actor时,可以通过ActorSystem创建Actor,并获得ActorRef来向Actor发送消息。当Actor系统启动后,会自动创建一个根Actor,称为GuardianActor,它是所有Actor的父Actor,用于管理整个Actor系统的生命周期。

例如,以下是一个简单的ActorSystem示例:

import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import akka.actor.Props;

public class MyActorSystem {
    
    public static void main(String[] args) {
        
        //在创建MyActor实例之前,需要先创建一个ActorSystem实例,例如:
        ActorSystem system = ActorSystem.create("my-actor-system");
        
        
        //接着,我们可以使用ActorSystem实例创建MyActor实例:
        ActorRef myActor = system.actorOf(Props.create(MyActor.class), "my-actor");
        
        //上述代码使用Props类来创建一个MyActor实例,并将其注册到ActorSystem中,同时指定了MyActor实例的名称为my-actor。现在,我们可以向myActor发送消息,例如:
        myActor.tell("Hello, Actor!", ActorRef.noSender());
        
        //上述代码将一个类型为String、内容为"Hello, Actor!"的消息发送给myActor,同时指定了消息的发送者为ActorRef.noSender(),表示该消息是无发送者的。当myActor收到消息时,它会调用其createReceive方法中匹配类型为String的消息的行为,即打印收到的消息内容。

        // 关闭ActorSystem
        system.terminate();
    }
}

在上面的示例中,我们使用ActorSystem创建了一个名为"my-actor-system"的ActorSystem,并通过Props创建了一个MyActor实例,并获得了对该Actor的ActorRef。接下来,我们向Actor发送了一条消息,并在最后关闭了ActorSystem。

总之,ActorSystem是整个Actor应用程序的核心,它是管理Actor的主要接口,负责创建Actor、管理Actor的生命周期、线程池、消息队列和监管策略等。

1):ActorRef的含义

在Akka中,ActorRef表示对Actor的引用。可以把ActorRef看作是一个Actor实例的代理,可以通过它来发送消息和进行远程调用。它是一个轻量级的对象,可以被高效地序列化和传输。

ActorRef是一个重要的概念,它代表了一个Actor的引用。ActorRef可以看作是一个Actor的代理,我们可以通过它来向一个Actor发送消息,查询一个Actor的状态等。

ActorRef具有以下几个重要的属性:

  1. 唯一性:每个Actor实例都有唯一的ActorRef,可以通过ActorRef来区分不同的Actor实例。

  2. 不可变性:ActorRef是不可变的,一旦创建,就不能被修改。

  3. 异步性:向ActorRef发送消息是异步的操作,发送者不会阻塞等待消息处理完成。

  4. 路由:可以使用ActorRef进行路由,将消息路由到不同的Actor实例。

  5. 生命周期管理:ActorRef可以用于监视和管理Actor实例的生命周期,例如重启、停止、恢复等操作。

创建ActorRef的方式主要有以下几种:

  1. 在Actor内部使用self()方法创建自己的ActorRef。

  2. 在Actor外部使用ActorSystem.actorOf()方法创建新的ActorRef。

  3. 通过ActorSelection从ActorSystem中获取一个已存在的ActorRef。

我们创建一个Actor时,Akka会返回一个ActorRef实例,例如:

ActorRef myActor = system.actorOf(Props.create(MyActor.class), "my-actor");

上述代码创建了一个名为my-actorMyActor实例,并返回了一个ActorRef实例myActor。我们可以使用myActor来向MyActor发送消息,例如:

myActor.tell("Hello, Actor!", ActorRef.noSender());

上述代码将一个类型为String、内容为"Hello, Actor!"的消息发送给myActor。由于ActorRef是一个Actor的代理,因此我们可以创建多个ActorRef实例来引用同一个Actor,例如:

ActorRef myActor1 = system.actorOf(Props.create(MyActor.class), "my-actor1");
ActorRef myActor2 = system.actorFor("akka://my-system/user/my-actor1");

上述代码分别创建了两个名为my-actor1MyActor实例,并返回了两个不同的ActorRef实例myActor1myActor2。由于它们引用的是同一个Actor,因此我们可以通过任意一个ActorRef实例向该Actor发送消息,例如:

myActor1.tell("Hello, Actor!", ActorRef.noSender());
myActor2.tell("Hello, Actor!", ActorRef.noSender());

上述代码都将一个类型为String、内容为"Hello, Actor!"的消息发送给同一个MyActor实例。

除了向Actor发送消息之外,ActorRef还可以用来查询一个Actor的状态。我们可以通过ActorReftell方法来发送一个询问消息给Actor,Actor接收到该消息后返回一个结果,例如:

Future<Object> future = Patterns.ask(myActor, new MyActor.GetState(), Timeout.apply(5, TimeUnit.SECONDS));

上述代码使用Akka提供的Patterns.ask方法向myActor发送了一个类型为MyActor.GetState的消息,并设置了一个超时时间为5秒。当myActor收到该消息时,它会调用其createReceive方法中匹配类型为MyActor.GetState的消息的行为,执行该行为并返回一个结果。Patterns.ask方法将返回一个Future实例,我们可以通过该实例来获取Actor返回的结果,例如:

Object result = Await.result(future, Duration.Inf());

上述代码会阻塞当前线程,直到Actor返回结果,然后返回Actor返回的结果。如果Actor没有在指定的超时时间内返回结果,则会抛出一个TimeoutException异常。

ActorRef还可以用于进行远程调用,这需要使用Akka的远程调用功能。如果ActorRef指向的Actor实例在远程节点上,则可以通过ActorRef进行远程调用。远程调用的方式与本地调用相同,只是在ActorRef的构造函数中需要指定远程Actor的地址。例如:

ActorRef remoteActorRef = system.actorOf(Props.create(MyActor.class), "myRemoteActor");
ActorSelection remoteActorSelection = system.actorSelection("akka.tcp://mySystem@remotehost:1234/user/myRemoteActor");
remoteActorSelection.tell("hello", ActorRef.noSender());

这将向名为“myRemoteActor”的远程Actor实例发送一条名为“hello”的消息。

总之,ActorRef是Akka中非常重要的一个概念,它是Actor实例的代理,可以用于向Actor发送消息、路由、远程调用和生命周期管理等操作。

2):Actor中的tell()和ask()详解

在Akka Actor模型中,可以使用tell()方法和ask()方法向Actor发送消息。

  • tell()方法:

tell()方法是Actor之间通信的基本方法。它是异步的,不会等待接收方Actor处理完消息。在tell()方法中,第一个参数是要发送的消息内容,第二个参数是接收方Actor的ActorRef,如果不需要接收方Actor回复消息,则可以使用ActorRef.noSender()作为第二个参数。

例如,如果我们想要向一个Actor发送一个字符串消息,可以使用如下的tell()方法:

actorRef.tell("hello", ActorRef.noSender());
  • ask()方法:

ask()方法也是一种向Actor发送消息的方式,不同的是,它会等待接收方Actor处理完消息并返回一个Future对象。在ask()方法中,第一个参数是接收方Actor的ActorRef,第二个参数是要发送的消息内容。除此之外,还需要使用Akka提供的Timeout类指定等待超时时间,以及使用Akka提供的ExecutionContext来执行回调函数。

例如,如果我们想要向一个Actor发送一个字符串消息,并等待接收方Actor返回一个字符串结果,可以使用如下的ask()方法:

Timeout timeout = Timeout.create(Duration.ofSeconds(5));
Future<Object> future = Patterns.ask(actorRef, "hello", timeout);
future.onComplete(new OnComplete<Object>() {
  @Override
  public void onComplete(Throwable throwable, Object o) throws Throwable {
    if (throwable != null) {
      System.out.println("ask failed: " + throwable.getMessage());
    } else {
      System.out.println("ask result: " + o);
    }
  }
}, system.dispatcher());

在这个例子中,我们创建了一个Timeout对象指定等待超时时间为5秒,然后使用Patterns.ask()方法向Actor发送消息,并返回一个Future对象。接下来,我们使用Future.onComplete()方法指定一个回调函数,在Future完成时执行。如果ask()操作成功,则会打印出返回的结果;如果ask()操作失败,则会打印出失败的原因。

总之,tell()方法是异步的,不等待接收方Actor处理完消息,而ask()方法是同步的,会等待接收方Actor处理完消息并返回一个Future对象。根据具体的应用场景,可以选择合适的方法来实现Actor之间的通信。

ask和tell的使用将在该专栏的其他文章中详解。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值