Akka系列(二):Akka中的Actor系统

Actor模型做为Akka中最核心的概念,因此Actor在Akka中的组织结构也相当重要,本文主要介绍Akka中Actor系统。git

Actor系统

Actor做为一种封装状态和行为的对象,老是须要一个系统去统一的组织和管理它们,在Akka中即为ActorSystem,其实这很是容易理解,比如一个公司,每一个员工均可以当作一个Actor,它们有本身的职位和职责,可是咱们须要把员工集合起来,统一进行管理和分配任务,因此咱们须要一个相应的系统进行管理,比如这里的ActorSystem对Actor进行管理同样。github

ActorSystem的主要功能

ActorSystem主要有如下三个功能:app

  • 管理调度服务tcp

  • 配置相关参数分布式

  • 日志功能this

1.管理调度服务

ActorSystem的的精髓在于将任务分拆,直到一个任务小到能够被完整处理,而后将其委托给Actor进行处理,因此ActorSystem最核心的一个功能就是管理和调度整个系统的运行,比如一个公司的管理者,他须要制定整个公司的发展计划,还须要将工做分配给相应的工做人员去完成,保障整个公司的正确运转,其实这里也体现了软件设计中的分而治之,Actor中的核心思想也是这样。spa

ActorSystem模型例子:插件

图片描述

上图是一个简单的开发协做的过程,我以为这个例子应该能够清晰的表达Akka中Actor的组织结构,固然不只于此。主要有如下几个特色:scala

  • Akka中Actor的组织是一种树形结构设计

  • 每一个Actor都有父级,有可能有子级固然也可能没有

  • 父级Actor给其子级Actor分配资源,任务,并管理其的生命状态(监管和监控)

Actor系统每每有成千上万个Actor,使用树形机构来组织管理Actor是很是适合的。

并且Akka天生就是分布式,你能够向一个远程的Actor发送消息,但你须要知道这个Actor的具体位置在哪,这时候你就会发现,树形结构对于肯定一个Actor的路径来讲是很是有利(好比Linux的文件存储),因此我以为Actor用树形结构组织能够说是再完美不过了。

2.根据配置建立环境

一个完善的ActorSystem必须有相关的配置信息,好比使用的日志管理,不一样环境打印的日志级别,拦截器,邮箱等等,Akka使用Typesafe配置库,这是一个很是强大的配置库,后续我也准备写一篇后续文章,你们尽请期待哈。

下面用一个简单的例子来讲明一下ActorSystem会根据配置文件内容去生成相应的Actor系统环境:

1.首先咱们按照默认配置打印一下系统的日志级别,搭建Akka环境请看我上一篇文章:Akka系列(一):Akka简介与Actor模型

val actorSystem = ActorSystem("robot-system")
println(s"the ActorSystem logLevel is ${actorSystem.settings.LogLevel}")

运行结果:

the ActorSystem logLevel is INFO

能够看出ActorSystem默认的日志输出级别是INFO

2.如今咱们在application.conf里配置日志的输出级别:

akka {

# Log level used by the configured loggers (see "loggers") as soon
# as they have been started; before that, see "stdout-loglevel"
# Options: OFF, ERROR, WARNING, INFO, DEBUG
loglevel = "DEBUG"
}

运行结果:

[DEBUG] [03/26/2017 12:07:12.434] [main] [EventStream(akka://robot-system)] logger log1-Logging$DefaultLogger started
[DEBUG] [03/26/2017 12:07:12.436] [main] [EventStream(akka://robot-system)] Default Loggers started
the ActorSystem logLevel is DEBUG

能够发现咱们ActorSystem的日志输出级别已经变成了DEBUG
 
这里主要是演示ActorSystem能够根据配置文件的内容去加载相应的环境,并应用到整个ActorSystem中,这对于咱们配置ActorSystem环境来讲是很是方便的。
 

3.日志功能

有不少人可能会疑惑,日志不该该只是记录程序运行状态和排除错误的嘛,怎么在Akka中会变得相当重要,Akka拥有高容错机制,这无疑须要完善的日志记录才能使Actor出错后能及时作出相应的恢复策略,好比Akka中的持久化,具体相应的一些做用我可能会在后续写相应章节的时候提到。

Actor引用,路径和地址

有了上面的知识,这里了解Actor引用,路径和地址就容易多了。

什么时Actor引用?

Actor引用是ActorRef的子类,每一个Actor有惟一的ActorRef,Actor引用能够当作是Actor的代理,与Actor打交道都须要经过Actor引用,Actor引用能够帮对应Actor发送消息,也能够接收消息,向Actor发送消息实际上是将消息发送到Actor对应的引用上,再由它将消息投寄到具体Actor的信箱中,因此ActorRef在整个Actor系统是一个很是重要的角色。

如何得到Actor引用?

  • 直接建立Actor

  • 查找已经存在的Actor

1.得到ActorRef

看我上一篇文章的同窗对这种方式得到Actor引用应该是比较了解,这里我会具体演示一下得到ActorRef的几种方式:

假定如今由这么一个场景:老板嗅到了市场上的一个商机,准备开启一个新项目,他将要求传达给了经理,经理根据相应的需求,来安排适合的的员工进行工做。

这个例子很简单,如今咱们来模拟一下这个场景:

1.首先咱们来建立一些消息:

trait Message {
  val content: String
}
case class Business(content: String) extends Message {}
case class Meeting(content: String) extends Message {}
case class Confirm(content: String, actorPath: ActorPath) extends Message {}
case class DoAction(content: String) extends Message {}
case class Done(content: String) extends Message {}

2.咱们来建立一家公司,这里就是ActorSystem的化身:

val actorSystem = ActorSystem("company-system") //首先咱们建立一家公司
//建立Actor获得ActorRef的一种方式,利用ActorSystem.actorOf
val bossActor = actorSystem.actorOf(Props[BossActor], "boss") //公司有一个Boss
bossActor ! Business("Fitness industry has great prospects") //从市场上观察到健身行业将会有很大的前景

3.这里咱们会建立几种角色,好比上面Boss,这里咱们还有Manager,Worker,让咱们来看看吧:

class BossActor extends Actor {
  val log = Logging(context.system, this)
  implicit val askTimeout = Timeout(5 seconds)
  import context.dispatcher
  var taskCount = 0
  def receive: Receive = {
    case b: Business =>
      log.info("I must to do some thing,go,go,go!")
      println(self.path.address)
      //建立Actor获得ActorRef的另外一种方式,利用ActorContext.actorOf
      val managerActors = (1 to 3).map(i =>
        context.actorOf(Props[ManagerActor], s"manager${i}")) //这里咱们召唤3个主管
      //告诉他们开会商量大计划
      managerActors foreach {
        _ ? Meeting("Meeting to discuss big plans") map {
          case c: Confirm =>
            //为何这里能够知道父级Actor的信息?
            //熟悉树结构的同窗应该知道每一个节点有且只有一个父节点(根节点除外)
            log.info(c.actorPath.parent.toString)
            //根据Actor路径查找已经存在的Actor得到ActorRef
            //这里c.actorPath是绝对路径,你也能够根据相对路径获得相应的ActorRef
            val manager = context.actorSelection(c.actorPath)
            manager ! DoAction("Do thing")
        }
      }
    case d: Done => {
      taskCount += 1
      if (taskCount == 3) {
        log.info("the project is done, we will earn much money")
        context.system.terminate()
      }
    }
  }
}
class ManagerActor extends Actor {
  val log = Logging(context.system, this)
  def receive: Receive = {
    case m: Meeting =>
      sender() ! Confirm("I have receive command", self.path)
    case d: DoAction =>
      val workerActor = context.actorOf(Props[WorkerActor], "worker")
      workerActor forward d
  }
}

class WorkerActor extends Actor {
  val log = Logging(context.system, this)
  def receive: Receive = {
    case d: DoAction =>
      log.info("I have receive task")
      sender() ! Done("I hava done work")
  }
}

光看这段代码可能不那么容易理解,这里我会画一个流程图帮助你理解这段程序:

程序流程图:

图片描述

看了上面的流程图对程序应该有所了解了,过多的解释我这里就不讲解了,能够看注释,或者下载源代码本身去跑一跑。源码连接

这里主要是有两个知识点:

  • 建立Actor得到ActorRef的两种方式

  • 根据Actor路径得到ActorRef

前一个知识点应该比较清晰了,具体来讲说第二个。

2.Actor路径与地址

熟悉类Unix系统的同窗应该对路径这个概念很熟悉了。ActorSystem中的路径也很相似,每一个ActorSystem都有一个根守护者,用/表示,在根守护者下有一个名user的Actor,它是全部system.actorOf()建立的父Actor,因此咱们程序中bossActor的路径为:

/user/boss

地址顾名思义是Actor所在的位置,为何要有地址这一个概念,这就是Akka强大的理念了,Akka中全部的东西都是被设计为在分布式环境下工做的,因此咱们能够向任意位置的Actor发送消息(前提你得知道它在哪),这时候地址的做用就显现出来来,首先咱们能够根据地址找到Actor在什么位置,再根据路径找到具体的Actor,好比咱们示例程序中bossActor,它的完整位置是

akka://company-system/user/boss

能够发现它的地址是

akka://company-system

其中akka表明纯本地的,Akka中默认远程Actor的位置通常用akka.tcp或者akka.udp开头,固然你也可使用第三方插件,Akka的远程调用我也会专门写一篇文章。

总的来讲这一篇文章主要是讲解了ActorSystem的基础结构,相关配置,以及Actor引用,路径和地址等比较基础的知识点,这其实对理解整个Actor系统是如何运行的是颇有帮助的,博主也是写了很久,争取写的通俗容易理解一点,但愿能获得你们的支持,下一篇准备写一下Actor的监管和监控以及它的生命周期。有兴趣的同窗也能够关注个人我的博客

相关文章
相关标签/搜索