akka actors使用_使用Akka Actors和Java 8构建React性应用程序

akka actors使用

重要要点

  • actor模型为编写并发和分布式系统提供了更高级别的抽象,从而使开发人员不必进行显式锁定和线程管理。
  • 参与者模型提供了React式系统的核心功能,在React式宣言中定义为响应式,弹性式,弹性式和消息驱动式。
  • Akka是基于参与者的框架,可通过可用的Java 8 Lambda支持轻松实现。
  • 使用Actor,开发人员可以通过设计和实现系统的方式,使我们更加专注于核心功能,而无需关注管道。
  • 基于Actor的系统是快速发展的微服务架构的理想基础

尽管“React性”一词已经存在了很长一段时间,但直到最近才被业界认可为系统设计事实上的前进方向,并已成为主流。 2014年,Gartner写道,曾经如此受欢迎的三层架构开始展现其时代。 随着企业推动的持续现代化努力,这一点变得更加清晰,企业不得不开始重新思考他们学习构建应用程序十多年的方式。

微服务正席卷软件行业,冲击波将传统的开发工作流程推向了核心。 我们已经看到软件设计范例发生了变化,项目管理方法也在不断发展。 但是,随之而来的向新应用程序设计和实现的转变一直在以空前的势头通过IT系统锻造它的方式。 即使微服务这个词不是一个全新的词,我们的行业也意识到,这不仅是关于耦合RESTful端点和切块。 真正的价值在于更好的资源消耗和针对不可预测的工作负载的极高可伸缩性。 响应式宣言的特征正在Swift成为基于微服务的体系结构的新圣经,因为它们本质上是分布式的响应式应用程序。

当今应用中的角色模型

应用程序必须具有很高的响应能力,以保留用户的兴趣,并且必须快速发展以保持相关性,以满足不断变化的需求和受众的期望。 可用于建筑应用的技术持续快速发展。 科学已经发展,新出现的需求不能依靠昨天的工具和方法论。 参与者模型已成为一种构架的有效工具,该构架利用了多核内存中集群环境所利用的处理能力。

actor模型提供了一个相对简单但功能强大的模型,用于设计和实现可在所有系统资源(从线程和核心到服务器和数据中心的群集)中分配和共享工作的应用程序。 它为构建具有高并发水平的应用程序和提高资源效率水平提供了有效的框架。 重要的是,参与者模型还具有定义良好的方式来优雅地处理错误和故障,从而确保一定程度的弹性,可以隔离问题并防止级联故障和大量停机。

过去,构建高度并发的系统通常涉及大量的底层布线和很难掌握的非常技术性的编程技术。 这些技术挑战吸引了系统核心业务功能的注意力,因为大部分工作必须集中在功能细节上,这需要大量的时间和精力来进行管道和接线。 当使用actor构建系统时,由于管道和接线已经内置在actor模型中,因此可以在更高的抽象水平上完成工作。 这不仅使我们从传统系统实现的繁琐细节中解放出来,还使我们可以更加专注于核心系统功能和创新。

具有Java 8和Akka的Actor

Akka是一个工具包和运行时,用于在JVM上构建高度并发,分布式和弹性消息驱动的应用程序。 Akka“角色”是Akka工具包中的工具之一,它使您无需考虑底层线程和锁就可以编写并发代码。 其他工具包括Akka Streams和Akka http。 尽管Akka是用Scala编写的,但也有Java API 。 以下示例适用于Akka 2.4.9及更高版本,但自Akka 2.3.0引入以来,具有Akka的lambda支持的Java部分被标记为“实验性”,因此请期待进一步的更改,直到正式发布并不再进行试验。

演员是阿卡族的基本工作单位。 演员是状态和行为的容器,可以创建和监督子演员。 Actor通过异步消息彼此交互,异步消息被同步处理,一次只处理一条消息。 此模型保护参与者的内部状态,使其线程安全,并实现不会阻止其他参与者的事件驱动行为。 首先,您只需要akka maven依赖项就可以了。

<dependency>
   <groupId>com.typesafe.akka</groupId>
   <artifactId>akka-actor_2.11</artifactId>
   <version>2.4.9</version>
</dependency>

改变演员状态

就像我们在移动设备上交换文本消息一样,我们使用消息来调用参与者。 就像文本消息一样,参与者之间的消息必须是不可变的。 与演员合作最重要的部分是定义它可以接收的消息。 (该消息通常也称为协议,因为它定义了参与者之间的交互点。)参与者接收消息并对消息做出各种React。 他们可以发送其他消息,更改其状态或行为并创建其他参与者。

演员的初始行为是通过在默认constructor. receive()matches中的ReceiveBuilder的帮助下实现receive()方法来定义的constructor. receive()matches constructor. receive()matches传入的消息并执行相关行为。 使用Lambda表达式定义每个消息的行为。 在以下示例中, ReceiveBuilder使用对接口方法“onMessage”的引用。 onMessage方法将增加一个计数器(内部状态)并通过AbstractLoggingActor.log方法记录一条信息消息。

static class Counter extends AbstractLoggingActor {
    static class Message { }

    private int counter = 0;

    {
      receive(ReceiveBuilder
        .match(Message.class, this::onMessage)
        .build()
      );
    }

    private void onMessage(Message message) {
      counter++;
      log().info("Increased counter " + counter);
    }
}

演员到位后,需要启动它。 这是通过ActorSystem完成的,该系统控制其中的actor的生命周期。 但是首先,我们需要提供有关如何启动actor的其他信息。 akka.actor.Props是一个配置对象,用于向框架的所有部分公开上下文范围的配置。 它用于创建演员。 它是不可变的,因此是线程安全的并且可以完全共享。

return Props.create(Counter.class);

Props对象描述了Props的构造函数参数。 好的做法是将其封装到更接近于actor的构造函数的工厂函数中。 ActorSystem本身是在main方法中创建的。

public static void main(String[] args) {
    ActorSystem system = ActorSystem.create("sample1");
    ActorRef counter = system.actorOf(Counter.props(), "counter");

}

ActorSystem (“sample1”)和所包含的actor (“counter”)都被命名为用于导航actor层次的名称。 稍后再详细介绍。 现在, ActorRef可以向演员发送消息,例如:

counter.tell(new Counter.Message(), ActorRef.noSender());

这里,两个参数定义了要发送的消息以及消息的发送者。 (顾名思义, noSender表示在这种情况下未使用发件人。)如果运行上述示例,则将获得预期的输出:

[01/10/2017 10:15:15.400] [sample1-akka.actor.default-dispatcher-4] [akka://sample1/user/counter] Increased counter 1

这是一个非常简单的示例,但是它提供了我们所需的所有线程安全性。 从不同线程发送到参与者的消息可以避免并发问题,因为参与者框架会序列化消息处理。 您可以在线找到完整的示例

改变演员的行为

您会注意到,我们的简单示例修改了参与者状态,但从未改变其行为,也从未向其他参与者发送消息。 考虑防盗警报系统的情况; 可以使用密码启用和禁用它,并且其传感器检测活动。 如果有人尝试在没有正确密码的情况下禁用警报,则会响起。 actor可以对以下三个消息作出React:禁用,使用密码启用(作为有效负载提供)以及入室盗窃活动。 他们全都签了合同:

static class Alarm extends AbstractLoggingActor {
    // contract
    static class Activity {}
    static class Disable {
      private final String password;
      public Disable(String password) {
        this.password = password;
      }
    }
    static class Enable {
      private final String password;
      public Enable(String password) {
        this.password = password;
      }
    }

    // ...
}

actor获得密码的预设属性,该属性也传递到构造函数中。

private final String password;
public Alarm(String password) {
      this.password = password;
     // ...
}

前面提到的akka​​.actor.Props配置对象还需要知道password属性,以便在actor系统启动时将其传递给实际的构造函数。

public static Props props(String password) {
      return Props.create(Alarm.class, password);
}

警报参与者还需要为每个可能的消息提供一个行为。 这些行为是AbstractActorreceive方法的实现。 receive方法应该定义一系列match语句(每种类型为PartialFunction<Object, BoxedUnit> ),定义actor可以处理的消息以及如何处理消息的实现。

private final PartialFunction<Object, BoxedUnit> enabled;
private final PartialFunction<Object, BoxedUnit> disabled;

如果此签名看起来令人恐惧,则您的代码可以使用ReceiveBuilder有效地将其ReceiveBuilder ,如前所述。

public Alarm(String password) {
      this.password = password;
      
     enabled = ReceiveBuilder
        .match(Activity.class, this::onActivity)
        .match(Disable.class, this::onDisable)
        .build();

      disabled = ReceiveBuilder
        .match(Enable.class, this::onEnable)
        .build();

      receive(disabled);
    }
}

请注意,最后receive的呼叫会将默认行为设置为“禁用”。 这三种行为都是使用三种现有方法( onActivity, onDisable, onEnable )实现的。 这些方法中最简单的是onActivity 。 如果有活动,警报将字符串记录到控制台。 请注意,活动不需要消息有效负载,因此我们将其名称忽略。

private void onActivity(Activity ignored) {
    log().warning("oeoeoeoeoe, alarm alarm!!!");
 }

如果actor收到enable消息,则新状态将记录到控制台,并且状态更改为enabled 。 如果密码不匹配,则会记录一条简短警告。 消息负载现在包含密码,我们可以访问它以验证密码。

private void onEnable(Enable enable) {
   if (password.equals(enable.password)) {
     log().info("Alarm enable");
     getContext().become(enabled);
   } else {
     log().info("Someone failed to enable the alarm");
   }
}

万一收到禁用消息,执行者需要检查密码,记录有关已更改状态的短信,然后将状态实际更改为禁用,或记录一条密码错误的警告消息。

private void onDisable(Disable disable) {
  if (password.equals(disable.password)) {
    log().info("Alarm disabled");
    getContext().become(disabled);
  } else {
    log().warning("Someone who didn't know the password tried to disable it");
  }
}

这样就完成了actor的逻辑,我们已经准备好启动actor系统并向其发送一些消息。 请注意,我们的秘密密码“ cats”作为属性传递给参与者系统。

ActorSystem system = ActorSystem.create();
    final ActorRef alarm = system.actorOf(Alarm.props("cat"), "alarm");

消息:

alarm.tell(new Alarm.Activity(), ActorRef.noSender());
    alarm.tell(new Alarm.Enable("dogs"), ActorRef.noSender());
    alarm.tell(new Alarm.Enable("cat"), ActorRef.noSender());
    alarm.tell(new Alarm.Activity(), ActorRef.noSender());
    alarm.tell(new Alarm.Disable("dogs"), ActorRef.noSender());
    alarm.tell(new Alarm.Disable("cat"), ActorRef.noSender());
    alarm.tell(new Alarm.Activity(), ActorRef.noSender());

产生如下输出:

[01/10/2017 10:15:15.400] [default-akka.actor.default-dispatcher-4] [akka://default/user/alarm] Someone failed to enable the alarm
[01/10/2017 10:15:15.401] [default-akka.actor.default-dispatcher-4] [akka://default/user/alarm] Alarm enable
[WARN] [01/10/2017 10:15:15.403] [default-akka.actor.default-dispatcher-4] [akka://default/user/alarm] oeoeoeoeoe, alarm alarm!!!
[WARN] [01/10/2017 10:15:15.404] [default-akka.actor.default-dispatcher-4] [akka://default/user/alarm] Someone who didn't know the password tried to disable it
[01/10/2017 10:15:15.404] [default-akka.actor.default-dispatcher-4] [akka://default/user/alarm] Alarm disabled

您可以在线找到完整的工作示例 。 到目前为止,我们仅使用单个参与者来处理消息。 但是就像在企业组织中一样,参与者形成自然的层次结构。

演员层次

演员可以创建其他演员。 当一个演员创建另一个演员时, 创建者称为主管 ,而创建的演员称为worker 。 可能会出于多种原因创建工人参与者,最常见的原因是委派工作。 主管创建一个或多个工人演员并将工作委托给他们。

主管也成为工人的看守人。 就像父母在照顾孩子的幸福一样,上司也倾向于工人的幸福。 如果工人遇到问题,它将暂停自身(这意味着它将在恢复之前不会处理正常的消息),并将故障通知主管。

到目前为止,我们已经创建了演员并分配了名称。 角色名称用于标识层次结构中的角色。 通常与之交互最多的actor是所有用户创建的actor的父级,即路径为"/user"的监护人。 使用primordial system.actorOf()创建的actor是此监护人的直接子级,并且当其终止时,系统中的所有正常actor也将被关闭。 在上面的警报示例中,创建了一个路径为/user/alarm的用户Actor。 由于演员是按照严格的分层方式创建的,因此存在一个唯一的演员名称序列,该顺序是通过递归地遵循子代与父代之间的监督链接向下直至演员系统的根源来给出的。 尽管参与者层次结构与文件系统层次结构有一些根本性的区别,但是该序列可以看作是将文件文件夹封闭在文件系统中,因此采用的名称“ path”来引用它。

在角色内部,您可以调用getContext().actorOf(props, “alarm-child”)创建一个名为“ alarm-child”的新角色作为警报actor的子角色。 子生命周期与监督角色绑定在一起,这意味着,如果您停止“警报”角色,它也会停止子角色:

此层次结构还对基于角色的系统中的故障处理方式具有重要影响。 角色系统的典型特征是任务被拆分和委派,直到它们变得足够小以至于可以一件一件地处理。 这样,不仅可以清楚地明确任务本身的结构,而且可以根据以下方面来推断产生的参与者:

  • 他们应该处理哪些消息
  • 他们应该如何正常React
  • 以及应该如何处理故障。

如果一个参与者没有处理特定情况的手段,它将向其主管发送相应的失败消息,以寻求帮助。 主管对于故障做出React有四个不同的选择:

  • 恢复孩子,保持其累积的内部状态,但忽略导致失败的消息。
  • 重新启动子级,通过启动新实例清除其累积的内部状态。
  • 永久停止孩子,并将以后所有有关该孩子的信息发送给Dead-Letter办公室
  • 上报故障,从而使主管本身失败

让我们用一个示例来具体化所有这些: NonTrustWorthyChild接收Command消息,并为每个消息增加一个内部计数器。 如果消息计数可被4整除,则会引发RuntimeException,该异常将升级为Supervisor。 这里没有什么新鲜的东西,因为命令消息没有有效负载。

public class NonTrustWorthyChild extends AbstractLoggingActor {

  public static class Command {}
  private long messages = 0L;

  {
    receive(ReceiveBuilder
      .match(Command.class, this::onCommand)
      .build()
    );
  }

  private void onCommand(Command c) {
    messages++;
    if (messages % 4 == 0) {
      throw new RuntimeException("Oh no, I got four commands, can't handle more");
    } else {
      log().info("Got a command " + messages);
    }
  }

  public static Props props() {
    return Props.create(NonTrustWorthyChild.class);
  }
}

Supervisor在其构造函数中启动NonTrustWorthyChild ,并将他收到的所有命令消息直接转发给子级。

public class Supervisor extends AbstractLoggingActor {
{
    final ActorRef child = getContext().actorOf(NonTrustWorthyChild.props(), "child");

    receive(ReceiveBuilder
      .matchAny(command -> child.forward(command, getContext()))
      .build()
    );

  }
  //…
}

Supervisor actor实际启动时,结果层次将为“/user/supervisor/child” 。 在此之前,需要定义所谓的监督策略。 Akka提供了两类监管策略: OneForOneStrategyAllForOneStrategy. 它们之间的区别在于,前者仅将获得的指令应用于失败的孩子,而后者也将其应用于所有兄弟姐妹。 通常,您应该使用OneForOneStrategy ,如果没有明确指定,则使用默认值。 它是通过重写SupervisorStrategy方法来定义的。

@Override
public SupervisorStrategy supervisorStrategy() {
   return new OneForOneStrategy(
      10,
      Duration.create(10, TimeUnit.SECONDS),
      DeciderBuilder
          .match(RuntimeException.class, ex -> stop())
          .build()
   );
}

The first parameter defines the maxNrOfRetries ,它是在停止子actor之前允许子actor重启的次数。 (负值表示无限制)。 所述withinTimeRange参数定义的时间窗的持续时间maxNrOfRetries 。 如上所述,该策略在10秒内尝试10次。 DeciderBuilder工作原理与ReceiveBuilder完全相同, ReceiveBuilder可以定义匹配发生的异常以及如何对它们进行响应。 在这种情况下,如果10秒内有10次重试,则Supervisor停止NonTrustWorthyChild并将所有剩余消息发送到NonTrustWorthyChild信箱。 。

角色系统由Supervisor角色启动。

ActorSystem system = ActorSystem.create();
final ActorRef supervisor = system.actorOf(Supervisor.props(), "supervisor");

系统启动后,我们开始向Supervisor发送10条命令消息。 请注意,消息“命令”是在NonTrustWorthyChild中定义的。

for (int i = 0; i < 10; i++) {
  supervisor.tell(new NonTrustWorthyChild.Command(), ActorRef.noSender());
}

输出显示,在收到四则消息之后,该异常会升级为Supervisor ,其余消息会发送到deadLetters框中。 如果将SupervisorStrategy定义为restart()而不是stop() ,则将启动NonTrustWorthyChild actor的新实例。

[01/10/2017 12:33:47.540] [default-akka.actor.default-dispatcher-3] [akka://default/user/supervisor/child] Got a command 1
[01/10/2017 12:33:47.540] [default-akka.actor.default-dispatcher-3] [akka://default/user/supervisor/child] Got a command 2
[01/10/2017 12:33:47.540] [default-akka.actor.default-dispatcher-3] [akka://default/user/supervisor/child] Got a command 3
[01/10/2017 12:33:47.548] [default-akka.actor.default-dispatcher-4] [akka://default/user/supervisor] Oh no, I got four commands, I can't handle any more
java.lang.RuntimeException: Oh no, I got four commands, I can't handle any more
	...
[01/10/2017 12:33:47.556] [default-akka.actor.default-dispatcher-3] [akka://default/user/supervisor/child] Message [com.lightbend.akkasample.sample3.NonTrustWorthyChild$Command] from Actor[akka://default/deadLetters] to Actor[akka://default/user/supervisor/child#-1445437649] was not delivered. [1] dead letters encountered.

可以使用配置设置'akka.log-dead-letters''akka.log-dead-letters-during-shutdown'来关闭或调整此日志记录。

您可以在线跟踪完整的示例,并使用SupervisorStrategy

摘要

借助Akka和Java 8,现在可以创建分布式的,基于微服务的系统,而这几年前就只是梦想。 现在,所有行业的企业都希望能够创建能够随着业务发展而发展并迎合用户异想天开的系统的能力。 现在,我们可以弹性扩展支持大量用户并处理大量数据的系统。 现在,可以通过一定程度的弹性来加固系统,从而使系统能够以小时而非秒为单位来衡量停机时间。 基于Actor的系统使我们能够创建快速发展的微服务体系结构,该体系结构可以不断扩展和运行。

参与者模型提供了React式系统的核心功能,在React式宣言中定义为响应式,弹性,弹性和消息驱动的。

actor系统可以从单个节点水平扩展到具有多个节点的集群,这一事实为我们提供了灵活地扩展系统以应对海量负载的灵活性。 另外,还可以实现具有弹性缩放能力的系统,即手动或自动缩放系统的能力,以充分支持系统活动的高峰和低谷。

对于参与者和参与者系统,故障检测和恢复是一项体系结构功能,不能在以后进行修补。 开箱即用,您可以获得角色监督策略,用于处理下级工作人员角色的问题,直至角色系统级别,并通过主动监视群集状态的节点群集将处理故障的过程融入角色和角色的DNA中。系统。 这从最基本的层次开始,即参与者之间的异步消息交换:如果您向我发送消息,则必须考虑可能的结果。 收到您期望的答复时,您会怎么做? 这一直朝着实施用于处理离开和加入集群的节点的策略的方向发展。

在设计系统时,在许多方面,以行为者的角度思考对于我们来说要直观得多。 行为者互动的方式对我们来说更自然,因为它在简单的层面上与我们作为人类的互动方式更为共通。 这使我们能够以允许我们更多地专注于系统的核心功能而更少关注管道的方式设计和实施系统。

翻译自: https://www.infoq.com/articles/Building-Reactive-Applications-with-Akka/?topicPageSponsorship=c1246725-b0a7-43a6-9ef9-68102c8d48e1

akka actors使用

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值