[Akka]傳送一條訊息的內部流程

devos發表於2015-04-18

本想通過了解一下Akka-actor工程中主要的類的概念,來看下Akka內部運作的機制。無奈裡邊的類的確太多,註釋中對每個類的功能也沒有足夠的解釋。所以還是通過debug的方式,找個入手點,看一下互相之間呼叫的關係。

最初的選擇是看一下ActorSystem的實始化過程,但發現難度挺大,因為這個初始化工作是為以後的行為做準備,所以僅根據初始化的動作,難以瞭解其目的是什麼。所以就試著從訊息傳送的過程瞭解起,發現這個邏輯更好理解。

下面來看一下其執行過程吧。程式碼如下,使用預設的配置。

object Main extends App{
  val system = ActorSystem("football")
  val actor = system.actorOf(Props[PrintActor])
  actor ! "hello"
}

想要了解的是actor ! "hello"的執行過程。


 

首先,actor的型別是ActorRef,在預設的配置下,這個ActorRef的具體型別為RepointableActorRef。

它的!方法的定義為

def !(message: Any)(implicit sender: ActorRef = Actor.noSender) = underlying.sendMessage(message, sender)

underlying的定義為

def underlying: Cell = Unsafe.instance.getObjectVolatile(this, cellOffset).asInstanceOf[Cell]

Cell是ActorCell混入的一個特質,後者是Actor的實體部分(相對於ActorRef)。

對於underlying.sendMessage的呼叫,會呼叫ActorCell的sendMessage方法,這個方法實現於Dispatch這個trait。

def sendMessage(msg: Envelope): Unit =
    try {
      if (system.settings.SerializeAllMessages) {
        val unwrapped = (msg.message match {
          case DeadLetter(wrapped, _, _) ⇒ wrapped
          case other                     ⇒ other
        }).asInstanceOf[AnyRef]
        if (!unwrapped.isInstanceOf[NoSerializationVerificationNeeded]) {
          val s = SerializationExtension(system)
          s.deserialize(s.serialize(unwrapped).get, unwrapped.getClass).get
        }
      }
      dispatcher.dispatch(this, msg)
    } catch handleException

雖然有些怪怪的,但是handleException是一個PartialFunction,所以語法是沒問題的。

在if (system.settings.SerializeAllMessages)裡的部分是根據需要把訊息序列化後又反序列化了一遍,可能是為了副作用,因為這個流程中沒有改變msg。

下面就走到了dispatcher.dispatch(this,msg)。

預設的Dispathcer實現是akka.dispatch.Dispatcher。其dispatch方法的實現為:

  protected[akka] def dispatch(receiver: ActorCell, invocation: Envelope): Unit = {
    val mbox = receiver.mailbox
    mbox.enqueue(receiver.self, invocation)
    registerForExecution(mbox, true, false)
  }

它把訊息加入到receiver的mailbox中,然後呼叫registerForExecution。

  protected[akka] override def registerForExecution(mbox: Mailbox, hasMessageHint: Boolean, hasSystemMessageHint: Boolean): Boolean = {
    if (mbox.canBeScheduledForExecution(hasMessageHint, hasSystemMessageHint)) { //This needs to be here to ensure thread safety and no races
      if (mbox.setAsScheduled()) {
        try {
          executorService execute mbox
          true
        } catch {
          case e: RejectedExecutionException ⇒
            try {
              executorService execute mbox
              true
            } catch { //Retry once
              case e: RejectedExecutionException ⇒
                mbox.setAsIdle()
                eventStream.publish(Error(e, getClass.getName, getClass, "registerForExecution was rejected twice!"))
                throw e
            }
        }
      } else false
    } else false
  }

registerForExecution這個名字起得很到位,因為它的確只是"register",並不實際執行它。實際執行execution的是一個執行緒池,所以這個方法會呼叫executorService.execute(mbox)。

為什麼能夠執行mailbox呢?因為mailbox實現了Runnable方法。

  override final def run(): Unit = {
    try {
      if (!isClosed) { //Volatile read, needed here
        processAllSystemMessages() //First, deal with any system messages
        processMailbox() //Then deal with messages
      }
    } finally {
      setAsIdle() //Volatile write, needed here
      dispatcher.registerForExecution(this, false, false)
    }
  }

在上篇文章中,俺認為在設計Akka時,應該考慮提交給執行緒池的任務的粒度,在這個run方法中,實現上決定了這個粒度:ProcessAllSystemMessages以及processMailbox。

  @tailrec private final def processMailbox(
    left: Int = java.lang.Math.max(dispatcher.throughput, 1),
    deadlineNs: Long = if (dispatcher.isThroughputDeadlineTimeDefined == true) System.nanoTime + dispatcher.throughputDeadlineTime.toNanos else 0L): Unit =
    if (shouldProcessMessage) {
      val next = dequeue()
      if (next ne null) {
        if (Mailbox.debug) println(actor.self + " processing message " + next)
        actor invoke next
        if (Thread.interrupted())
          throw new InterruptedException("Interrupted while processing actor messages")
        processAllSystemMessages()
        if ((left > 1) && ((dispatcher.isThroughputDeadlineTimeDefined == false) || (System.nanoTime - deadlineNs) < 0))
          processMailbox(left - 1, deadlineNs)
      }
    }

這個方法會呼叫actor.invoke(next)來處理訊息。

  final def invoke(messageHandle: Envelope): Unit = try {
    currentMessage = messageHandle
    cancelReceiveTimeout() // FIXME: leave this here???
    messageHandle.message match {
      case msg: AutoReceivedMessage ⇒ autoReceiveMessage(messageHandle)
      case msg                      ⇒ receiveMessage(msg)
    }
    currentMessage = null // reset current message after successful invocation
  } catch handleNonFatalOrInterruptedException { e ⇒
    handleInvokeFailure(Nil, e)
  } finally {
    checkReceiveTimeout // Reschedule receive timeout
  }

對於AutoReceivedMessage會走autoRecieveMessage這條路,其它的走receiveMessage(msg)這條路。

final def receiveMessage(msg: Any): Unit = actor.aroundReceive(behaviorStack.head, msg)
protected[akka] def aroundReceive(receive: Actor.Receive, msg: Any): Unit = receive.applyOrElse(msg, unhandled)

至此,可以看到我們在actor中定義的receive方法返回的Actor.Receive這個PartialFunction被應用於msg。也就是說,至此,我們定義的處理訊息的邏輯被執行。


可見,Akka處理訊息的過程,實際上是就是把訊息加入了actor的mailbox中,然後生成一個任務(即Mailbox的run方法中的動作)提交給執行緒池。

當然這其中的很多細節決定了Akka的正確工作。比如每次呼叫processMailbox處理多少訊息;比如equeue一個訊息到mailbox的動作是否阻塞,阻塞多久;比如,在Mailbox的receiveMessage方法中,behaviorStack的行為。

相關文章