閱讀883 返回首頁    go 技術社區[雲棲]


Akka筆記之請求與響應

前麵我們講到了Actor的消息傳遞,並看到了如何發送一條fire-n-forget消息(也就是說,消息發送給Actor後我們就不管了,不從Actor那接收響應)。

技術上來講,消息發送給Actor就是希望能有副作用的。設計上便是如此。目標Actor可以不做響應,也可以做如下兩件事情——

1. 給發送方回複一條響應(在本例中,TeacherActor會將一句名言回複給StudentActor)
2. 將響應轉發給其它的目標受眾Actor,後者也可以進行響應/轉發/產生副作用。Router和Supervisor就是這種情況。(很快我們就會看到)

請求及響應

本文中我們隻關注第一點——請求及響應周期。

image

這張圖說明了我們這次要做的事情。為了簡單點,圖中我並沒有畫出ActorSystem, Dispatcher以及Mailbox。

1. DriverApp將一條InitSignal消息發送給StudentActor。
2. StudentActor響應InitSignal消息並將一條QuoteRequest消息發送到TeacherActor。
3. 正如前麵所說的那樣,TeacherActor會回複一個QuoteResponse。
4. StudentActor將日誌打印到控製台或者logger裏。

同樣的,我們會寫一個測試用例來驗證下它。

現在我們來仔細地分析下這四個步驟:

1. DRIVERAPP將一條INITSIGNAL消息發送給STUDENTACTOR

image

現在你應該能猜到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點放到一起來講?因為它太簡單了,如果分開講的話我怕你嫌我囉嗦。

image

那麼,第2步——StudentActor接收到DriverApp發過來的InitSingal消息並將QuoteRequest發送給TeacherActor。

def receive = {
    case InitSignal=> {
          teacherActorRef!QuoteRequest
    }
    ...
    ...

搞定!

第4步——StudentActor將TeacherActor發過來的消息打印出來。

image

說到做到:

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

  上一篇:go  Java Reflection(七):私有變量和私有方法
  下一篇:go  Java IO: PipedOutputStream