1. 程式人生 > >Akka筆記之請求與響應

Akka筆記之請求與響應

英文原文連結譯文連結,原文作者:Arun Manivannan ,譯者:有孚

前面我們講到了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

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中進行下載。

在下一篇中,我們將會看到如何在Akka中使用排程器以及如何通過Kamon來監控你的Akka應用。

本文最早釋出於我的個人部落格: Java譯站