SmallRye Stork入門
分散システムの本質は、サービス間の相互作用にあります。現代のアーキテクチャでは、負荷を共有したり、冗長性によって回復力を高めたりするために、サービスの複数のインスタンスを持つことがよくあります。しかし、サービスの最適なインスタンスをどのように選択すればよいのでしょうか?そこで、 SmallRye Storkがお役に立ちます。Storkが最適なインスタンスを選択してくれます。次の機能を提供します:
-
拡張可能なサービスディスカバリーメカニズム
-
ConsulとKubernetesの組込サポート
-
カスタマイズ可能なクライアントロードバランシング戦略
この技術は、previewと考えられています。 preview では、下位互換性やエコシステムでの存在は保証されていません。具体的な改善には設定や API の変更が必要になるかもしれませんが、 stable になるための計画は現在進行中です。フィードバックは メーリングリスト や GitHub の課題管理 で受け付けています。 とりうるステータスの完全なリストについては、 FAQの項目 を参照してください。 |
前提条件
このガイドを完成させるには、以下が必要です:
-
約15分
-
IDE
-
JDK 17+がインストールされ、
JAVA_HOME
が適切に設定されていること -
Apache Maven 3.9.9
-
動作するコンテナランタイム(Docker, Podman)
-
使用したい場合は、 Quarkus CLI
-
ネイティブ実行可能ファイルをビルドしたい場合、MandrelまたはGraalVM(あるいはネイティブなコンテナビルドを使用する場合はDocker)をインストールし、 適切に設定していること
アーキテクチャ
このガイドでは、次のような構成のアプリケーションを構築します。
-
9000番ポートで公開されるシンプルなブルーサービス
-
9001番ポートで公開されるシンプルなレッドサービス
-
ブルーまたはレッドのサービスを呼び出すRESTクライアント(選択はStorkに委ねられている)
-
RESTクライアントを使用し、サービスを呼び出すRESTエンドポイント
-
ブルーとレッドのサービスは Consulに登録されています。
簡略化のため、(Consulを除く)すべてのコンポーネントは、同じQuarkusアプリケーション内で実行されます。もちろん、実際には各コンポーネントはそれぞれのプロセスで実行されます。
ソリューション
次のセクションの指示に従って、段階的にアプリケーションを作成することをお勧めします。しかし、完成した例をすぐに見ることもできます。
Gitレポジトリをクローンするか git clone https://github.com/quarkusio/quarkus-quickstarts.git
、 アーカイブ をダウンロードします。
ソリューションは stork-quickstart
ディレクトリ にあります。
ディスカバリーとセレクション
その前に、ディスカバリーとセレクションについて説明する必要があります。
-
サービスディスカバリーとは、サービスインスタンスを探すプロセスのことです。サービス・ディスカバリーでは、サービス・インスタンスのリストが作成されます。このリストは、(リクエストにマッチするサービスがない場合)空の可能性もあれば、複数のサービス・インスタンスを含む可能性もあります。
-
サービスセレクションはロードバランスとも呼ばれ、ディスカバリープロセスから返されたリストの中から最適なインスタンスを選択します。その結果、1つのサービスインスタンスになるか、適切なインスタンスが見つからない場合は例外となります。
Storkはディスカバリーとセレクションの両方を扱います。しかし、サービスとの通信は処理せず、サービスインスタンスを提供するだけです。Quarkusの様々な統合機能は、そのサービスインスタンスからサービスの場所を抽出します。
プロジェクトのブートストラップ
quarkus-rest-clientとquarkus-rest extensionsをインポートして、Quarkusプロジェクトを作成します:
Windowsユーザーの場合:
-
cmdを使用する場合、(バックスラッシュ
\
を使用せず、すべてを同じ行に書かないでください)。 -
Powershellを使用する場合は、
-D
パラメータを二重引用符で囲んでください。例:"-DprojectArtifactId=stork-quickstart"
生成されたプロジェクトに、以下の依存関係を追加します:
<dependency>
<groupId>io.smallrye.stork</groupId>
<artifactId>stork-service-discovery-consul</artifactId>
</dependency>
<dependency>
<groupId>io.smallrye.reactive</groupId>
<artifactId>smallrye-mutiny-vertx-consul-client</artifactId>
</dependency>
implementation("io.smallrye.stork:stork-service-discovery-consul")
implementation("io.smallrye.reactive:smallrye-mutiny-vertx-consul-client")
stork-service-discovery-consul
はConsulのサービスディスカバリーの実装を提供しています。 smallrye-mutiny-vertx-consul-client
はConsulのクライアントで、Consulにサービスを登録するために使用します。
ブルーサービスとレッドサービス
一番最初から始めましょう:私たちが発見(ディスカバリー)し、選択(セレクション)し、呼び出すサービスです。
以下の内容で src/main/java/org/acme/services/BlueService.java
を作成してください。
package org.acme.services;
import io.quarkus.runtime.StartupEvent;
import io.vertx.mutiny.core.Vertx;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
@ApplicationScoped
public class BlueService {
@ConfigProperty(name = "blue-service-port", defaultValue = "9000") int port;
/**
* Start an HTTP server for the blue service.
*
* Note: this method is called on a worker thread, and so it is allowed to block.
*/
public void init(@Observes StartupEvent ev, Vertx vertx) {
vertx.createHttpServer()
.requestHandler(req -> req.response().endAndForget("Hello from Blue!"))
.listenAndAwait(port);
}
}
アプリケーションの起動時に、新しいHTTPサーバー(Vert.xを使用)を作成し、シンプルなサービスを実装します。各HTTPリクエストに対して、"Hello from Blue!"をボディとするレスポンスを送信します。
同じ要領で、以下の内容の src/main/java/org/acme/services/RedService.java
を作成します。
package org.acme.services;
import io.quarkus.runtime.StartupEvent;
import io.vertx.mutiny.core.Vertx;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
@ApplicationScoped
public class RedService {
@ConfigProperty(name = "red-service-port", defaultValue = "9001") int port;
/**
* Start an HTTP server for the red service.
*
* Note: this method is called on a worker thread, and so it is allowed to block.
*/
public void init(@Observes StartupEvent ev, Vertx vertx) {
vertx.createHttpServer()
.requestHandler(req -> req.response().endAndForget("Hello from Red!"))
.listenAndAwait(port);
}
}
今回は"Hello from Red!"と書かれています。
Consulでのサービス登録
サービスの実装が完了したので、Consulに登録する必要があります。
StorkはConsulに限らず、他のサービスディスカバリーメカニズムとも統合されています。 |
src/main/java/org/acme/services/Registration.java
ファイルを以下の内容で作成します。
package org.acme.services;
import io.quarkus.runtime.StartupEvent;
import io.vertx.ext.consul.ServiceOptions;
import io.vertx.mutiny.ext.consul.ConsulClient;
import io.vertx.ext.consul.ConsulClientOptions;
import io.vertx.mutiny.core.Vertx;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
@ApplicationScoped
public class Registration {
@ConfigProperty(name = "consul.host") String host;
@ConfigProperty(name = "consul.port") int port;
@ConfigProperty(name = "red-service-port", defaultValue = "9000") int red;
@ConfigProperty(name = "blue-service-port", defaultValue = "9001") int blue;
/**
* Register our two services in Consul.
*
* Note: this method is called on a worker thread, and so it is allowed to block.
*/
public void init(@Observes StartupEvent ev, Vertx vertx) {
ConsulClient client = ConsulClient.create(vertx, new ConsulClientOptions().setHost(host).setPort(port));
client.registerServiceAndAwait(
new ServiceOptions().setPort(red).setAddress("localhost").setName("my-service").setId("red"));
client.registerServiceAndAwait(
new ServiceOptions().setPort(blue).setAddress("localhost").setName("my-service").setId("blue"));
}
}
アプリケーションが起動すると、Vert.x Consul Clientを使ってConsulに接続し、2つのインスタンスを登録します。どちらの登録も同じ名前( my-service
)を使用していますが、同じ サービス の2つのインスタンスであることを示すために異なるIDを使用しています。
RESTクライアントインターフェースとフロントエンドAPI
ここまでは、Storkを使わずに、これから発見するサービス、選択するサービス、呼び出すサービスの骨組を構築してきました。
REST Clientを使ってサービスを呼び出します。
以下の内容で src/main/java/org/acme/MyService.java
ファイルを作成します:
package org.acme;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
/**
* The REST Client interface.
*
* Notice the `baseUri`. It uses `stork://` as URL scheme indicating that the called service uses Stork to locate and
* select the service instance. The `my-service` part is the service name. This is used to configure Stork discovery
* and selection in the `application.properties` file.
*/
@RegisterRestClient(baseUri = "stork://my-service")
public interface MyService {
@GET
@Produces(MediaType.TEXT_PLAIN)
String get();
}
これは、1つのメソッドを含む単純なRESTクライアントインターフェイスです。ただし、 baseUri
属性に注意してください。 stork://
で始まっています。これは、RESTクライアントがサービスインスタンスのディスカバリーとセレクションをStorkに委ねることを指示しています。また、URLの my-service
にもご注目ください。これは、アプリケーションの設定で使用するサービス名です。
RESTクライアントの使用方法を変更するものではありません。以下の内容で src/main/java/org/acme/FrontendApi.java
ファイルを作成します。
package org.acme;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
/**
* A frontend API using our REST Client (which uses Stork to locate and select the service instance on each call).
*/
@Path("/api")
public class FrontendApi {
@RestClient MyService service;
@GET
@Produces(MediaType.TEXT_PLAIN)
public String invoke() {
return service.get();
}
}
通常通り、RESTクライアントを注入して使用します。
Stork Filter
RESTクライアントで設定された baseUri
は、 StorkClientRequestFilter
クラスで処理されます。これは Jakarta REST フィルタ です。メッセージに関連するメタデータを処理する必要がある場合:HTTPヘッダー、クエリパラメーター、メディアタイプ、その他のメタデータなど、メッセージに関連するメタデータを処理する必要がある場合は、別のフィルターを実装して必要なものを構成することができます。それでは、サービスにロギング機能を追加するために、カスタムフィルターを実装してみましょう。 CustomLoggingFilter
を作成し、@Provider アノテーションを付けます:
package org.acme;
import io.vertx.core.http.HttpServerRequest;
import org.jboss.logging.Logger;
import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestContext;
import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestFilter;
import jakarta.ws.rs.ext.Provider;
@Provider
public class CustomLoggingFilter implements ResteasyReactiveClientRequestFilter {
private static final Logger LOG = Logger.getLogger(CustomLoggingFilter.class);
@Override
public void filter(ResteasyReactiveClientRequestContext requestContext) {
LOG.infof("Resolved address by Stork: %s",requestContext.getUri().toString());
}
}
フィルタの実行順序は、 プロパティ で定義されます。 CustomLoggingFilter
はデフォルト値を使用しているので、ユーザーレベルの優先順位、 StorkClientRequestFilter
はセキュリティ認証フィルタの優先順位を使用していることに注意してください。これは、 StorkClientRequestFilter
が CustomLoggingFilter
の前に実行されることを意味します。この挙動を変更するには、 @Priority
アノテーションを使用します。
Stork設定
システムはほぼ完成です。あとはStorkと Registration
Beanを設定するだけです。
src/main/resources/application.properties
に以下を追加します:
consul.host=localhost
consul.port=8500
quarkus.stork.my-service.service-discovery.type=consul
quarkus.stork.my-service.service-discovery.consul-host=localhost
quarkus.stork.my-service.service-discovery.consul-port=8500
quarkus.stork.my-service.load-balancer.type=round-robin
最初の2行は、 Registration
Beanが使用するConsulのロケーションを提供します。
stork.my-service.service-discovery
は、 my-service
サービスの検索に使用するサービスディスカバリーのタイプを示しています。 consul
stork.my-service.service-discovery.consul-host
と stork.my-service.service-discovery.consul-port
は Consul へのアクセスを設定します。最後に、 stork.my-service.load-balancer
は、サービスのセレクションを設定します。今回のケースでは、 round-robin
を使用します。
アプリケーションの実行
できました!では、実際に動くかどうか見てみましょう。
まず、Consulを起動します:
docker run --rm --name consul -p 8500:8500 -p 8501:8501 consul:1.7 agent -dev -ui -client=0.0.0.0 -bind=0.0.0.0 --https-port=8501
Consulを別の方法で起動する場合は、アプリケーションの設定を編集することを忘れないでください。
次に、アプリケーションをパッケージ化します:
quarkus build
./mvnw install
./gradlew build
そして実行します:
> java -jar target/quarkus-app/quarkus-run.jar
別の端末で、次を実行します。
> curl http://localhost:8080/api
...
> curl http://localhost:8080/api
...
> curl http://localhost:8080/api
...
応答は Hello from Red!
と Hello from Blue!
の間で交互に行われます。
このアプリケーションをネイティブ実行可能ファイルにコンパイルして、起動することができます。
quarkus build --native
./mvnw install -Dnative
./gradlew build -Dquarkus.native.enabled=true
次のように起動します:
> ./target/stork-getting-started-1.0.0-SNAPSHOT-runner