AKKA官方文档阅读笔记(2)JAVA版2.5.16

以下内容来自文档:https://doc.akka.io/docs/akka/2.5/guide/tutorial_4.html

Actor的最佳划分粒度是使用Akka的猿们需要重点设计的,不能太粗(Actor太少)也不能太细(Actor太多),通常会有多种可行方案,但是我们要找到最好的。

下面的几条准则可以帮我们设计出最好的Actor层级结构:

  • 总的来说,粗粒度更好一些,引入不必要的更多更小的Actor引起的问题会比它们解决的问题要多
  • 当系统存在以下需求的时候使用细粒度Actor:
    • 高并发
    • 具有许多状态的Actor之间存在复杂的交互。 我们将在下一章中看到一个很好的例子。
    • Actor的状态很多,细分成更多的Actor才有意义
    • 当一个Actor承担多个不相关的职责时。 将Actor细分可以使得它在失败并恢复的时候不影响其他Actor

官网给出了一个例子,假设我们要接收一些设备发送的温度数据,每个设备有自己的唯一ID(device id),同时分了若干个组,每个设备属于一个组,所以系统收到的温度数据中也会有组ID(group id),actor层级结构设计如下:
在这里插入图片描述

我们可以列出以下主要功能点:

  • DevideManager会收到所有带有group id和device id的消息,它要对每条消息:
    • 判断此group id是否有对应的Actor,有的话把消息发给它
    • 没有的话创建新Actor
  • DeviceGroup与DeviceManager做的事情类型,只是它使用device id判断它的下一级Actor
  • Device收到消息

下面这两个类分别是注册请求和注册成功的确认消息:

public static final class RequestTrackDevice {
  public final String groupId;
  public final String deviceId;

  public RequestTrackDevice(String groupId, String deviceId) {
    this.groupId = groupId;
    this.deviceId = deviceId;
  }
}

public static final class DeviceRegistered {
}

下面贴出代码,遵循从底层往高层的顺序,先Device,然后是DeviceGroup,然后是DeviceManager

Device

接收三类消息:

  1. 注册。返回注册成功
  2. 记录温度。将温度数据写入本身状态lastTemperatureReading
  3. 查询温度。返回lastTemperatureReading
import akka.actor.AbstractActor;
import akka.actor.Props;
import akka.event.Logging;
import akka.event.LoggingAdapter;

import jdocs.tutorial_4.DeviceManager.DeviceRegistered;
import jdocs.tutorial_4.DeviceManager.RequestTrackDevice;

import java.util.Optional;

public class Device extends AbstractActor {
  private final LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);

  final String groupId;

  final String deviceId;

  public Device(String groupId, String deviceId) {
    this.groupId = groupId;
    this.deviceId = deviceId;
  }

  public static Props props(String groupId, String deviceId) {
    return Props.create(Device.class, groupId, deviceId);
  }

  public static final class RecordTemperature {
    final long requestId;
    final double value;

    public RecordTemperature(long requestId, double value) {
      this.requestId = requestId;
      this.value = value;
    }
  }

  public static final class TemperatureRecorded {
    final long requestId;

    public TemperatureRecorded(long requestId) {
      this.requestId = requestId;
    }
  }

  public static final class ReadTemperature {
    final long requestId;

    public ReadTemperature(long requestId) {
      this.requestId = requestId;
    }
  }

  public static final class RespondTemperature {
    final long requestId;
    final Optional<Double> value;

    public RespondTemperature(long requestId, Optional<Double> value) {
      this.requestId = requestId;
      this.value = value;
    }
  }

  Optional<Double> lastTemperatureReading = Optional.empty();

  @Override
  public void preStart() {
    log.info("Device actor {}-{} started", groupId, deviceId);
  }

  @Override
  public void postStop() {
    log.info("Device actor {}-{} stopped", groupId, deviceId);
  }

  @Override
  public Receive createReceive() {
    return receiveBuilder()
            .match(RequestTrackDevice.class, r -> {
              if (this.groupId.equals(r.groupId) && this.deviceId.equals(r.deviceId)) {
                getSender().tell(new DeviceRegistered(), getSelf());
              } else {
                log.warning(
                        "Ignoring TrackDevice request for {}-{}.This actor is responsible for {}-{}.",
                        r.groupId, r.deviceId, this.groupId, this.deviceId
                );
              }
            })
            .match(RecordTemperature.class, r -> {
              log.info("Recorded temperature reading {} with {}", r.value, r.requestId);
              lastTemperatureReading = Optional.of(r.value);
              getSender().tell(new TemperatureRecorded(r.requestId), getSelf());
            })
            .match(ReadTemperature.class, r -> {
              getSender().tell(new RespondTemperature(r.requestId, lastTemperatureReading), getSelf());
            })
            .build();
  }
}

Device测试类

@Test
public void testReplyToRegistrationRequests() {
  TestKit probe = new TestKit(system);
  ActorRef deviceActor = system.actorOf(Device.props("group", "device"));

  deviceActor.tell(new DeviceManager.RequestTrackDevice("group", "device"), probe.getRef());
  probe.expectMsgClass(DeviceManager.DeviceRegistered.class);
  assertEquals(deviceActor, probe.getLastSender());
}

@Test
public void testIgnoreWrongRegistrationRequests() {
  TestKit probe = new TestKit(system);
  ActorRef deviceActor = system.actorOf(Device.props("group", "device"));

  deviceActor.tell(new DeviceManager.RequestTrackDevice("wrongGroup", "device"), probe.getRef());
  probe.expectNoMessage();

  deviceActor.tell(new DeviceManager.RequestTrackDevice("group", "wrongDevice"), probe.getRef());
  probe.expectNoMessage();
}

DeviceGroup

  • 处理注册请求,要么发送给已有的Actor,要么创建一个新的Actor
  • 跟踪已有的Actor,把停止了的移除

DeviceGroup内部维护了一个Map<String, ActorRef>,key是device id,用于根据设备ID判断相应的DeviceActor是否存在了并获取其引用

注意这个类里给DeviceActor发消息时没有使用tell,而是使用的forward,它们的区别是啥呢?tell有两个参数,第二个参数可以指定发送者,而forward只有一个参数,就是消息本身,不管经历几次中专,它会保持最初的发送者

public class DeviceGroup extends AbstractActor {
  private final LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);

  final String groupId;

  public DeviceGroup(String groupId) {
    this.groupId = groupId;
  }

  public static Props props(String groupId) {
    return Props.create(DeviceGroup.class, groupId);
  }

  final Map<String, ActorRef> deviceIdToActor = new HashMap<>();

  @Override
  public void preStart() {
    log.info("DeviceGroup {} started", groupId);
  }

  @Override
  public void postStop() {
    log.info("DeviceGroup {} stopped", groupId);
  }

  private void onTrackDevice(DeviceManager.RequestTrackDevice trackMsg) {
    if (this.groupId.equals(trackMsg.groupId)) {
      ActorRef deviceActor = deviceIdToActor.get(trackMsg.deviceId);
      if (deviceActor != null) {
        deviceActor.forward(trackMsg, getContext());
      } else {
        log.info("Creating device actor for {}", trackMsg.deviceId);
        deviceActor = getContext().actorOf(Device.props(groupId, trackMsg.deviceId), "device-" + trackMsg.deviceId);
        deviceIdToActor.put(trackMsg.deviceId, deviceActor);
        deviceActor.forward(trackMsg, getContext());
      }
    } else {
      log.warning(
              "Ignoring TrackDevice request for {}. This actor is responsible for {}.",
              groupId, this.groupId
      );
    }
  }

  @Override
  public Receive createReceive() {
    return receiveBuilder()
            .match(DeviceManager.RequestTrackDevice.class, this::onTrackDevice)
            .build();
  }
}

到目前为止,我们已经实现了在group中注册device的功能。还记得在group中维护的Map<String,ActorRef>吗,我们总不能只增不减吧。假设现在只要终端设备停止了,它对应的Actor就会停止,那问题来了,group怎么知道它下面的某个子Actor停止了呢?
Akka提供了一个叫Death Watch(死亡凝视哈哈哈哈哈)的功能,允许Actor监视watch(请把这个词和监管supervision区分开)其他Actor,在被监视的Actor停止时,监视者会收到通知Terminated(actorRef),里面包含着被监视者的引用。和监管不同,监视不仅限于在父子Actor之间使用。所以ActorGroup要增加如下功能:

  • watch每一个创建的DeviceActor
  • 当收到子Actor停止的消息时,把它从Map中移除

现在还有个问题是,group收到的通知里面只有子Actor的引用,而我们删除子Actor是需要它的设备ID,因为Map的key是设备ID,所以没办法了,再加一个反过来的Map吧:Map<ActorRef, String>

下面是改过之后的类,重点关注对Terminated.class类消息的处理:

public class DeviceGroup extends AbstractActor {
  private final LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);

  final String groupId;

  public DeviceGroup(String groupId) {
    this.groupId = groupId;
  }

  public static Props props(String groupId) {
    return Props.create(DeviceGroup.class, groupId);
  }

  final Map<String, ActorRef> deviceIdToActor = new HashMap<>();
  final Map<ActorRef, String> actorToDeviceId = new HashMap<>();

  @Override
  public void preStart() {
    log.info("DeviceGroup {} started", groupId);
  }

  @Override
  public void postStop() {
    log.info("DeviceGroup {} stopped", groupId);
  }

  private void onTrackDevice(DeviceManager.RequestTrackDevice trackMsg) {
    if (this.groupId.equals(trackMsg.groupId)) {
      ActorRef deviceActor = deviceIdToActor.get(trackMsg.deviceId);
      if (deviceActor != null) {
        deviceActor.forward(trackMsg, getContext());
      } else {
        log.info("Creating device actor for {}", trackMsg.deviceId);
        deviceActor = getContext().actorOf(Device.props(groupId, trackMsg.deviceId), "device-" + trackMsg.deviceId);
        getContext().watch(deviceActor);
        actorToDeviceId.put(deviceActor, trackMsg.deviceId);
        deviceIdToActor.put(trackMsg.deviceId, deviceActor);
        deviceActor.forward(trackMsg, getContext());
      }
    } else {
      log.warning(
              "Ignoring TrackDevice request for {}. This actor is responsible for {}.",
              groupId, this.groupId
      );
    }
  }

  private void onTerminated(Terminated t) {
    ActorRef deviceActor = t.getActor();
    String deviceId = actorToDeviceId.get(deviceActor);
    log.info("Device actor for {} has been terminated", deviceId);
    actorToDeviceId.remove(deviceActor);
    deviceIdToActor.remove(deviceId);
  }

  @Override
  public Receive createReceive() {
    return receiveBuilder()
            .match(DeviceManager.RequestTrackDevice.class, this::onTrackDevice)
            .match(Terminated.class, this::onTerminated)
            .build();
  }
}

DeviceManager

跟DeviceGroup很类似的

public class DeviceManager extends AbstractActor {
  private final LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);

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

  public static final class RequestTrackDevice {
    public final String groupId;
    public final String deviceId;

    public RequestTrackDevice(String groupId, String deviceId) {
      this.groupId = groupId;
      this.deviceId = deviceId;
    }
  }

  public static final class DeviceRegistered {
  }

  final Map<String, ActorRef> groupIdToActor = new HashMap<>();
  final Map<ActorRef, String> actorToGroupId = new HashMap<>();

  @Override
  public void preStart() {
    log.info("DeviceManager started");
  }

  @Override
  public void postStop() {
    log.info("DeviceManager stopped");
  }

  private void onTrackDevice(RequestTrackDevice trackMsg) {
    String groupId = trackMsg.groupId;
    ActorRef ref = groupIdToActor.get(groupId);
    if (ref != null) {
      ref.forward(trackMsg, getContext());
    } else {
      log.info("Creating device group actor for {}", groupId);
      ActorRef groupActor = getContext().actorOf(DeviceGroup.props(groupId), "group-" + groupId);
      getContext().watch(groupActor);
      groupActor.forward(trackMsg, getContext());
      groupIdToActor.put(groupId, groupActor);
      actorToGroupId.put(groupActor, groupId);
    }
  }

  private void onTerminated(Terminated t) {
    ActorRef groupActor = t.getActor();
    String groupId = actorToGroupId.get(groupActor);
    log.info("Device group actor for {} has been terminated", groupId);
    actorToGroupId.remove(groupActor);
    groupIdToActor.remove(groupId);
  }

  public Receive createReceive() {
    return receiveBuilder()
            .match(RequestTrackDevice.class, this::onTrackDevice)
            .match(Terminated.class, this::onTerminated)
            .build();
  }

}

在下一章中,我们将介绍组查询功能,它将建立一个新的Actor交互模式:分散 - 聚集。 强调:它有个强大的功能,允许用户查询属于组的所有设备的状态!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
内容简介 本书将尝试帮助入门级、中级以及高级读者理解基本的分布式计算概念,并且展示 如何使用 Akka 来构建具备高容错性、可以横向扩展的分布式网络应用程序。Akka 是一 个强大的工具集,提供了很多选项,可以对在本地机器上处理或网络远程机器上处理的 某项工作进行抽象封装,使之对开发者不可见。本书将介绍各种概念,帮助读者理解 网络上各系统进行交互的困难之处,并介绍如何使用 Akka 提供的解决方案来解决这些 问题。 作者简介 Jason Goodwin 是一个基本上通过自学成才的开发者。他颇具企业家精神,在学校 学习商学。不过他从 15 岁起就开始学习编程,并且一直对技术保持着浓厚的兴趣。这对 他的职业生涯产生了重要的影响,从商学转向了软件开发。现在他主要从事大规模分布 式系统的开发。在业余时间,他喜欢自己原创电子音乐。 他在 mDialog 公司第一次接触到 Akka 项目。mDialog 是一家使用 Scala/Akka 的公司, 为主流出商提供视频广告插入软件。这家公司最终被 Google 收购。他同时还是一名很 有影响力的“技术控”,将 Akka 引入加拿大一家主要的电信公司,帮助该公司为客户提 供容错性更高、响应更及时的软件。除此之 外,他还为该公司中的一些团队教授 Akka、 函数式以及并发编程等知识。 目录 第 1 章 初识 Actor:Akka 工具集以及 Actor 模型的介绍。 第 2 章 Actor 与并发:响应式编程。Actor 与 Future 的使用。 第 3 章 传递消息:消息传递模式。 第 4 章 Actor 的生命周期—处理状态与错误:Actor 生命周期、监督机制、Stash/ Unstash、Become/Unbecome 以及有限自动机。 第 5 章 纵向扩展:并发编程、Router Group/Pool、Dispatcher、阻塞 I/O 的处理以 及 API。 第 6 章 横向扩展—集群化:集群、CAP 理论以及 Akka Cluster。 第 7 章 处理邮箱问题:加大邮箱负载、不同邮箱的选择、熔断机制。 第 8 章 测试与设计:行为说明、领域驱动设计以及 Akka Testkit。 第 9 章 尾声:其他 Akka 特性。下一步需要学习的知识。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值