文件

§與 Pekko 整合

Pekko 使用 Actor 模型來提升抽象層級,並提供一個更好的平台來建置正確的並行和可擴充應用程式。對於容錯,它採用「讓它崩潰」模型,該模型已在電信產業中成功建置出自我修復的應用程式,也就是永不停止的系統。Actor 也提供透明分佈的抽象,以及真正可擴充和容錯應用程式的基礎。

§應用程式 Actor 系統

Pekko 可以與稱為 Actor 系統的數個容器搭配使用。Actor 系統管理其設定為用於執行其包含的 Actor 的資源。

Play 應用程式定義應用程式要使用的特殊 Actor 系統。此 Actor 系統遵循應用程式生命週期,並在應用程式重新啟動時自動重新啟動。

§撰寫 Actor

若要開始使用 Pekko,您需要撰寫 Actor。以下是簡單的 Actor,它只會向要求它的任何對象問候。

import org.apache.pekko.actor._

object HelloActor {
  def props = Props[HelloActor]()

  case class SayHello(name: String)
}

class HelloActor extends Actor {
  import HelloActor._

  def receive = {
    case SayHello(name: String) =>
      sender() ! "Hello, " + name
  }
}

此 Actor 遵循一些 Pekko 慣例

§建立和使用 Actor

若要建立和/或使用 Actor,您需要 ActorSystem。這可以透過宣告對 ActorSystem 的依賴性來取得,如下所示

import javax.inject._

import actors.HelloActor
import org.apache.pekko.actor._
import play.api.mvc._

@Singleton
class Application @Inject() (system: ActorSystem, cc: ControllerComponents) extends AbstractController(cc) {
  val helloActor = system.actorOf(HelloActor.props, "hello-actor")

  // ...
}

actorOf 方法用於建立新的 Actor。請注意,我們已宣告此控制器為單例。這是必要的,因為我們正在建立 Actor 並儲存對它的參照,如果控制器未設定為單例範圍,這表示每次建立控制器時都會建立新的 Actor,最終會擲回例外,因為您無法在同一個系統中使用相同名稱建立兩個 Actor。

§詢問 Actor

您可以對 Actor 執行的最基本動作是傳送訊息給它。當您傳送訊息給 Actor 時,不會有回應,它是發射後就忘記。這也稱為告知模式。

然而,在 Web 應用程式中,告知模式通常沒有用,因為 HTTP 是具有要求和回應的通訊協定。在這種情況下,您更有可能想要使用詢問模式。詢問模式傳回 Future,然後您可以將它對應到您自己的結果類型。

以下是使用詢問模式的 HelloActor 範例

import scala.concurrent.duration._

import org.apache.pekko.pattern.ask
implicit val timeout: Timeout = 5.seconds

def sayHello(name: String) = Action.async {
  (helloActor ? SayHello(name)).mapTo[String].map { message => Ok(message) }
}

一些注意事項

§相依注入 actor

如果你願意,你可以讓 Guice 執行你的 actor 的實例化,並將 actor 參照繫結到你的控制器和元件,讓它們相依。

例如,如果你想要一個相依於 Play 設定的 actor,你可以這樣做

import javax.inject._

import org.apache.pekko.actor._
import play.api.Configuration

object ConfiguredActor {
  case object GetConfig
}

class ConfiguredActor @Inject() (configuration: Configuration) extends Actor {
  import ConfiguredActor._

  val config = configuration.getOptional[String]("my.config").getOrElse("none")

  def receive = {
    case GetConfig =>
      sender() ! config
  }
}

Play 提供了一些輔助程式來協助提供 actor 繫結。這些輔助程式允許 actor 本身相依注入,並允許 actor 的 actor 參照注入到其他元件中。若要使用這些輔助程式繫結 actor,請建立一個模組,如相依注入文件中所述,然後混合 PekkoGuiceSupport 特質並使用 bindActor 方法繫結 actor

import actors.ConfiguredActor
import com.google.inject.AbstractModule
import play.api.libs.concurrent.PekkoGuiceSupport

class MyModule extends AbstractModule with PekkoGuiceSupport {
  override def configure = {
    bindActor[ConfiguredActor]("configured-actor")
  }
}

這個 actor 既會被命名為 configured-actor,也會使用 configured-actor 名稱進行注入限定。現在你可以在你的控制器和其他元件中相依於這個 actor

import javax.inject._

import scala.concurrent.duration._
import scala.concurrent.ExecutionContext

import actors.ConfiguredActor._
import org.apache.pekko.actor._
import org.apache.pekko.pattern.ask
import org.apache.pekko.util.Timeout
import play.api.mvc._

@Singleton
class Application @Inject() (
    @Named("configured-actor") configuredActor: ActorRef,
    components: ControllerComponents
)(
    implicit ec: ExecutionContext
) extends AbstractController(components) {
  implicit val timeout: Timeout = 5.seconds

  def getConfig = Action.async {
    (configuredActor ? GetConfig).mapTo[String].map { message => Ok(message) }
  }
}

§相依注入子 actor

上述內容適用於注入根 actor,但你建立的許多 actor 將會是子 actor,它們未繫結到 Play 應用程式的生命週期,並且可能傳遞額外的狀態給它們。

為了協助注入子 Actor 的依賴關係,Play 使用 Guice 的 AssistedInject 支援。

假設您有以下 Actor,它依賴要注入的組態,以及一個金鑰

import javax.inject._

import com.google.inject.assistedinject.Assisted
import org.apache.pekko.actor._
import play.api.Configuration

object ConfiguredChildActor {
  case object GetConfig

  trait Factory {
    def apply(key: String): Actor
  }
}

class ConfiguredChildActor @Inject() (configuration: Configuration, @Assisted key: String) extends Actor {
  import ConfiguredChildActor._

  val config = configuration.getOptional[String](key).getOrElse("none")

  def receive = {
    case GetConfig =>
      sender() ! config
  }
}

請注意,key 參數宣告為 @Assisted,這表示它將手動提供。

我們還定義了一個 Factory 特質,它採用 key,並傳回一個 Actor。我們不會實作這個,Guice 會為我們實作,提供一個不僅傳遞我們的 key 參數,還會找出 Configuration 依賴關係並注入它的實作。由於特質只傳回一個 Actor,因此在測試這個 Actor 時,我們可以注入一個傳回任何 Actor 的工廠,例如,這允許我們注入一個仿冒的子 Actor,而不是實際的子 Actor。

現在,依賴這個的 Actor 可以延伸 InjectedActorSupport,它可以依賴我們建立的工廠

import javax.inject._

import org.apache.pekko.actor._
import play.api.libs.concurrent.InjectedActorSupport

object ParentActor {
  case class GetChild(key: String)
}

class ParentActor @Inject() (
    childFactory: ConfiguredChildActor.Factory
) extends Actor
    with InjectedActorSupport {
  import ParentActor._

  def receive = {
    case GetChild(key: String) =>
      val child: ActorRef = injectedChild(childFactory(key), key)
      sender() ! child
  }
}

它使用 injectedChild 建立並取得子 Actor 的參考,傳入金鑰。第二個參數(此範例中的 key)將用作子 Actor 的名稱。

最後,我們需要繫結我們的 Actor。在我們的模組中,我們使用 bindActorFactory 方法來繫結父 Actor,並將子工廠繫結到子實作

import actors._
import com.google.inject.AbstractModule
import play.api.libs.concurrent.PekkoGuiceSupport

class MyModule extends AbstractModule with PekkoGuiceSupport {
  override def configure = {
    bindActor[ParentActor]("parent-actor")
    bindActorFactory[ConfiguredChildActor, ConfiguredChildActor.Factory]
  }
}

這將讓 Guice 自動繫結 ConfiguredChildActor.Factory 的執行個體,它將在建立 ConfiguredChildActor 時提供 Configuration 的執行個體。

§組態

預設的 Actor 系統組態會從 Play 應用程式組態檔讀取。例如,若要組態應用程式 Actor 系統的預設調度器,請將這些行加入 conf/application.conf

pekko.actor.default-dispatcher.fork-join-executor.parallelism-max = 64
pekko.actor.debug.receive = on

有關 Pekko 記錄組態,請參閱 組態記錄

§內建 Actor 系統名稱

預設情況下,Play Actor 系統的名稱為 application。您可以透過 conf/application.conf 中的項目變更此名稱

play.pekko.actor-system = "custom-name"

注意:如果您想將 Play 應用程式 ActorSystem 放入 Pekko 集群,此功能會很有用。

§使用您自己的 Actor 系統

雖然我們建議您使用內建 Actor 系統,因為它會設定所有內容,例如正確的類別載入器、生命週期掛勾等,但並未禁止您使用自己的 Actor 系統。不過,請務必執行下列動作

§Pekko 協調關閉

Play 使用 Pekko 的 協調關閉處理 ApplicationServer 的關閉。請在 協調關閉常見部分中尋找更多資訊。

注意:Play 僅處理其內部 ActorSystem 的關閉。如果您使用額外的 Actor 系統,請確保它們全部終止,並隨時將您的終止代碼移轉到 協調關閉

§Pekko 集群

您可以讓您的 Play 應用程式加入現有的 Pekko 集群。在這種情況下,建議您優雅地離開集群。Play 附帶 Pekko 的協調關閉,它將負責優雅地離開。

如果您已經有自訂的集群離開代碼,建議您將其替換為 Pekko 的處理方式。請參閱 Pekko 文件 以取得更多詳細資訊。

§更新 Pekko 版本

如果您想要使用較新版本的 Pekko,也就是 Play 尚未使用的版本,您可以將下列內容新增到您的 build.sbt 檔案中

// The newer Pekko version you want to use.
val pekkoVersion = "1.0.0"

// Pekko dependencies used by Play
libraryDependencies ++= Seq(
  "org.apache.pekko" %% "pekko-actor"                 % pekkoVersion,
  "org.apache.pekko" %% "pekko-actor-typed"           % pekkoVersion,
  "org.apache.pekko" %% "pekko-stream"                % pekkoVersion,
  "org.apache.pekko" %% "pekko-slf4j"                 % pekkoVersion,
  "org.apache.pekko" %% "pekko-serialization-jackson" % pekkoVersion,
  // Only if you are using Pekko Testkit
  "org.apache.pekko" %% "pekko-testkit" % pekkoVersion
)

當然,其他 Pekko 人工製品可以遞移新增。使用 sbt-dependency-graph 來更好地檢查您的建置並查看您需要明確新增哪些內容。

如果您尚未切換到 Netty 伺服器後端,因此正在使用 Play 的預設 Pekko HTTP 伺服器後端,您還必須更新 Pekko HTTP。因此,您也需要明確新增其依賴項

// The newer Pekko HTTP version you want to use.
val pekkoHTTPVersion = "1.0.0"

// Pekko HTTP dependency used by Play
libraryDependencies ++= Seq(
  "org.apache.pekko" %% "pekko-http-core" % pekkoHTTPVersion
)

注意:在進行此類更新時,請記住您需要遵循 Pekko 的 二進制相容性規則。如果您手動新增其他 Pekko 人工製品,請記住保持所有 Pekko 人工製品的版本一致,因為 不允許混合版本

注意:在解決相依性時,sbt 會取得此專案宣告或以遞移方式新增的最新版本。這表示如果 Play 相依的 Pekko(或 Pekko HTTP)版本比你宣告的版本新,則 Play 的版本會獲勝。請參閱 sbt 如何在此執行驅逐 的詳細資訊。

下一步:使用訊息進行國際化


在此文件找到錯誤?此頁面的原始程式碼可在 此處 找到。在閱讀 文件指南 後,請隨時提交拉取要求。有問題或建議要分享?請前往 我們的社群論壇 與社群展開對話。