§與 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 慣例
- 它傳送/接收的訊息,或其通訊協定,定義在其伴隨物件上
- 它也在其伴隨物件上定義
props
方法,傳回用於建立它的道具
§建立和使用 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) }
}
一些注意事項
- 需要匯入 ask 模式,然後這會在 actor 上提供一個
?
營運子。 - ask 的回傳類型是
Future[Any]
,通常在詢問 actor 之後,你會想做的第一件事是使用mapTo
方法將其對應到預期的類型。 - 範圍內需要一個隱含的逾時設定 - ask 模式必須有逾時設定。如果 actor 回應的時間超過逾時設定,回傳的 future 會以逾時錯誤完成。
§相依注入 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 系統。不過,請務必執行下列動作
- 註冊 停止掛勾,以便在 Play 關閉時關閉 Actor 系統
- 從 Play 環境傳入正確的類別載入器,否則 Pekko 將無法找到您的應用程式類別
- 請務必不要從預設
pekko
組態讀取您的 Pekko 組態,因為 Play 的 Actor 系統已經在使用此組態,否則會造成問題,例如系統嘗試繫結到相同的遠端埠時
§Pekko 協調關閉
Play 使用 Pekko 的 協調關閉處理 Application
和 Server
的關閉。請在 協調關閉常見部分中尋找更多資訊。
注意: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 如何在此執行驅逐 的詳細資訊。
下一步:使用訊息進行國際化
在此文件找到錯誤?此頁面的原始程式碼可在 此處 找到。在閱讀 文件指南 後,請隨時提交拉取要求。有問題或建議要分享?請前往 我們的社群論壇 與社群展開對話。