883
技術社區[雲棲]
Akka筆記之請求與響應
前麵我們講到了Actor的消息傳遞,並看到了如何發送一條fire-n-forget消息(也就是說,消息發送給Actor後我們就不管了,不從Actor那接收響應)。
技術上來講,消息發送給Actor就是希望能有副作用的。設計上便是如此。目標Actor可以不做響應,也可以做如下兩件事情——
1. 給發送方回複一條響應(在本例中,TeacherActor會將一句名言回複給StudentActor)
2. 將響應轉發給其它的目標受眾Actor,後者也可以進行響應/轉發/產生副作用。Router和Supervisor就是這種情況。(很快我們就會看到)
請求及響應
本文中我們隻關注第一點——請求及響應周期。
這張圖說明了我們這次要做的事情。為了簡單點,圖中我並沒有畫出ActorSystem, Dispatcher以及Mailbox。
1. DriverApp將一條InitSignal消息發送給StudentActor。
2. StudentActor響應InitSignal消息並將一條QuoteRequest消息發送到TeacherActor。
3. 正如前麵所說的那樣,TeacherActor會回複一個QuoteResponse。
4. StudentActor將日誌打印到控製台或者logger裏。
同樣的,我們會寫一個測試用例來驗證下它。
現在我們來仔細地分析下這四個步驟:
1. DRIVERAPP將一條INITSIGNAL消息發送給STUDENTACTOR
現在你應該能猜到DriverApp到底是幹什麼的了。它隻做了4件事情:
1. 初始化ActorSystem
//Initialize the ActorSystem val system = ActorSystem("UniversityMessageSystem”)
2. 創建TeacherActor
//create the teacher actor val teacherRef = system.actorOf(Props[TeacherActor], "teacherActor”)
3. 創建StudentActor
//create the Student Actor - pass the teacher actorref as a constructor parameter to StudentActor val studentRef = system.actorOf(Props(new StudentActor(teacherRef)), "studentActor")
你會注意到我把TeacherActor的一個ActorRef的引用作為構造函數的參數傳給了StudentActor,這樣StudentActor才能夠通過ActorRef來將消息發送給TeacherActor。當然還有別的方法(比如通過Props來傳遞),不過這麼做對後續即將講到的Supervisor和Router來說會方便一點。很快我們會看到子Actor也能實現這個功能,不過那個方法用在這裏並不適合——學生來生成老師,這看起來不太對勁吧?
最後,
4. DriverApp將InitSignal消息發送給了StudentActor,這樣StudentActor會開始將QuoteRequest消息發送給TeacherActor。
//send a message to the Student Actor studentRef ! InitSignal
DriverClass講的已經夠多了。後麵的Thread.sleep和ActorSystem.shutdown就是等了幾秒,以便消息發送完成,然後再最終將ActorSystem關掉。
DRIVERAPP.SCALA
package me.rerun.akkanotes.messaging.requestresponse import akka.actor.ActorSystem import akka.actor.Props import me.rerun.akkanotes.messaging.protocols.StudentProtocol._ import akka.actor.ActorRef object DriverApp extends App { //Initialize the ActorSystem val system = ActorSystem("UniversityMessageSystem") //construct the teacher actor val teacherRef = system.actorOf(Props[TeacherActor], "teacherActor") //construct the Student Actor - pass the teacher actorref as a constructor parameter to StudentActor val studentRef = system.actorOf(Props(new StudentActor(teacherRef)), "studentActor") //send a message to the Student Actor studentRef ! InitSignal //Let's wait for a couple of seconds before we shut down the system Thread.sleep(2000) //Shut down the ActorSystem. system.shutdown() }
2. STUDENTACTOR響應INITSIGNAL消息並將QUOTEREQUEST消息發送給TEACHERACTOR
以及
4. STUDENTACTOR接收到TEACHERACTOR回複的QuoteResponse然後將日誌打印到控製台/logger上來
為什麼我把第2和第4點放到一起來講?因為它太簡單了,如果分開講的話我怕你嫌我囉嗦。
那麼,第2步——StudentActor接收到DriverApp發過來的InitSingal消息並將QuoteRequest發送給TeacherActor。
def receive = { case InitSignal=> { teacherActorRef!QuoteRequest } ... ...
搞定!
第4步——StudentActor將TeacherActor發過來的消息打印出來。
說到做到:
case QuoteResponse(quoteString) => { log.info ("Received QuoteResponse from Teacher") log.info(s"Printing from Student Actor $quoteString") }
我猜你肯定覺得這很像是偽代碼。
那麼,完整的StudentActor應該是這樣的:
STUDENTACTOR.SCALA
package me.rerun.akkanotes.messaging.requestresponse import akka.actor.Actor import akka.actor.ActorLogging import me.rerun.akkanotes.messaging.protocols.TeacherProtocol._ import me.rerun.akkanotes.messaging.protocols.StudentProtocol._ import akka.actor.Props import akka.actor.ActorRef class StudentActor (teacherActorRef:ActorRef) extends Actor with ActorLogging { def receive = { case InitSignal=> { teacherActorRef!QuoteRequest } case QuoteResponse(quoteString) => { log.info ("Received QuoteResponse from Teacher") log.info(s"Printing from Student Actor $quoteString") } } }
3. TeacherActor回複QuoteResponse
這和我們在前麵的fire-n-forget那篇)中看到的代碼是類似的。
TeacherActor接收到QuoteRequest消息然後回複一個QuoteResponse。
TEACHERACTOR.SCALA
package me.rerun.akkanotes.messaging.requestresponse import scala.util.Random import akka.actor.Actor import akka.actor.ActorLogging import akka.actor.actorRef2Scala import me.rerun.akkanotes.messaging.protocols.TeacherProtocol._ class TeacherActor extends Actor with ActorLogging { val quotes = List( "Moderation is for cowards", "Anything worth doing is worth overdoing", "The trouble is you think you have time", "You never gonna know if you never even try") def receive = { case QuoteRequest => { import util.Random //Get a random Quote from the list and construct a response val quoteResponse = QuoteResponse(quotes(Random.nextInt(quotes.size))) //respond back to the Student who is the original sender of QuoteRequest sender ! quoteResponse } } }
測試用例
現在,我們的測試用例會來模擬下DriverApp。由於StudentActor隻是打印了個日誌消息,我們沒法對QuoteResponse本身進行斷言,那麼我們就看下EventStream中是不是有這條日誌消息就好了(就像上回做的那樣)
那麼,我們的測試用例看起來會是這樣的:
"A student" must { "log a QuoteResponse eventually when an InitSignal is sent to it" in { import me.rerun.akkanotes.messaging.protocols.StudentProtocol._ val teacherRef = system.actorOf(Props[TeacherActor], "teacherActor") val studentRef = system.actorOf(Props(new StudentActor(teacherRef)), "studentActor") EventFilter.info (start="Printing from Student Actor", occurrences=1).intercept{ studentRef!InitSignal } } }
代碼
項目的完整代碼可以從Github中進行下載。
最後更新:2017-05-23 12:02:32