Akka 使用系列之一: 快速入門
Akka 是 Spark 實現內部通訊的組件,Spark 啟動過程的第一步便是建立 Akka 的 ActorSystem。因此看了有幾篇文章學習了下 Akka 相關知識。
1 Actor 模型和 Akka 簡介
Actor 模型由 Carl Hewitt 于上世紀70年代早期提出,目的是為了解決分布式編程中一系列的編程問題。Actor 的要點包括:Actor 是一個個相互之間獨立的實體; Actor 可以通過消息來通信,一個 Actor 收到其他Actor的信息后,可以根據需要作出各種相應反應;消息的類型可以是任意的,消息的內容也可以是任意的;當一個 Actor 收到多個消息時,它先建立一個消息隊列,將接收到的消息就放入隊列,每次從隊列中取出一個消息體進行處理。
Akka 是一個用 Scala 編寫的庫,用于簡化編寫容錯的、高可伸縮性的 Actor 模型應用。Akka 使得開發人員可以更輕松地開發具有容錯性、可擴展性和跨平臺的并發程序,在工業界得到了廣泛應用。
2 Akka 入門
下面我們將從一個學生老師例子出發,快速入門 Actor 模型。例子是從博客 rerun 拿來的 (我就是看這個博客學習 Akka 的,強烈推薦英語好的同學看看)。這個例子有兩個角色,一個是勤學好問的學生,一個是睿智的老師。每天早上,學生都會給老師發送一封郵件,向老師請教問題;而老師看到郵件之后,通過郵件給學生發送答案。在這個過程中,有幾點需要注意:
1. 郵件一旦發送,就不能改變;
2. 學生和老師都按照自己的工作節奏檢查郵箱;
3. 學生發送郵件之后,可以不等老師的回復。即工作流程可以是阻塞,也可以是非阻塞;
Akka 用于簡化編寫容錯的、高可伸縮性的 Actor 模型應用,即我們很容易用 Akka 實現上述學生老師的 Actor 模型。我們先建立老師 Actor, 將老師 Actor 實現成一個服務(我是不是想歪了),代碼如下所示。
class TeacherActor extends Actor {
def receive = {
case "1+1等于多少?" => {
val originalSender = sender;//
sender ! "1+1等于2"
}
case "歷史上規模最大的眾籌行動是什么?" => {
val originalSender = sender;//
sender ! "歷史上規模最大的眾籌行動是 +1s"
}
case "騰訊第一網紅是誰?" => {
val originalSender = sender;//
sender ! "騰訊第一網紅是\"我去\""
}
case _ => {
val originalSender = sender;//
sender ! "這個問題,老師也得查查書"
}
}
}
object TeacherServices extends App {
val config = ConfigFactory
.parseResources("lietal.conf")
.getConfig("RemoteServerSideActor")
//讀入配置
val system = ActorSystem("TeacherService", config)
//使用配置,建立 Actor 模型系統 ActorSystem。
//ActorSystem 是訪問 Actor 模型系統的接口,類似于 Spark 的 SparkContext。
system.actorOf(Props[TeacherActor], "teacherActor")
//創建TearcherActor,返回一個引用
//teacherActor 是 Actor 的名,客戶端需要用
}
然后我們建立學生 Actor, 將學生 Actor 作為客戶端。
class StudentActor extends Actor{
val path = "akka.tcp://remote-system@127.0.0.1:4999/"
+"user/teacherActor"
// 遠程Actor的路徑,通過該路徑能夠獲取到遠程Actor的一個引用
val remoteServerRef = context.actorSelection(path)
// 獲取到遠程Actor的一個引用,通過該引用可以向遠程Actor發送消息
def receive = {
case res:String => {
println (res)
//打印出從老師 Actor 獲得的答案
}
case time:Long => {
remoteServerRef ! "歷史上規模最大的眾籌行動是什么?";
}
}
}
object StudentSimulator extends App{
val config = ConfigFactory
.parseResources("lietal.conf")
.getConfig("RemoteClientSideActor")
//讀入客戶端配置
val actorSystem = ActorSystem("StudentClient", config);
//使用配置,建立 ActorSystem
val studentActor = actorSystem.actorOf(Props[StudentActor])
//獲得 StudentActor 的一個引用。
//在程序中 Actor 不能直接被訪問。
//所有操作都必須通過 ActorRef 引用。
while(true){
studentActor ! 7.toLong // 7 點起床。。
Thread.sleep(5000) // 假裝一天過去了
}
}
不管是服務端還是客戶端,程序開始都從 lietal.conf 配置文件讀入相應的配置。其中服務端讀入 RemoteServerSideActor 的配置,而客戶端讀入 RemoteClientSideActor 的配置。lietal.conf 配置文件放在資源目錄 src/main/resources,以便打包時打入包內。lietal.conf 的內容如下所示。
RemoteServerSideActor {
akka {
actor {
provider = "akka.remote.RemoteActorRefProvider"
}
remote {
enabled-transports = ["akka.remote.netty.tcp"]
netty.tcp {
hostname = "127.0.0.1"
port = 4999
}
}
}
}
RemoteClientSideActor {
akka {
actor {
provider = "akka.remote.RemoteActorRefProvider"
}
remote {
enabled-transports = ["akka.remote.netty.tcp"]
netty.tcp {
hostname = "127.0.0.1"
port = 5000
}
}
}
}
從配置文件來看,如果我們在服務端啟動兩個老師 Actor, 他們會共用一個端口。我們很容易理解,所有發往老師 Actor 的消息都發往了服務器的一個端口,Akka 內部有一套機制將消息分發到不同的 Actor 中。這套機制就是 Akka 的 dispatcher,負責分發不同的消息到不同的 Actor。
完整項目代碼已經上傳到 Github 上了,需要的同學自取。將完整項目打包之后,分別以 TearcherService 和 StudentClient 為主類運行程序,老師 Actor 和學生 Actor 之間的通信就運行起來了。下面是老師服務端運行的結果。
下面是學生客戶端運行的結果。
3 總結
一開始我只想實現一個單機版本的老師學生 Actor,實現之后發現不能體現 Akka 的特點,因此又實現一個網絡版的老師學生 Actor。實現完網絡版之后,稍微加深了對 Actor 之間消息傳遞的理解。
來自:http://www.algorithmdog.com/akka-usage-getstarted