§與 Pekko 整合
Pekko 使用 Actor 模型來提高抽象層級,並提供一個更好的平台來建置正確的並行和可擴充應用程式。對於容錯,它採用「讓它崩潰」模型,此模型已在電信產業中成功用於建置自我修復的應用程式,也就是永不停止的系統。Actor 也提供透明化分佈的抽象,以及真正可擴充和容錯應用程式的基礎。
§應用程式 Actor 系統
Pekko 可以與稱為 Actor 系統的幾個容器搭配使用。Actor 系統管理它被設定為用來執行其中所包含 Actor 的資源。
Play 應用程式定義一個特殊的 Actor 系統供應用程式使用。此 Actor 系統遵循應用程式生命週期,並在應用程式重新啟動時自動重新啟動。
§撰寫 Actor
要開始使用 Pekko,您需要撰寫一個 Actor。以下是只會對要求它的人打招呼的簡單 Actor。
package actors;
import org.apache.pekko.actor.*;
import org.apache.pekko.japi.*;
import actors.HelloActorProtocol.*;
public class HelloActor extends AbstractActor {
public static Props getProps() {
return Props.create(HelloActor.class);
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(
SayHello.class,
hello -> {
String reply = "Hello, " + hello.name;
sender().tell(reply, self());
})
.build();
}
}
在此請注意,HelloActor
定義一個稱為 getProps
的靜態方法,此方法傳回一個 Props
物件,用來描述如何建立 Actor。這是 Pekko 的一個良好慣例,用於將實例化邏輯與建立 Actor 的程式碼分開。
這裡顯示的另一個最佳實務是 HelloActor
傳送和接收的訊息被定義為另一個稱為 HelloActorProtocol
的類別的靜態內部類別
package actors;
public class HelloActorProtocol {
public static class SayHello {
public final String name;
public SayHello(String name) {
this.name = name;
}
}
}
§建立和使用 Actor
要建立和/或使用 Actor,您需要一個 ActorSystem
。這可以透過宣告對 ActorSystem 的依賴性來取得,然後您可以使用 actorOf
方法來建立一個新的 Actor。
您可以對 Actor 執行的最基本動作是傳送訊息給它。當您傳送訊息給 Actor 時,不會有回應,它是發射後就忘記。這也稱為tell 模式。
然而,在網頁應用程式中,tell 模式通常沒什麼用,因為 HTTP 是一種具有要求和回應的協定。在這種情況下,您更有可能想要使用 ask 模式。ask 模式會傳回 Scala Future
,您可以使用 scala.compat.java8.FutureConverts.toJava
將其轉換為 Java CompletionStage
,然後對應到您自己的結果類型。
以下是使用 ask 模式搭配我們的 HelloActor
的範例
import org.apache.pekko.actor.*;
import play.mvc.*;
import scala.jdk.javaapi.FutureConverters;
import javax.inject.*;
import java.util.concurrent.CompletionStage;
import static org.apache.pekko.pattern.Patterns.ask;
@Singleton
public class Application extends Controller {
final ActorRef helloActor;
@Inject
public Application(ActorSystem system) {
helloActor = system.actorOf(HelloActor.getProps());
}
public CompletionStage<Result> sayHello(String name) {
return FutureConverters.asJava(ask(helloActor, new SayHello(name), 1000))
.thenApply(response -> ok((String) response));
}
}
幾項注意事項
- ask 模式需要匯入,通常最方便的方式是靜態匯入
ask
方法。 - 傳回的 future 會轉換為
CompletionStage
。產生的承諾是CompletionStage<Object>
,因此當您存取其值時,需要將其轉換為您預期從 actor 傳回的類型。 - ask 模式需要逾時,我們已提供 1000 毫秒。如果 actor 花費超過該時間來回應,傳回的承諾會以逾時錯誤完成。
- 由於我們在建構函式中建立 actor,因此我們需要將控制器範圍設定為
Singleton
,這樣就不會在每次使用此控制器時建立新的 actor。
§相依性注入 actor
如果您願意,您可以讓 Guice 建立您的 actor,並將 actor 參考繫結到它們,讓您的控制器和元件相依。
例如,如果您想要一個相依於 Play 組態的 actor,您可以這樣做
import com.typesafe.config.Config;
import javax.inject.Inject;
import org.apache.pekko.actor.AbstractActor;
public class ConfiguredActor extends AbstractActor {
private Config configuration;
@Inject
public ConfiguredActor(Config configuration) {
this.configuration = configuration;
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(
ConfiguredActorProtocol.GetConfig.class,
message -> {
sender().tell(configuration.getString("my.config"), self());
})
.build();
}
}
Play 提供一些輔助程式來協助提供 Actor 繫結。這些程式允許 Actor 本身進行依賴注入,並允許將 Actor 的 Actor 參照注入到其他元件。若要使用這些輔助程式繫結 Actor,請按照 依賴注入文件 中的說明建立模組,然後混合 PekkoGuiceSupport
介面,並使用 bindActor
方法來繫結 Actor
import com.google.inject.AbstractModule;
import play.libs.pekko.PekkoGuiceSupport;
public class MyModule extends AbstractModule implements PekkoGuiceSupport {
@Override
protected void configure() {
bindActor(ConfiguredActor.class, "configured-actor");
}
}
此 Actor 將同時命名為 configured-actor
,並將使用 configured-actor
名稱進行限定以供注入。您現在可以在控制器和其他元件中依賴 Actor
import org.apache.pekko.actor.ActorRef;
import play.mvc.*;
import scala.jdk.javaapi.FutureConverters;
import javax.inject.Inject;
import javax.inject.Named;
import java.util.concurrent.CompletionStage;
import static org.apache.pekko.pattern.Patterns.ask;
public class Application extends Controller {
private ActorRef configuredActor;
@Inject
public Application(@Named("configured-actor") ActorRef configuredActor) {
this.configuredActor = configuredActor;
}
public CompletionStage<Result> getConfig() {
return FutureConverters.asJava(
ask(configuredActor, new ConfiguredActorProtocol.GetConfig(), 1000))
.thenApply(response -> ok((String) response));
}
}
§依賴注入子 Actor
上述內容適用於注入根 Actor,但您建立的許多 Actor 將是子 Actor,這些 Actor 沒有繫結到 Play 應用程式的生命週期,並且可能傳遞其他狀態給它們。
為了協助依賴注入子 Actor,Play 使用 Guice 的 AssistedInject 支援。
假設您有以下 Actor,它依賴要注入的組態,以及一個金鑰
import com.google.inject.assistedinject.Assisted;
import com.typesafe.config.Config;
import javax.inject.Inject;
import org.apache.pekko.actor.AbstractActor;
public class ConfiguredChildActor extends AbstractActor {
private final Config configuration;
private final String key;
@Inject
public ConfiguredChildActor(Config configuration, @Assisted String key) {
this.configuration = configuration;
this.key = key;
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(ConfiguredChildActorProtocol.GetConfig.class, this::getConfig)
.build();
}
private void getConfig(ConfiguredChildActorProtocol.GetConfig get) {
sender().tell(configuration.getString(key), self());
}
}
在這種情況下,我們使用了建構函式注入 - Guice 的輔助注入支援僅與建構函式注入相容。由於 key
參數將在建立時提供,而不是由容器提供,因此我們已使用 @Assisted
對其進行註解。
現在在子項目的通訊協定中,我們定義一個 Factory
介面,它採用 key
並傳回 Actor
import org.apache.pekko.actor.Actor;
public class ConfiguredChildActorProtocol {
public static class GetConfig {}
public interface Factory {
public Actor create(String key);
}
}
我們不會實作這個介面,Guice 會為我們執行此動作,提供一個實作,不僅傳遞我們的 key
參數,還會找出 Configuration
依賴項並注入它。由於特質只傳回一個 Actor
,因此在測試此 Actor 時,我們可以注入傳回任何 Actor 的因子,例如這允許我們注入模擬的子 Actor,而不是實際的子 Actor。
現在,依賴此項目的執行者可以延伸InjectedActorSupport
,並且可以依賴我們建立的工廠
import javax.inject.Inject;
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.actor.ActorRef;
import play.libs.pekko.InjectedActorSupport;
public class ParentActor extends AbstractActor implements InjectedActorSupport {
private ConfiguredChildActorProtocol.Factory childFactory;
@Inject
public ParentActor(ConfiguredChildActorProtocol.Factory childFactory) {
this.childFactory = childFactory;
}
@Override
public Receive createReceive() {
return receiveBuilder().match(ParentActorProtocol.GetChild.class, this::getChild).build();
}
private void getChild(ParentActorProtocol.GetChild msg) {
String key = msg.key;
ActorRef child = injectedChild(() -> childFactory.create(key), key);
sender().tell(child, self());
}
}
它使用injectedChild
建立並取得子執行者的參考,傳入金鑰。第二個參數(此範例中的key
)將用作子執行者的名稱。
最後,我們需要繫結我們的執行者。在我們的模組中,我們使用bindActorFactory
方法繫結父執行者,並將子工廠繫結至子執行實作
import com.google.inject.AbstractModule;
import play.libs.pekko.PekkoGuiceSupport;
public class MyModule extends AbstractModule implements PekkoGuiceSupport {
@Override
protected void configure() {
bindActor(ParentActor.class, "parent-actor");
bindActorFactory(ConfiguredChildActor.class, ConfiguredChildActorProtocol.Factory.class);
}
}
這將讓 Guice 自動繫結ConfiguredChildActorProtocol.Factory
的執行個體,在實例化ConfiguredChildActor
時,它將提供Configuration
的執行個體。
§組態
預設的執行者系統組態會從 Play 應用程式組態檔讀取。例如,若要組態應用程式執行者系統的預設調度器,請將這些列新增至conf/application.conf
檔
pekko.actor.default-dispatcher.fork-join-executor.parallelism-max = 64
pekko.actor.debug.receive = on
有關 Pekko 記錄組態,請參閱組態記錄。
§內建執行者系統名稱
預設情況下,Play 執行者系統的名稱為application
。您可以透過conf/application.conf
中的項目變更此名稱
play.pekko.actor-system = "custom-name"
注意:如果您想將 Play 應用程式
ActorSystem
放入 Pekko 群集,此功能會很有用。
§使用您自己的執行者系統
雖然我們建議您使用內建的執行者系統,因為它會設定所有內容,例如正確的類別載入器、生命週期掛鉤等,但沒有任何因素會阻止您使用自己的執行者系統。然而,務必確保您執行下列事項
- 註冊停止掛鉤,以便在 Play 關閉時關閉執行者系統
- 從 Play Environment傳入正確的類別載入器,否則 Pekko 將無法找到您的應用程式類別
- 確保您不會從預設的
pekko
設定檔讀取您的 Pekko 設定檔,因為 Play 的 actor 系統已經在使用它,這將導致問題,例如系統嘗試繫結到相同的遠端埠時
§非同步執行程式碼區塊
Pekko 中的常見使用案例是並行執行一些運算,而不需要 Actor 的額外實用程式。如果您發現自己建立一個 Actor 池只是為了並行執行運算,那麼有一個更簡單(且更快速)的方法
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import play.mvc.*;
public class Application extends Controller {
public CompletionStage<Result> index() {
return CompletableFuture.supplyAsync(this::longComputation)
.thenApply((Integer i) -> ok("Got " + i));
}
}
§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 在這裡如何執行逐出 的更多詳細資訊。
下一步:使用訊息進行國際化
在此文件檔中發現錯誤嗎?此頁面的原始程式碼可以在 這裡 找到。在閱讀 文件檔指南 後,請隨時提出協力要求。有問題或建議要分享嗎?請前往 我們的社群論壇 與社群展開對話。