精华内容
下载资源
问答
  • Akka

    2019-10-25 03:21:58
    Akka
  • 快速入门 Akka Java 指南

    万次阅读 多人点赞 2019-04-01 18:18:55
    Akka 是一个用于在 JVM 上构建高并发、分布式和容错的事件驱动应用程序的运行时工具包。Akka 既可以用于 Java,也可以用于 Scala。本指南通过描述 Java 版本的Hello World示例来介绍 Akka

    温馨提示:Akka 中文指南的 GitHub 地址为「akka-guide」,欢迎大家StarFork,纠错。


    Akka 是一个用于在 JVM 上构建高并发、分布式和容错的事件驱动应用程序的运行时工具包。Akka 既可以用于 Java,也可以用于 Scala。本指南通过描述 Java 版本的Hello World示例来介绍 Akka。如果你喜欢将 Akka 与 Scala 结合使用,请切换到「快速入门 Akka Scala 指南」。

    Actors 是 Akka 的执行单元。Actor 模型是一种抽象,它让编写正确的并发、并行和分布式系统更加容易。Hello World示例说明了 Akka 的基础知识。在 30 分钟内,你应该能够下载并运行示例,并使用本指南了解示例是如何构造的。这会让你初步了解 Akka 的魅力,希望这能够让你拥有深入了解 Akka 的兴趣!

    在体验过这个示例之后,想深入了解 Akka,阅读「Getting Started Guide」是一个很好的选择。

    下载示例

    Java 版本的Hello World示例是一个包含 Maven 和 Gradle 构建文件的压缩项目。你可以在 Linux、MacOS 或 Windows 上运行它。唯一的先决条件是安装 Java 8 和 Maven 或 Gradle。

    下载和解压示例:

    • 在「Lightbend Tech Hub」上通过点击CREATE A PROJECT FOR ME下载压缩文件。
    • 将 ZIP 文件解压缩到方便的位置:
      • 在 Linux 和 OSX 系统上,打开终端并使用命令unzip akka-quickstart-java.zip
      • 在 Windows 上,使用文件资源管理器等工具提取项目。

    运行示例

    确保你已经安装了构建工具,然后打开终端窗口,并从项目目录中键入以下命令以运行Hello World

    //  Maven
    $ mvn compile exec:exec
    
    // Grade
    $ gradle run
    

    输出应该如下所示(一直向右滚动以查看 Actor 输出):

    // Maven
    [INFO] Scanning for projects...
    [INFO]
    [INFO] ------------------------------------------------------------------------
    [INFO] Building app 1.0
    [INFO] ------------------------------------------------------------------------
    [INFO]
    [INFO] --- exec-maven-plugin:1.6.0:exec (default-cli) @ app ---
    >>> Press ENTER to exit <<<
    [INFO] [05/11/2017 14:07:20.790] [helloakka-akka.actor.default-dispatcher-2] [akka://helloakka/user/printerActor] Hello, Java
    [INFO] [05/11/2017 14:07:20.791] [helloakka-akka.actor.default-dispatcher-2] [akka://helloakka/user/printerActor] Good day, Play
    [INFO] [05/11/2017 14:07:20.791] [helloakka-akka.actor.default-dispatcher-2] [akka://helloakka/user/printerActor] Howdy, Akka
    [INFO] [05/11/2017 14:07:20.791] [helloakka-akka.actor.default-dispatcher-2] [akka://helloakka/user/printerActor] Howdy, Lightbend
    
    // Grade
    :compileJava UP-TO-DATE
    :processResources NO-SOURCE
    :classes UP-TO-DATE
    :run
    >>> Press ENTER to exit <<<
    [INFO] [05/11/2017 14:08:22.884] [helloakka-akka.actor.default-dispatcher-2] [akka://helloakka/user/printerActor] Howdy, Akka
    [INFO] [05/11/2017 14:08:22.884] [helloakka-akka.actor.default-dispatcher-2] [akka://helloakka/user/printerActor] Good day, Play
    [INFO] [05/11/2017 14:08:22.884] [helloakka-akka.actor.default-dispatcher-2] [akka://helloakka/user/printerActor] Hello, Java
    [INFO] [05/11/2017 14:08:22.884] [helloakka-akka.actor.default-dispatcher-2] [akka://helloakka/user/printerActor] Howdy, Lightbend
    <=========----> 75% EXECUTING
    > :run
    

    恭喜你,你刚刚运行了你的第一个 Akka 应用程序。

    Hello World 都做了什么?

    正如你在控制台输出中看到的,该示例输出了一些问候语。让我们看看运行时都发生了什么。

    mainclass-actorsystem

    首先,主函数main创建了一个akka.actor.ActorSystem,它是一个运行Actors的容器。接下来,它创建了三个Greeter Actor实例和一个Printer Actor实例。

    然后,该示例将消息发送给Greeter Actor实例,后者在内部存储消息。最后,发送给Greeter Actor的指令消息触发它们向Printer Actor发送消息,Printer Actor将消息输出到控制台:

    mainclass-hhgp

    Akka 对 Actor 和异步消息传递的使用带来了一系列好处。大家可以考虑一下都带来了什么好处?

    使用 Actor 模型的好处

    Akka 的以下特性使你能够以直观的方式解决困难的并发性和可伸缩性挑战:

    • 事件驱动模型:Event-driven model,Actor 通过响应消息来执行工作。Actor 之间的通信是异步的,允许 Actor 发送消息并继续自己的工作,而不是阻塞等待响应。
    • 强隔离原则:Strong isolation principles,与 Java 中的常规对象不同,Actor 在调用的方法方面,没有一个公共 API。相反,它的公共 API 是通过 Actor 处理的消息来定义的。这可以防止 Actor 之间共享状态;观察另一个 Actor 状态的唯一方法是向其发送请求状态的消息。
    • 位置透明:Location transparency,系统通过工厂方法构造 Actor 并返回对实例的引用。因为位置无关紧要,所以 Actor 实例可以启动、停止、移动和重新启动,以向上和向下扩展以及从意外故障中恢复。
    • 轻量级:Lightweight,每个实例只消耗几百个字节,这实际上允许数百万并发 Actor 存在于一个应用程序中。

    让我们看看在Hello World示例中使用 Actor 和消息一起工作的一些最佳实践。

    定义 Actor 和消息

    消息可以是任意类型(Object的任何子类型),你可以将装箱类型(如StringIntegerBoolean等)作为消息发送,也可以将普通数据结构(如数组和集合类型)作为消息发送。

    Hello World的 Actor 使用三种不同的消息:

    • WhoToGreet:问候消息的接受者;
    • Greet:执行问候的指令;
    • Greeting:包含问候语的消息。

    在定义 Actor 及其消息时,请记住以下建议:

    • 因为消息是 Actor 的公共 API,所以定义具有良好名称、丰富语义和特定于域的含义的消息是一个很好的实践,即使它们只是包装你的数据类型,这将使基于 Actor 的系统更容易使用、理解和调试。
    • 消息应该是不可变的,因为它们在不同的线程之间共享。
    • 将 Actor 的关联消息作为静态类放在 Actor 的类中是一个很好的实践,这使得理解 Actor 期望和处理的消息类型更加容易。
    • 在 Actor 类中使用静态props方法来描述如何构造 Actor 也是一种常见的模式。

    让我们看看 Actor 如何实现GreeterPrinter来演示这些最佳实践。

    Greeter Actor

    下面的代码段来自于Greeter.java,其实现了Greeter Actor

    package com.lightbend.akka.sample;
    
    import akka.actor.AbstractActor;
    import akka.actor.ActorRef;
    import akka.actor.Props;
    import com.lightbend.akka.sample.Printer.Greeting;
    
    public class Greeter extends AbstractActor {
      static public Props props(String message, ActorRef printerActor) {
        return Props.create(Greeter.class, () -> new Greeter(message, printerActor));
      }
    
      static public class WhoToGreet {
        public final String who;
    
        public WhoToGreet(String who) {
            this.who = who;
        }
      }
    
      static public class Greet {
        public Greet() {
        }
      }
    
      private final String message;
      private final ActorRef printerActor;
      private String greeting = "";
    
      public Greeter(String message, ActorRef printerActor) {
        this.message = message;
        this.printerActor = printerActor;
      }
    
      @Override
      public Receive createReceive() {
        return receiveBuilder()
            .match(WhoToGreet.class, wtg -> {
              this.greeting = message + ", " + wtg.who;
            })
            .match(Greet.class, x -> {
              printerActor.tell(new Greeting(greeting), getSelf());
            })
            .build();
      }
    }
    

    让我们来解析上面的功能:

    • Greeter类扩展了akka.actor.AbstractActor类并实现了createReceive方法。
    • Greeter构造函数接受两个参数:String message,它将在构建问候语时使用,ActorRef printerActor是处理问候语输出的 Actor 的引用。
    • receiveBuilder定义了行为;Actor 应该如何响应它接收到的不同消息。Actor 可以有状态。访问或改变 Actor 的内部状态是线程安全的,因为它受 Actor 模型的保护。createReceive方法应处理 Actor 期望的消息。对于Greeter,它需要两种类型的消息:WhoToGreetGreet,前者将更新 Actor 的问候语状态,后者将触发向Printer Actor发送问候语。
    • greeting变量包含 Actor 的状态,默认设置为""
    • 静态props方法创建并返回Props实例。Props是一个配置类,用于指定创建 Actor 的选项,将其视为不可变的,因此可以自由共享用于创建包含相关部署信息的 Actor 的方法。这个例子简单地传递了 Actor 在构造时需要的参数。我们将在本教程的后面部分看到props方法的实际应用。

    Printer Actor

    Printer的实现非常简单:

    • 它通过Logging.getLogger(getContext().getSystem(), this);创建一个日志器。通过这样做,我们可以在 Actor 中编写log.info(),而不需要任何额外的连接。
    • 它只处理一种类型的消息Greeting,并记录该消息的内容。
    package com.lightbend.akka.sample;
    
    import akka.actor.AbstractActor;
    import akka.actor.Props;
    import akka.event.Logging;
    import akka.event.LoggingAdapter;
    
    public class Printer extends AbstractActor {
      static public Props props() {
        return Props.create(Printer.class, () -> new Printer());
      }
    
      static public class Greeting {
        public final String message;
    
        public Greeting(String message) {
          this.message = message;
        }
      }
    
      private LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);
    
      public Printer() {
      }
    
      @Override
      public Receive createReceive() {
        return receiveBuilder()
            .match(Greeting.class, greeting -> {
                log.info(greeting.message);
            })
            .build();
      }
    }
    

    创建 Actor

    到目前为止,我们已经研究了 Actor 的定义和他们的消息。现在,让我们更深入地了解位置透明(location transparency)的好处,看看如何创建 Actor 实例。

    位置透明的好处

    在 Akka 中,不能使用new关键字创建 Actor 的实例。相反,你应该使用工厂方法创建 Actor 实例。工厂不返回 Actor 实例,而是返回指向 Actor 实例的引用akka.actor.ActorRef。在分布式系统中,这种间接创建实例的方法增加了很多好处和灵活性。

    在 Akka 中位置无关紧要。位置透明性意味着,无论是在正在运行 Actor 的进程内,还是运行在远程计算机上,ActorRef都可以保持相同语义。如果需要,运行时可以通过更改 Actor 的位置或整个应用程序拓扑来优化系统。这就启用了故障管理的“让它崩溃(let it crash)”模型,在该模型中,系统可以通过销毁有问题的 Actor 和重新启动健康的 Actor 来自我修复。

    Akka ActorSystem

    akka.actor.ActorSystem工厂在某种程度上类似于 Spring 的 BeanFactory,它是运行 Actor 的容器并管理他们的生命周期。actorOf工厂方法创建 Actor 并接受两个参数,一个名为props的配置对象和一个String类型的 Actor 名称。

    Actor 和 ActorSystem 的名字在 Akka 中很重要。例如,使用它们进行查找。使用与你的域模型(domain model)一致的有意义的名称可以更容易地对它们进行推理。

    前面我们看了Hello World的 Actor 定义。现在,让我们看看AkkaQuickstart.java文件中创建 Greeter Actor 和 Printer Actor 实例的代码:

    final ActorRef printerActor = 
      system.actorOf(Printer.props(), "printerActor");
    final ActorRef howdyGreeter = 
      system.actorOf(Greeter.props("Howdy", printerActor), "howdyGreeter");
    final ActorRef helloGreeter = 
      system.actorOf(Greeter.props("Hello", printerActor), "helloGreeter");
    final ActorRef goodDayGreeter = 
      system.actorOf(Greeter.props("Good day", printerActor), "goodDayGreeter");
    

    注意以下事项:

    • 使用 ActorSystem 上的actorOf方法创建 Printer Actor。正如我们在前面讨论的,它使用了Printer类的静态props方法来获取Props值。ActorRef 提供了对新创建的 Printer Actor 实例的引用。
    • 对于Greeter,代码创建三个 Actor 实例,每个实例都有一个特定的问候语。

    注意:在本例中,Greeter Actor 都使用了相同的 Printer 实例,但我们可以创建多个 Printer Actor 实例。示例中使用一个实例来说明稍后我们将讨论的消息传递(message passing)的一个重要概念。

    接下来,我们来看看如何与 Actor 通信。

    异步通信

    Actor 是被动的和消息驱动的。Actor 在收到消息前什么都不做。Actor 使用异步消息进行通信。这样可以确保发送者不会一直等待接收者处理他们的消息。相反,发件人将邮件放在收件人的邮箱之后,就可以自由地进行其他工作。Actor 的邮箱本质上是一个具有排序语义的消息队列。从同一个 Actor 发送的多条消息的顺序被保留,但可以与另一个 Actor 发送的消息交错。

    你可能想知道 Actor 在不处理消息的时候在做什么,比如,做什么实际的工作?实际上,它处于挂起状态,在这种状态下,它不消耗除内存之外的任何资源。同样,这也展示了 Actor 的轻量级和高效性。

    给 Actor 发生消息

    要将消息放入 Actor 的邮箱,我们需要使用ActorReftell方法。例如,Hello World的主函数main向 Greeter Actor 发送如下消息:

    howdyGreeter.tell(new WhoToGreet("Akka"), ActorRef.noSender());
    howdyGreeter.tell(new Greet(), ActorRef.noSender());
    
    howdyGreeter.tell(new WhoToGreet("Lightbend"), ActorRef.noSender());
    howdyGreeter.tell(new Greet(), ActorRef.noSender());
    
    helloGreeter.tell(new WhoToGreet("Java"), ActorRef.noSender());
    helloGreeter.tell(new Greet(), ActorRef.noSender());
    
    goodDayGreeter.tell(new WhoToGreet("Play"), ActorRef.noSender());
    goodDayGreeter.tell(new Greet(), ActorRef.noSender());
    

    Greeter Actor 也向 Printer Actor 发送消息:

    printerActor.tell(new Greeting(greeting), getSelf());
    

    我们已经研究了如何创建 Actor 和发送消息。现在,让我们来回顾一下Main类的全部内容。

    Main class

    Hello WorldMain类创建和控制 Actor。注意,使用ActorSystem作为容器,并使用actorOf方法创建 Actor。最后,类创建要发送给 Actor 的消息。

    package com.lightbend.akka.sample;
    
    import java.io.IOException;
    
    import com.lightbend.akka.sample.Greeter.Greet;
    import com.lightbend.akka.sample.Greeter.WhoToGreet;
    
    import akka.actor.ActorRef;
    import akka.actor.ActorSystem;
    
    public class AkkaQuickstart {
      public static void main(String[] args) {
        final ActorSystem system = ActorSystem.create("helloakka");
        try {
          final ActorRef printerActor = 
            system.actorOf(Printer.props(), "printerActor");
          final ActorRef howdyGreeter = 
            system.actorOf(Greeter.props("Howdy", printerActor), "howdyGreeter");
          final ActorRef helloGreeter = 
            system.actorOf(Greeter.props("Hello", printerActor), "helloGreeter");
          final ActorRef goodDayGreeter = 
            system.actorOf(Greeter.props("Good day", printerActor), "goodDayGreeter");
    
          howdyGreeter.tell(new WhoToGreet("Akka"), ActorRef.noSender());
          howdyGreeter.tell(new Greet(), ActorRef.noSender());
    
          howdyGreeter.tell(new WhoToGreet("Lightbend"), ActorRef.noSender());
          howdyGreeter.tell(new Greet(), ActorRef.noSender());
    
          helloGreeter.tell(new WhoToGreet("Java"), ActorRef.noSender());
          helloGreeter.tell(new Greet(), ActorRef.noSender());
    
          goodDayGreeter.tell(new WhoToGreet("Play"), ActorRef.noSender());
          goodDayGreeter.tell(new Greet(), ActorRef.noSender());
    
          System.out.println(">>> Press ENTER to exit <<<");
          System.in.read();
        } catch (IOException ioe) {
        } finally {
          system.terminate();
        }
      }
    }
    

    类似地,让我们再次看看定义 Actor 和他们接受的消息的完整源代码。

    完整代码

    下面是创建示例应用程序的三个类GreeterPrinterAkkaQuickstart的完整源代码:

    Greater.java

    package com.lightbend.akka.sample;
    
    import akka.actor.AbstractActor;
    import akka.actor.ActorRef;
    import akka.actor.Props;
    import com.lightbend.akka.sample.Printer.Greeting;
    
    public class Greeter extends AbstractActor {
      static public Props props(String message, ActorRef printerActor) {
        return Props.create(Greeter.class, () -> new Greeter(message, printerActor));
      }
    
      static public class WhoToGreet {
        public final String who;
    
        public WhoToGreet(String who) {
            this.who = who;
        }
      }
    
      static public class Greet {
        public Greet() {
        }
      }
    
      private final String message;
      private final ActorRef printerActor;
      private String greeting = "";
    
      public Greeter(String message, ActorRef printerActor) {
        this.message = message;
        this.printerActor = printerActor;
      }
    
      @Override
      public Receive createReceive() {
        return receiveBuilder()
            .match(WhoToGreet.class, wtg -> {
              this.greeting = message + ", " + wtg.who;
            })
            .match(Greet.class, x -> {
              printerActor.tell(new Greeting(greeting), getSelf());
            })
            .build();
      }
    }
    

    Printer.java

    package com.lightbend.akka.sample;
    
    import akka.actor.AbstractActor;
    import akka.actor.Props;
    import akka.event.Logging;
    import akka.event.LoggingAdapter;
    
    public class Printer extends AbstractActor {
      static public Props props() {
        return Props.create(Printer.class, () -> new Printer());
      }
    
      static public class Greeting {
        public final String message;
    
        public Greeting(String message) {
          this.message = message;
        }
      }
    
      private LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);
    
      public Printer() {
      }
    
      @Override
      public Receive createReceive() {
        return receiveBuilder()
            .match(Greeting.class, greeting -> {
                log.info(greeting.message);
            })
            .build();
      }
    }
    

    AkkaQuickstart.java

    package com.lightbend.akka.sample;
    
    import java.io.IOException;
    
    import com.lightbend.akka.sample.Greeter.Greet;
    import com.lightbend.akka.sample.Greeter.WhoToGreet;
    
    import akka.actor.ActorRef;
    import akka.actor.ActorSystem;
    
    public class AkkaQuickstart {
      public static void main(String[] args) {
        final ActorSystem system = ActorSystem.create("helloakka");
        try {
          final ActorRef printerActor = 
            system.actorOf(Printer.props(), "printerActor");
          final ActorRef howdyGreeter = 
            system.actorOf(Greeter.props("Howdy", printerActor), "howdyGreeter");
          final ActorRef helloGreeter = 
            system.actorOf(Greeter.props("Hello", printerActor), "helloGreeter");
          final ActorRef goodDayGreeter = 
            system.actorOf(Greeter.props("Good day", printerActor), "goodDayGreeter");
    
          howdyGreeter.tell(new WhoToGreet("Akka"), ActorRef.noSender());
          howdyGreeter.tell(new Greet(), ActorRef.noSender());
    
          howdyGreeter.tell(new WhoToGreet("Lightbend"), ActorRef.noSender());
          howdyGreeter.tell(new Greet(), ActorRef.noSender());
    
          helloGreeter.tell(new WhoToGreet("Java"), ActorRef.noSender());
          helloGreeter.tell(new Greet(), ActorRef.noSender());
    
          goodDayGreeter.tell(new WhoToGreet("Play"), ActorRef.noSender());
          goodDayGreeter.tell(new Greet(), ActorRef.noSender());
    
          System.out.println(">>> Press ENTER to exit <<<");
          System.in.read();
        } catch (IOException ioe) {
        } finally {
          system.terminate();
        }
      }
    }
    

    作为另一个最佳实践,我们应该提供一些单元测试。

    测试 Actor

    Hello World示例中的测试展示了 JUnit 框架的使用。虽然测试的覆盖范围不完整,但它简单地展示了测试 Actor 代码是多么的容易,并提供了一些基本概念。你可以把它作为一个练习来增加你自己的知识。

    测试类使用的是akka.test.javadsl.TestKit,它是用于 Actor 和 Actor 系统集成测试的模块。这个类只使用了TestKit提供的一部分功能。

    集成测试可以帮助我们确保 Actor 的行为是异步的。第一个测试使用TestKit探针来询问和验证预期的行为。让我们看看源代码片段:

    package com.lightbend.akka.sample;
    
    import static org.junit.Assert.assertEquals;
    
    import org.junit.AfterClass;
    import org.junit.BeforeClass;
    import org.junit.Test;
    
    import com.lightbend.akka.sample.Greeter.Greet;
    import com.lightbend.akka.sample.Greeter.WhoToGreet;
    import com.lightbend.akka.sample.Printer.Greeting;
    
    import akka.actor.ActorRef;
    import akka.actor.ActorSystem;
    import akka.testkit.javadsl.TestKit;
    
    public class AkkaQuickstartTest {
        static ActorSystem system;
    
        @BeforeClass
        public static void setup() {
            system = ActorSystem.create();
        }
    
        @AfterClass
        public static void teardown() {
            TestKit.shutdownActorSystem(system);
            system = null;
        }
    
        @Test
        public void testGreeterActorSendingOfGreeting() {
            final TestKit testProbe = new TestKit(system);
            final ActorRef helloGreeter = system.actorOf(Greeter.props("Hello", testProbe.getRef()));
            helloGreeter.tell(new WhoToGreet("Akka"), ActorRef.noSender());
            helloGreeter.tell(new Greet(), ActorRef.noSender());
            Greeting greeting = testProbe.expectMsgClass(Greeting.class);
            assertEquals("Hello, Akka", greeting.message);
        }
    }
    

    一旦我们引用了TestKit探针,我们就将它的ActorRef作为构造函数参数的一部分传递给Greeter。然后,我们向Greeter发送两条信息:一条是设置问候语,另一条是触发Greeting的发送。TestKitexpectMsg方法验证是否发送了消息。

    示例代码只涉及了TestKit功能的一小部分,在「这里」可以找到更完整的概述。

    现在我们已经检查了所有代码。让我们再次运行该示例并查看其输出。

    运行应用程序

    你可以通过命令行或者 IDE 来运行Hello World应用程序。在本指南的最后一个主题,我们描述了如何在 IntelliJ IDEA 中运行该示例。但是,在我们再次运行应用程序之前,让我们先快速的查看构建文件。

    • Maven POM 文件
    <project>
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>hello-akka-java</groupId>
        <artifactId>app</artifactId>
        <version>1.0</version>
    
        <properties>
          <akka.version>$akka_version$</akka.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>com.typesafe.akka</groupId>
                <artifactId>akka-actor_2.12</artifactId>
                <version>\${akka.version}</version>
            </dependency>
            <dependency>
                <groupId>com.typesafe.akka</groupId>
                <artifactId>akka-testkit_2.12</artifactId>
                <version>\${akka.version}</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.5.1</version>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>exec-maven-plugin</artifactId>
                    <version>1.6.0</version>
                    <configuration>
                        <executable>java</executable>
                        <arguments>
                            <argument>-classpath</argument>
                            <classpath />
                            <argument>com.lightbend.akka.sample.AkkaQuickstart</argument>
                        </arguments>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </project>
    
    • Grade 构建文件
    apply plugin: 'java'
    apply plugin: 'idea'
    apply plugin: 'application'
    
    
    repositories {
        mavenCentral()
        mavenLocal()
    }
    
    dependencies {
      compile 'com.typesafe.akka:akka-actor_2.12:$akka_version$'
      testCompile 'com.typesafe.akka:akka-testkit_2.12:$akka_version$'
      testCompile 'junit:junit:4.12'
    }
    
    mainClassName = "com.lightbend.akka.sample.AkkaQuickstart"
    
    run {
      standardInput = System.in
    }
    

    运行项目

    和前面一样,从控制台运行应用程序:

    //  Maven
    $ mvn compile exec:exec
    
    // Grade
    $ gradle run
    

    输出应该如下所示(一直向右滚动以查看 Actor 输出):

    // Maven
    [INFO] Scanning for projects...
    [INFO]
    [INFO] ------------------------------------------------------------------------
    [INFO] Building app 1.0
    [INFO] ------------------------------------------------------------------------
    [INFO]
    [INFO] --- exec-maven-plugin:1.6.0:exec (default-cli) @ app ---
    >>> Press ENTER to exit <<<
    [INFO] [05/11/2017 14:07:20.790] [helloakka-akka.actor.default-dispatcher-2] [akka://helloakka/user/printerActor] Hello, Java
    [INFO] [05/11/2017 14:07:20.791] [helloakka-akka.actor.default-dispatcher-2] [akka://helloakka/user/printerActor] Good day, Play
    [INFO] [05/11/2017 14:07:20.791] [helloakka-akka.actor.default-dispatcher-2] [akka://helloakka/user/printerActor] Howdy, Akka
    [INFO] [05/11/2017 14:07:20.791] [helloakka-akka.actor.default-dispatcher-2] [akka://helloakka/user/printerActor] Howdy, Lightbend
    
    // Grade
    :compileJava UP-TO-DATE
    :processResources NO-SOURCE
    :classes UP-TO-DATE
    :run
    >>> Press ENTER to exit <<<
    [INFO] [05/11/2017 14:08:22.884] [helloakka-akka.actor.default-dispatcher-2] [akka://helloakka/user/printerActor] Howdy, Akka
    [INFO] [05/11/2017 14:08:22.884] [helloakka-akka.actor.default-dispatcher-2] [akka://helloakka/user/printerActor] Good day, Play
    [INFO] [05/11/2017 14:08:22.884] [helloakka-akka.actor.default-dispatcher-2] [akka://helloakka/user/printerActor] Hello, Java
    [INFO] [05/11/2017 14:08:22.884] [helloakka-akka.actor.default-dispatcher-2] [akka://helloakka/user/printerActor] Howdy, Lightbend
    <=========----> 75% EXECUTING
    > :run
    

    还记得我们设置 Printer Actor 使用 Akka 的 Logger 吗?这就是为什么我们记录东西时会有很多额外的信息。日志输出包含诸如何时和从哪个 Actor 记录日志之类的信息。现在,让我们将重点放在 Printer Actor 的输出上:

    ... Howdy, Akka
    ... Hello, Java
    ... Good day, Play
    ... Howdy, Lightbend
    

    这是我们的代码向 Greeter Actor 发送消息的结果:

    howdyGreeter.tell(new WhoToGreet("Akka"), ActorRef.noSender());
    howdyGreeter.tell(new Greet(), ActorRef.noSender());
    
    howdyGreeter.tell(new WhoToGreet("Lightbend"), ActorRef.noSender());
    howdyGreeter.tell(new Greet(), ActorRef.noSender());
    
    helloGreeter.tell(new WhoToGreet("Java"), ActorRef.noSender());
    helloGreeter.tell(new Greet(), ActorRef.noSender());
    
    goodDayGreeter.tell(new WhoToGreet("Play"), ActorRef.noSender());
    goodDayGreeter.tell(new Greet(), ActorRef.noSender());
    

    为了执行单元测试,我们输入test命令:

    //  Maven
    $ mvn test
    
    // Grade
    $ gradle test
    

    尝试多次运行代码,并观察日志的顺序,你注意到它们的输入顺序发生变化了吗?这里发生了什么?异步行为变得很明显。这可能是你的一个新思维模式。但是,一旦你获得了使用它的经验,一切都会变得清晰;就像「Neo in the Matrix」一样。

    下一步

    如果你使用 IntelliJ,请尝试将示例项目与 IntelliJ IDEA 集成。

    想要继续了解更多有关 Akka 和 Actor Systems 的信息,请参阅「Getting Started Guide」,欢迎你加入我们!

    IntelliJ IDEA

    JetBrains 的 IntelliJ 是 Java/Scala 社区中领先的 IDE 之一,它对 Akka 有着极好的支持。本节将指导你完成示例项目的设置、测试和运行。

    设置项目

    设置项目很简单。打开 IntelliJ 并选择File -> Open...并指向你安装示例项目的目录。

    检查项目代码

    如果我们打开文件src/main/java/com/lightbend/akka/sample/HelloAkka.java,我们将看到许多行以//# ...开头,作为文档的注释。为了从源代码中去掉这些行,我们可以在 IntelliJ 中使用出色的查找/替换功能。选择Edit -> Find -> Replace in Path...,选中Regex框并添加[//#].*正则表达式,然后单击查找窗口中的Replace in Find Window...。选择想要替换的内容,并对所有文件重复此操作。

    测试和运行

    对于测试,我们只需右键单击文件src/test/java/com/lightbend/akka/sample/HelloAkkaTest.java,然后选择Run 'HelloAkkaTest'

    类似地运行应用程序,我们右击文件src/main/java/com/lightbend/akka/sample/HelloAkka.java,并选择Run 'HelloAkka.main()'

    有关更多详细信息,请参阅「运行应用程序」部分。

    想要进一步了解 IntelliJ IDEA,可以参阅「史上最简单的 IntelliJ IDEA 教程」系列文章。


    英文原文链接Akka Quickstart with Java.


    ———— ☆☆☆ —— 返回 -> Akka 中文指南 <- 目录 —— ☆☆☆ ————

    展开全文
  • akka

    2019-11-28 16:43:24
    https://blog.csdn.net/liubenlong007/article/details/53782966 一个akka学习地址,收藏慢慢看

    https://blog.csdn.net/liubenlong007/article/details/53782966
    一个akka学习地址,收藏慢慢看

    展开全文
  • 25Akka

    2021-01-07 09:54:01
    1.Akka Akka是spark中提供的通信模型。 Akka特点:高可靠.,高性能,高容错,可扩展的分布式RPC功能 所有的通信模型都是基于Actor。 spark里的角色有两个:master和worker,集群先启动master,然后再启动worker, worker...
  • akka-study:akkaの勉强
  • akka-grpc:Akka gRPC-源码

    2021-02-05 14:52:40
    akka-grpc 支持在Akka Streams上构建流gRPC服务器和客户端。 该库旨在用作使用Akka工具包的项目中的构建块。 文献资料 文档 项目状态 该库可以在生产中使用,但是API和构建系统插件仍有望得到改进,并且。 双方...
  • Akka作为一个天生用于构建分布式应用的工具,当然提供了用于分布式组件即Akka Remote,那么我们就来看看如何用Akka Remote以及Akka Serialization来构建分布式应用。背景很多同学在程序的开发中都会遇到一个问题,当...

    Akka作为一个天生用于构建分布式应用的工具,当然提供了用于分布式组件即Akka Remote,那么我们就来看看如何用Akka Remote以及Akka Serialization来构建分布式应用。

    背景

    很多同学在程序的开发中都会遇到一个问题,当业务需求变得越来越复杂,单机服务器已经不足以承载相应的请求的时候,我们都会考虑将服务部署到不同的服务器上,但服务器之间可能需要相互调用,那么系统必须拥有相互通信的接口,用于相应的数据交互,这时候一个好的远程调用方案是一个绝对的利器,主流的远程通信有以下几种选择:

    RPC(Remote Procedure Call Protocol)

    Web Service

    RMI (Remote Method Invocation)

    JMS(Java Messaging Service)

    这几种方式都是被采用比较广泛的通信方案,有兴趣的同学可以自己去了解一下,这里我会讲一下RMI和JMS。

    JAVA远程调用

    RMI和JMS相信很多写过Java程序的同学都知道,是Java程序用来远程通信的主要方式,那么RMI和JMS又有什么区别呢?

    1.RMI

    i.特征:

    同步通信:在使用RMI调用远程方法时,线程会持续等待直到结果返回,所以它是一个同步阻塞操作;

    强耦合:请求的系统中需要使用的RMI服务进行接口声明,返回的数据类型有一定的约束;

    ii.优点:

    实现相对简单,方法调用形式通俗易理解,接口声明服务功能清晰。

    iii.缺点:

    只局限支持JVM平台;

    对无法兼容Java语言的其他语言也不适用;

    2.JMS

    i.特征:

    异步通信:JMS发送消息进行通信,在通信过程中,线程不会被阻塞,不必等待请求回应,所以是一个异步操作;

    松耦合:不需要接口声明,返回的数据类型可以是各种各样,比如JSON,XML等;

    ii.通信方式:

    (1)点对点消息传送模型

    顾名思义,点对点可以理解为两个服务器的定点通信,发送者和接收者都能明确知道对方是谁,大致模型如下:

    d660c04d959cd106def44aec22defa07.gif

    (2)发布/订阅消息传递模型

    点对点模型有些场景并不是很适用,比如有一台主服务器,它产生一条消息需要让所有的从服务器都能收到,若采用点对点模型的话,那主服务器需要循环发送消息,后续若有新的从服务器增加,还要改主服务器的配置,这样就会导致不必要的麻烦,那么发布/订阅模型是怎么样的呢?其实这种模式跟设计模式中的观察者模式很相似,相信很多同学都很熟悉,它最大的特点就是较松耦合,易扩展等特点,所以发布/订阅模型的大致结构如下:

    8e72514ae96b0e5cee28ba38ef488579.gif

    iii.优点:

    由于使用异步通信,不需要线程暂停等待,性能相对较高。

    iiii.缺点:

    技术实现相对复杂,并需要维护相关的消息队列;

    更通俗的说:

    RMI可以看成是用打电话的方式进行信息交流,而JMS更像是发短信。

    总的来说两种方式没有孰优孰劣,我们也不用比较到底哪种方式比较好,存在即合理,更重要的是哪种选择可能更适合你的系统。

    Akka Remote

    上面讲到JAVA中远程通信的方式,但我们之前说过Akka也是基于JVM平台的,那么它的通信方式又有什么不同呢?

    在我看来,Akka的远程通信方式更像是RMI和JMS的结合,但更偏向于JMS的方式,为什么这么说呢,我们先来看一个示例:

    我们先来创建一个远程的Actor:

    class RemoteActor extends Actor {

    def receive = {

    case msg: String =>

    println(s"RemoteActor received message '$msg'")

    sender ! "Hello from the RemoteActor"

    }

    }

    现在我们在远程服务器上启动这个Actor:

    val system = ActorSystem("RemoteDemoSystem")

    val remoteActor = system.actorOf(Props[RemoteActor], name = "RemoteActor")

    那么现在我们假如有一个系统需要向这个Actor发送消息应该怎么做呢?

    首先我们需要类似RMI发布自己的服务一样,我们需要为其他系统调用远程Actor提供消息通信的接口,在Akka中,设置非常简单,不需要代码侵入,只需简单的在配置文件里配置即可:

    akka {

    actor {

    provider = "akka.remote.RemoteActorRefProvider"

    }

    remote {

    enabled-transports = ["akka.remote.netty.tcp"]

    netty.tcp {

    hostname = $localIp //比如127.0.0.1

    port = $port //比如2552

    }

    log-sent-messages = on

    log-received-messages = on

    }

    }

    我们只需配置相应的驱动,传输方式,ip,端口等属性就可简单完成Akka Remote的配置。

    当然本地服务器也需要配置这些信息,因为Akka之间是需要相互通信的,当然配置除了hostname有一定的区别外,其他配置信息可一致,本例子是在同一台机器上,所以这里hostname是相同的。

    这时候我们就可以在本地的服务器向这个Actor发送消息了,首先我们可以创建一个本地的Actor:

    case object Init

    case object SendNoReturn

    class LocalActor extends Actor{

    val path = ConfigFactory.defaultApplication().getString("remote.actor.name.test")

    implicit val timeout = Timeout(4.seconds)

    val remoteActor = context.actorSelection(path)

    def receive: Receive = {

    case Init => "init local actor"

    case SendNoReturn => remoteActor ! "hello remote actor"

    }

    }

    其中的remote.actor.name.test的值为:“akka.tcp://RemoteDemoSystem@127.0.0.1:4444/user/RemoteActor”,另外我们可以看到我们使用了context.actorSelection(path)来获取的是一个ActorSelection对象,若是需要获得ActorRef,我们可以调用它的resolveOne(),它返回的是是一个Future[ActorRef],这里是不是很熟悉,因为它跟本地获取Actor方式是一样的,因为Akka中Actor是位置透明的,获取本地Actor和远程Actor是一样的。

    最后我们首先启动远程Actor的系统:

    object RemoteDemo extends App {

    val system = ActorSystem("RemoteDemoSystem")

    val remoteActor = system.actorOf(Props[RemoteActor], name = "RemoteActor")

    remoteActor ! "The RemoteActor is alive"

    }

    然后我们在本地系统中启动这个LocalActor,并向它发送消息:

    object LocalDemo extends App {

    implicit val system = ActorSystem("LocalDemoSystem")

    val localActor = system.actorOf(Props[LocalActor], name = "LocalActor")

    localActor ! Init

    localActor ! SendNoReturn

    }

    我们可以看到RemoteActor收到了一条消息:

    6c76ceb3fdf68c38bcd4772f3ec17bae.png

    从以上的步骤和结果看出可以看出,Akka的远程通信跟JMS的点对点模式似乎更相似一点,但是它有不需要我们维护消息队列,而是使用Actor自身的邮箱,另外我们利用context.actorSelection获取的ActorRef,可以看成远程Actor的副本,这个又和RMI相关概念类似,所以说Akka远程通信的形式上像是RMI和JMS的结合,当然底层还是通过TCP、UDP等相关网络协议进行数据传输的,从配置文件的相应内容便可以看出。

    上述例子演示的是sendNoReturn的模式,那么假如我们需要远程Actor给我们一个回复应该怎么做呢?

    首先我们创建一个消息:

    case object SendHasReturn

    def receive: Receive = {

    case SendHasReturn =>

    for {

    r

    } yield r

    }

    我们重新运行LocalActor并像RemoteActor发送一条消息:

    a00ebc4fe70b93a0041dbfefcd3289c1.png

    可以看到LocalActor在发送消息后并收到了RemoteActor返回来的消息,另外我们这里设置了超时时间,若在规定的时间内没有得到反馈,程序就会报错。

    Akka Serialization

    其实这一部分本可以单独拿出来写,但是相信序列化这块大家都应该有所了解了,所以就不准备讲太多序列化的知识了,怕班门弄斧,主要讲讲Akka中的序列化。

    继续上面的例子,假如我们这时向RemoteActor发送一个自定义的对象,比如一个case class对象,但是我们这是是在网络中传输这个消息,那么怎么保证这个对象类型和值呢,在同一个JVM系统中我们不需要担心这个,因为对象就在堆中,我们只要传递相应的地址即可就行,但是在不同的环境中,我们并不能这么做,我们在网络中只能传输字节数据,所以我们必须将对象做特殊的处理,在传输的时候转化成特定的由一连串字节组成的数据,而且我们又可以根据这些数据恢复成一个相应的对象,这便是序列化。

    我们先定义一个参与的case class, 并修改一下上面发送消息的语句:

    case object SendSerialization

    case class JoinEvt(

    id: Long,

    name: String

    )

    def receive: Receive = {

    case SendSerialization =>

    for {

    r

    } yield println(r)

    }

    这时我们重新启动RemoteActor和LocalActor所在的系统,发送这条消息:

    c28fc26605e5e31835c7480a97500f56.png

    有同学可能会觉得奇怪,我们明明没有对JoinEvt进行过任何序列化的标识和处理,为什么程序还能运行成功呢?

    其实不然,只不过是有人替我们默认做了,不用说,肯定是贴心的Akka,它为我们提供了一个默认的序列化策略,那就是我们熟悉又纠结的java.io.Serializable,沉浸在它的易使用性上,又对它的性能深恶痛绝,尤其是当有大量对象需要传输的分布式系统,如果是小系统,当我没说,毕竟存在即合理。

    又有同学说,既然Akka是一个天生分布式组件,为什么还用低效的java.io.Serializable,你问我我也不知道,可能当时的作者偷了偷懒,当然Akka现在可能觉醒了,首先它支持第三方的序列化工具,当然如果你有特殊需求,你也可以自己实现一个,而且在最新的文档中说明,在Akka 2.5x之后Akka内核消息全面废弃java.io.Serializable,用户自定义的消息暂时还是支持使用java.io.Serializable的,但是不推荐用,因为它是低效的,容易被攻击,所以在这里我也推荐大家再Akka中尽量不要在使用了java.io.Serializable。

    那么在Akka中我们如何使用第三方的序列化工具呢?

    这里我推荐一个在Java社区已经久负盛名的序列化工具:kryo,有兴趣的同学可以去了解一下:kryo,而且它也提供Akka使用的相关包,这里我们就使用它作为示例:

    这里我贴上整个项目的build.sbt, kryo的相关依赖也在里面:

    import sbt._

    import sbt.Keys._

    lazy val AllLibraryDependencies =

    Seq(

    "com.typesafe.akka" %% "akka-actor" % "2.5.3",

    "com.typesafe.akka" %% "akka-remote" % "2.5.3",

    "com.twitter" %% "chill-akka" % "0.8.4"

    )

    lazy val commonSettings = Seq(

    name := "AkkaRemoting",

    version := "1.0",

    scalaVersion := "2.11.11",

    libraryDependencies := AllLibraryDependencies

    )

    lazy val remote = (project in file("remote"))

    .settings(commonSettings: _*)

    .settings(

    // other settings

    )

    lazy val local = (project in file("local"))

    .settings(commonSettings: _*)

    .settings(

    // other settings

    )

    然后我们只需将application.conf中的actor配置替换成以下的内容:

    actor {

    provider = "akka.remote.RemoteActorRefProvider"

    serializers {

    kryo = "com.twitter.chill.akka.AkkaSerializer"

    }

    serialization-bindings {

    "java.io.Serializable" = none

    "scala.Product" = kryo

    }

    }

    其实其中的"java.io.Serializable" = none可以省略,因为若是有其他序列化的策略则会替换掉默认的java.io.Serializable的策略,这里只是为了更加仔细的说明。

    至此我们就可以使用kryo了,整个过程是不是很easy,迫不及待开始写demo了,那就快快开始吧。

    整个例子的相关的源码已经上传到akka-demo中:源码链接

    展开全文
  • akka_Akka基础知识

    2020-05-21 12:45:52
    akka 如前 一篇 文章所述,我们的系统基于Akka 。 在详细介绍我们的解决方案之前,我想先解释一下Akka的含义以及为何如此出色。 Akka是一个工具包,而不是一个框架,您可以简单地使用服务所需的位。 在本系列中,...

    akka

    一篇 文章所述,我们的系统基于Akka 在详细介绍我们的解决方案之前,我想先解释一下Akka的含义以及为何如此出色。

    Akka是一个工具包,而不是一个框架,您可以简单地使用服务所需的位。 在本系列中,我们将重点介绍核心功能,并且不会涉及Akka StreamsAkka-HttpAkka Cluster (因为它们不在范围内,不是因为它们并不出色)。

    Akka提供了不同的抽象来处理并发,并行运算和容错。 这种抽象称为演员模型。 如果您有使用旧Java生态系统的经验,您将知道编写安全且正确的多线程代码有多么困难。 提供像Actors这样的人性化抽象是可与引入时的Java内存管理相比的一场革命。 互联网规模和云计算带来的新挑战需要一种React性的编程方法。 Akka遵循这一理念,使您能够实现事件驱动的架构

    一路演员

    线程是一种昂贵的资源,因此我们需要谨慎使用它们。 在我们等待某些I / O操作响应时阻塞线程确实效率很低。 Actor以不同的方式使用线程,因此它们非常轻量( 每GB堆内存数百万个actor

    Actor很好地融合了面向对象和功能编程原则。 OOP基本上是关于邮件作为艾伦凯,OOP的先驱之一, 指出

    很抱歉,我很久以前为该主题创造了“对象”一词,因为它使许多人专注于较小的想法。 最大的想法是“消息传递”。

    参与者交换不可变的消息并保持其自身的封装状态:每次交互都需要通过消息来完成。

    容错能力

    Java中没有清晰,共享的错误处理模型,您可以在几个项目中工作后才能意识到这一点。 它的起源是一个令人困惑的抽象,称为Exception及其子类型Checked和Unchecked Exceptions。 唯一真正的区别是,已检查异常会迫使我们在直接调用方中处理问题,只是它们没有暗示我们应采用的策略。

    我强烈推荐这篇文章。 帖子中如此强大的想法之一是,错误不是可恢复的错误。 此报价包含在该帖子的原始来源中

    我参与了用C ++编写的库的开发。 一位开发人员告诉我,开发人员分为喜欢异常的开发人员和喜欢返回代码的其他开发人员。 在我看来,返回码的朋友赢得了。 但是,我得到的印象是,他们在错误的观点上进行了辩论:异常和返回代码具有同等的表现力,但是不应将其用于描述错误。 实际上,返回码包含诸如ARRAY_INDEX_OUT_OF_RANGE定义。 但我想知道:当函数从子例程获取此返回代码时,我的函数将如何React? 它将发送邮件给其程序员吗? 它可以将此代码依次返回给其调用者,但它也不知道如何处理。 更糟糕的是,由于我无法对函数的实现进行假设,因此我必须期望每个子例程都有ARRAY_INDEX_OUT_OF_RANGE 我的结论是, ARRAY_INDEX_OUT_OF_RANGE是一个(编程)错误。 它不能在运行时处理或修复,只能由其开发人员修复。 因此,不应有相应的返回码,而应有断言。

    Akka基于以下原则提供了一种处理故障/错误的好方法:

    • 单一责任原则:将故障管理委托给主管,从而创建专注的业务参与者。 我们前段时间同意将生命周期管理(例如对象创建)移至工厂,因此对象不负责创建自身。 在发生某些故障之后,恢复或重新启动对象是生命周期管理的一部分,而Akka会强制您将职责移交给主管。 生成的代码将松散耦合且具有高度内聚性。
    • 默认恢复策略:在系统出现某些故障后,了解我们的选择非常重要。 数据库暂时关闭了吗? 是否将某些输入与现有数据结合起来创建了不良状态? 那是个错误吗? 在应用某些补丁之前,我们是否需要放弃该特定请求或将系统的那部分弄乱? 这些问题将决定我们对该事件的响应,而Akka提供了一些内置策略
    override val supervisorStrategy =
      OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1 minute) {
        case _: ArithmeticException      => Resume
        case _: NullPointerException     => Restart
        case _: IllegalArgumentException => Stop
        case _: Exception                => Escalate
      }
    • 作为头等公民的失败:一些图书馆或生态系统通过晦涩的API隐藏失败。 Akka通常部署在分布式环境中,因此使用诸如网络之类的不可靠资源。 这迫使我们把失败摆在最前面。 即使在“更安全”的环境中,故障也无处不在,因此使用Akka之类的工具包对于创建可靠而强大的软件至关重要。

    并发与并行

    使用Akka,我们不会直接处理线程,它们被隐藏在抽象层下。 Akka应用程序的主干是Actor系统

    角色系统是一组角色的层次结构组,它们共享通用配置,例如调度程序,部署,远程功能和地址。 它也是创建或查找参与者的入口。

    分派器也是执行上下文,因此最终它是线程池所在的位置。 假设您的应用使用分配了4个线程的单个调度程序。 如您所见,线程是一种稀缺资源,如果我们确实阻止了某些参与者中的I / O或繁重的CPU工作,我们将仅使用其中一个线程,并且您可以看到您的服务将很快用完线程。

    解决方案是使用Scala Futures之类的结构包装这些操作,然后为这些任务提供不同的执行上下文。

    摘要

    Akka是一个很棒的工具包,其中包含针对现代问题的非常适合的抽象。 在下一篇文章中,我们将借助一些代码来了解如何在示例应用程序的约束下协调和监督参与者。

    翻译自: https://www.javacodegeeks.com/2016/05/akka-basics.html

    akka

    展开全文
  • Akka学习笔记

    千次阅读 2017-12-15 16:45:13
    akka

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 6,856
精华内容 2,742
关键字:

Akka