Scala---Akka Actor (一)tutorial-1

Akka Actor作为Scala并发编程中,最为关键的技术。对于一个学习Java编程的人来说,是一个非常优秀的并发编程框架,值得学习研究。

同时,在进行分布式学习的过程中,Akka也提供了相当友好的解决方案,所以掌握Actor技术是非常有价值的,它会帮助编程人员写出高性能、可伸缩、可测试的分布式应用。

得益于Actor告别了传统的Java并发编程,编程者不再需要使用底层的同步策略来去维护一个对象的状态(防止出现invalid状态)。告别锁、同步这些难以把握的Java特性,同时也不需要编程者编写高难度CAS代码(性能是不错),甚至一些non-blocked、asynchronous IO都已经被Akka IO解决好了。我们可以信赖依靠Akka,开发出更好的并发程序。


这边文章,我就Scala官网的quickstart案例,进行分析总结。如果英文水平允许,非常建议直接看官方文档,本文只是本人的个人观点。

官网案例讲解地址

进入正题。

源代码下载


(一)项目需求

在这个案例当中,我们要完成一个关于物联网的项目。我们现在有许多温度监控器,若干个温度监控器组成一个监控器组,系统将管理这些监控器组。在另一方面,用户可以实时获取制定温度监控器的信息,或者是监控器组的全部温度监控器的信息。下图是需求展示:



(二)项目框架

我们要使用Akka的Actor框架解决上面的问题。首先,我们先一起简单了解一下Actor的一些基础知识。

(1)Actor的层级结构

在Actor框架中,所有的actor实例(用户的,系统的)都是进行分级的。下图是分级图:


在上图中,/user对应的actor(通常名为system)是用户使用ActorSystem()方法创建出来的,system下面的所有actor都是用户自己创建的,完成用户特定的业务逻辑。从图中我们可以初步了解actorOf()方法是用来从一个actor中创建出一个新的actor。新创建的actor地址在父actor之下。

(2)Actor的生命周期

一个actor具有自己的声明周期,例如我们可以重写preStart(),使一个actor在被创建之前,调用该函数。类似的函数还有postStop()。

一个父actor停止运行,如下代码:context.stop(self),将会是其所有子actor结束执行。

(3)Actor的监控模式

一个父actor可以监控其子actor,并完成一些错误策略。
例如,context.watch(childActor),父actor将在能够接受的子actor发出的Terminated错误,并进行处理。
该部分是比较精彩的一部分,这里只讲了一些概念。之后的文章会继续补充的。

(4)Actor的消息队列

在Actor框架中,所有的actor对象都是通过消息进行同步。每一个actor有一个自己的消息队列。 消息队列的性质中,又对消息队列更为详细的解释。可以概括为一下几点:
1. 至多一次发送消息
2. 消息顺序无保证
3. A->B发送消息,从A发出的消息,B能够有序的接受(前提是消息不丢失)

(5)系统框架


上图中的系统结构十分清晰,IoTSupervisor作为系统整体的控制器,分别控制Device集群和用户集群。
DeviceManager作为Device集群的主管理器,主要用来创建DeviceGroup,并索引DeviceGroup。通过索引,将外部指令forward(这是个方法,可以认为是转发)给DeviceGroup。
DeviceGroup管理若干Device,创建Device,建立Device索引。接受来自DeviceManager的指令,并forward给Device进行处理。
Device接受来自DeviceGroup的指令,和来自外部sensor的数据。
用户集群本项目没有实现。

之后我们将自底向上实现Device集群

(二)实现Device的Actor

我们要实现一个Device,它的功能有:
1. 实时读取温度参数(读)
2. 实时更新温度参数(写)

(1)实现读协议

在实现读协议,我们先要了解一些Actor一半如何实现一个协议,之后的全部协议中,我们都要按照这个模式进行设计。Actor使用case类来实现一个协议,并使用scala的模式匹配来匹配协议。我们直接来看这个读协议的实现

final case class ReadTemperature(requestId: Long)
final case class RespondTemperature(requestId: Long, value: Option[Double])
ReadTemperature作为请求消息,需要传入一个请求Id(并不知道有什么用)。Device会返回一个RespondTemperature作为数据消息,针对制定的请求Id,放回一个Option(温度可能不存在)。上述代码放在Device(actor)的伴生对象中,方便外部进行调用。
一下是Device的实现:(只完成读协议)
import akka.actor.{ Actor, ActorLogging, Props }

object Device {
  def props(groupId: String, deviceId: String): Props = Props(new Device(groupId, deviceId))

  final case class ReadTemperature(requestId: Long)
  final case class RespondTemperature(requestId: Long, value: Option[Double])
}

class Device(groupId: String, deviceId: String) extends Actor with ActorLogging {
  var lastTemperatureReading: Option[Double] = None

  override def preStart(): Unit = log.info("Device actor {}-{} started", groupId, deviceId)
  override def postStop(): Unit = log.info("Device actor {}-{} stopped", groupId, deviceId)

  override def receive: Receive = {
    case ReadTemperature(id) =>
      sender() ! RespondTemperature(id, lastTemperatureReading)
  }

}
在receive方法中,Device如果接收到一个ReadTemperature消息,它将会返回给发送者一个RespondTemperature消息。

(2)实现写协议

写协议的实现是类似的。
import akka.actor.{ Actor, ActorLogging, Props }

object Device {
  def props(groupId: String, deviceId: String): Props = Props(new Device(groupId, deviceId))

  final case class RecordTemperature(requestId: Long, value: Double)
  final case class TemperatureRecorded(requestId: Long)

  final case class ReadTemperature(requestId: Long)
  final case class RespondTemperature(requestId: Long, value: Option[Double])
}

class Device(groupId: String, deviceId: String) extends Actor with ActorLogging {
  import Device._
  var lastTemperatureReading: Option[Double] = None

  override def preStart(): Unit = log.info("Device actor {}-{} started", groupId, deviceId)
  override def postStop(): Unit = log.info("Device actor {}-{} stopped", groupId, deviceId)

  override def receive: Receive = {
    case RecordTemperature(id, value) =>
      log.info("Recorded temperature reading {} with {}", value, id)
      lastTemperatureReading = Some(value)
      sender() ! TemperatureRecorded(id)

    case ReadTemperature(id) =>
      sender() ! RespondTemperature(id, lastTemperatureReading)
  }
}
Device接受到RecordTemperature消息,更新自己的温度状态,并返回一个确认消息。

(3)测试部分

对于我们实现的Device进行测试。 测试Demo讲解

"test1" should "reply with latest temperature reading" in {
  val probe = TestProbe()
  val deviceActor = system.actorOf(Device.props("group", "device"))

  deviceActor.tell(Device.RecordTemperature(requestId = 1, 24.0), probe.ref)
  probe.expectMsg(Device.TemperatureRecorded(requestId = 1))

  deviceActor.tell(Device.ReadTemperature(requestId = 2), probe.ref)
  val response1 = probe.expectMsgType[Device.RespondTemperature]
  response1.requestId should ===(2)
  response1.value should ===(Some(24.0))

  deviceActor.tell(Device.RecordTemperature(requestId = 3, 55.0), probe.ref)
  probe.expectMsg(Device.TemperatureRecorded(requestId = 3))

  deviceActor.tell(Device.ReadTemperature(requestId = 4), probe.ref)
  val response2 = probe.expectMsgType[Device.RespondTemperature]
  response2.requestId should ===(4)
  response2.value should ===(Some(55.0))
}
probe相当于一个actor,但是我们需要ActorRef,所以往往我们使用probe.ref。代码非常直观,不多做讲解了。

本文结束,还有下文。

小问题:Actor和ActorRef是不一样的吧?那为什么需要ActorRef呢?这个问题不会影响代码,但是这个问题还是有点意思的。





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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值