SmallRye Stork 入门
分布式系统的本质在于服务之间的交互。在现代架构中,您通常会有多个服务实例来分摊负载或通过冗余来提高弹性。但是,您如何选择服务的最佳实例?这就是 SmallRye Stork 的用武之地。Stork 将选择最合适的实例。它提供
-
可扩展的服务发现机制
-
内置 Consul 和 Kubernetes 支持
-
可定制的客户端负载均衡策略
此技术被认为是预览版。 在预览版中,不保证向后兼容性和生态系统中的存在。具体改进可能需要更改配置或 API,并且正在制定成为稳定版的计划。欢迎通过我们的邮件列表或我们的GitHub 问题跟踪器中的问题提供反馈。 有关可能的完整状态列表,请查看我们的常见问题解答条目。 |
先决条件
要完成本指南,您需要
-
大约 15 分钟
-
一个 IDE
-
已安装 JDK 17+ 并正确配置了
JAVA_HOME
-
Apache Maven 3.9.9
-
一个正常工作的容器运行时(Docker 或 Podman)
-
如果您想使用它,可以选择 Quarkus CLI
-
如果您想构建本机可执行文件(或者如果您使用本机容器构建,则为 Docker),可以选择安装 Mandrel 或 GraalVM 并进行适当的配置
架构
在本指南中,我们将构建一个由以下部分组成的应用:
-
一个在端口 9000 上公开的简单蓝色服务
-
一个在端口 9001 上公开的简单红色服务
-
一个调用蓝色或红色服务的 REST 客户端(选择委托给 Stork)
-
一个使用 REST 客户端并调用服务的 REST 端点
-
蓝色和红色服务已在 Consul 中注册。

为了简单起见,所有内容(Consul 除外)都将在同一个 Quarkus 应用程序中运行。当然,在现实世界中,每个组件都会在其自己的进程中运行。
解决方案
我们建议您按照下一节中的说明逐步创建应用程序。但是,您可以直接转到完整的示例。
克隆 Git 存储库:git clone https://github.com/quarkusio/quarkus-quickstarts.git
,或下载 archive。
该解决方案位于 stork-quickstart
目录中。
发现和选择
在继续之前,我们需要讨论发现与选择。
-
服务发现是定位服务实例的过程。它生成一个可能为空(如果没有服务匹配请求)或包含多个服务实例的服务实例列表。
-
服务选择,也称为负载均衡,从发现过程返回的列表中选择最佳实例。结果是一个服务实例,或者当找不到合适的实例时抛出一个异常。
Stork 处理发现和选择。但是,它不处理与服务的通信,而只提供一个服务实例。Quarkus 中的各种集成从该服务实例中提取服务的位置。

引导项目
创建一个 Quarkus 项目,使用您喜欢的方法导入 quarkus-rest-client、quarkus-rest 和 quarkus-smallrye-stork 扩展
对于 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 并注册我们的两个实例。两个注册都使用相同的名称 (my-service
),但使用不同的 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();
}
这是一个简单的 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 过滤器
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
前两行提供了 Registration
bean 使用的 Consul 位置。
其他属性与 Stork 相关。stork.my-service.service-discovery
指示我们将使用哪种类型的服务发现来定位 my-service
服务。在我们的例子中,它是 consul
。quarkus.stork.my-service.service-discovery.consul-host
和 quarkus.stork.my-service.service-discovery.consul-port
配置对 Consul 的访问。最后,quarkus.stork.my-service.load-balancer.type
配置服务选择。在我们的例子中,我们使用 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 https://:8080/api
...
> curl https://:8080/api
...
> curl https://: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