Actor模型作為Akka中最核心的概念,所以Actor在Akka中的組織結構也至關重要,本文主要介紹Akka中Actor系統。
Actor系統
Actor作為一種封裝狀態和行為的物件,總是需要一個系統去統一的組織和管理它們,在Akka中即為ActorSystem,其實這非常容易理解,好比一個公司,每個員工都可以看成一個Actor,它們有自己的職位和職責,但是我們需要把員工集合起來,統一進行管理和分配任務,所以我們需要一個相應的系統進行管理,好比這裡的ActorSystem對Actor進行管理一樣。
ActorSystem的主要功能
ActorSystem主要有以下三個功能:
- 管理排程服務
- 配置相關引數
- 日誌功能
1.管理排程服務
ActorSystem的的精髓在於將任務分拆,直到一個任務小到可以被完整處理,然後將其委託給Actor進行處理,所以ActorSystem最核心的一個功能就是管理和排程整個系統的執行,好比一個公司的管理者,他需要制定整個公司的發展計劃,還需要將工作分配給相應的工作人員去完成,保障整個公司的正確運轉,其實這裡也體現了軟體設計中的分而治之,Actor中的核心思想也是這樣。
ActorSystem模型例子:
上圖是一個簡單的開發協作的過程,我覺得這個例子應該可以清晰的表達Akka中Actor的組織結構,當然不僅於此。主要有以下幾個特點:
- 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的監管和監控以及它的生命週期。