Connecting to an Elasticsearch cluster
Elasticsearch 是一个众所周知的全文搜索引擎和 NoSQL 数据存储。 在本指南中,我们将了解如何让你的 REST 服务与 Elasticsearch 集群交互。 Quarkus 提供了两种访问 Elasticsearch 的方法:
-
较低级别的 REST 客户端
-
The Elasticsearch Java client
对于“高级 REST 客户端”,曾经存在过一个第三个 Quarkus 扩展,但它已被移除,因为 Elastic 已将此客户端弃用,并且它有一些许可问题。
- Prerequisites
- Architecture
- Creating the Maven project
- Creating your first JSON REST service
- Configuring Elasticsearch
- Running an Elasticsearch cluster
- Running the application
- Using the Elasticsearch Java Client
- Hibernate Search Elasticsearch
- Cluster Health Check
- Building a native executable
- Conclusion
- Configuration Reference
Architecture
本指南中的应用程序非常简单:用户可以使用窗体在列表中添加元素,然后更新列表。
浏览器和服务器之间所有信息都采用 JSON 格式。
元素存储在 Elasticsearch 中。
Creating the Maven project
首先,我们需要一个新项目。使用以下命令创建一个新项目:
quarkus create app {create-app-group-id}:{create-app-artifact-id} \
--no-code
cd {create-app-artifact-id}
要创建一个 Gradle 项目,添加 --gradle
或 --gradle-kotlin-dsl
选项。
有关如何安装和使用 Quarkus CLI 的详细信息,请参见 Quarkus CLI 指南。
mvn {quarkus-platform-groupid}:quarkus-maven-plugin:{quarkus-version}:create \
-DprojectGroupId={create-app-group-id} \
-DprojectArtifactId={create-app-artifact-id} \
-DnoCode
cd {create-app-artifact-id}
要创建一个 Gradle 项目,添加 -DbuildTool=gradle
或 -DbuildTool=gradle-kotlin-dsl
选项。
适用于 Windows 用户:
-
如果使用 cmd,(不要使用反斜杠
\
,并将所有内容放在同一行上) -
如果使用 Powershell,将
-D
参数用双引号引起来,例如"-DprojectArtifactId={create-app-artifact-id}"
此命令生成一个 Maven 结构,该结构会导入 Quarkus REST(以前的 RESTEasy Reactive)、Jackson 以及 Elasticsearch 低级 REST 客户端扩展。
Elasticsearch 低级 REST 客户端随 `quarkus-elasticsearch-rest-client`一起提供,该扩展已添加到您的构建文件中。
如果您想改用 Elasticsearch Java 客户端,请使用 `quarkus-elasticsearch-java-client`扩展替换 `quarkus-elasticsearch-rest-client`扩展。
我们在此处使用 `rest-jackson`扩展,未使用 JSON-B 变体,因为我们将使用 Vert.x `JsonObject`助手,利用此助手,可以将我们的对象序列化/反序列化为 Elasticsearch 的对象或从 Elasticsearch 的对象序列化/反序列化我们的对象,并且该助手实际上使用的是 Jackson。 |
如需将扩展添加到现有项目,请按照以下说明进行操作。
对于 Elasticsearch 低级 REST 客户端,请将以下依赖关系添加到您的构建文件:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-elasticsearch-rest-client</artifactId>
</dependency>
implementation("io.quarkus:quarkus-elasticsearch-rest-client")
对于 Elasticsearch Java 客户端,请将以下依赖关系添加到您的构建文件:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-elasticsearch-java-client</artifactId>
</dependency>
implementation("io.quarkus:quarkus-elasticsearch-java-client")
Creating your first JSON REST service
在此示例中,我们将创建一个应用程序来管理水果列表。
首先,我们按如下方式创建 `Fruit`bean:
package org.acme.elasticsearch;
public class Fruit {
public String id;
public String name;
public String color;
}
没什么新奇之处。需要特别注意的是,JSON 序列化层要求有缺省构造函数。
现在创建 org.acme.elasticsearch.FruitService
,它将成为我们应用程序的业务层,并将把水果存储到 Elasticsearch 实例或从 Elasticsearch 实例加载水果。在此,我们使用低级 REST 客户端,如果您想改用 Java API 客户端,请改而按照 Using the Elasticsearch Java Client段中的说明进行操作。
package org.acme.elasticsearch;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.apache.http.util.EntityUtils;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.Response;
import org.elasticsearch.client.RestClient;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
@ApplicationScoped
public class FruitService {
@Inject
RestClient restClient; (1)
public void index(Fruit fruit) throws IOException {
Request request = new Request(
"PUT",
"/fruits/_doc/" + fruit.id); (2)
request.setJsonEntity(JsonObject.mapFrom(fruit).toString()); (3)
restClient.performRequest(request); (4)
}
public Fruit get(String id) throws IOException {
Request request = new Request(
"GET",
"/fruits/_doc/" + id);
Response response = restClient.performRequest(request);
String responseBody = EntityUtils.toString(response.getEntity());
JsonObject json = new JsonObject(responseBody); (5)
return json.getJsonObject("_source").mapTo(Fruit.class);
}
public List<Fruit> searchByColor(String color) throws IOException {
return search("color", color);
}
public List<Fruit> searchByName(String name) throws IOException {
return search("name", name);
}
private List<Fruit> search(String term, String match) throws IOException {
Request request = new Request(
"GET",
"/fruits/_search");
//construct a JSON query like {"query": {"match": {"<term>": "<match"}}
JsonObject termJson = new JsonObject().put(term, match);
JsonObject matchJson = new JsonObject().put("match", termJson);
JsonObject queryJson = new JsonObject().put("query", matchJson);
request.setJsonEntity(queryJson.encode());
Response response = restClient.performRequest(request);
String responseBody = EntityUtils.toString(response.getEntity());
JsonObject json = new JsonObject(responseBody);
JsonArray hits = json.getJsonObject("hits").getJsonArray("hits");
List<Fruit> results = new ArrayList<>(hits.size());
for (int i = 0; i < hits.size(); i++) {
JsonObject hit = hits.getJsonObject(i);
Fruit fruit = hit.getJsonObject("_source").mapTo(Fruit.class);
results.add(fruit);
}
return results;
}
}
1 | 我们向我们的服务中注入一个 Elasticsearch 低级 RestClient 。 |
2 | 我们创建一个 Elasticsearch 请求。 |
3 | 我们使用 Vert.x `JsonObject`在将对象发送到 Elasticsearch 之前对其进行序列化,您可以使用任何方法将对象序列化为 JSON。 |
4 | 我们将请求(在此为索引请求)发送到 Elasticsearch。 |
5 | 为了从 Elasticsearch 反序列化对象,我们再次使用 Vert.x JsonObject 。 |
现在,按以下方式创建类 org.acme.elasticsearch.FruitResource
:
package org.acme.elasticsearch;
import jakarta.inject.Inject;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Response;
import java.io.IOException;
import java.net.URI;
import java.util.List;
import java.util.UUID;
import org.jboss.resteasy.reactive.RestQuery;
@Path("/fruits")
public class FruitResource {
@Inject
FruitService fruitService;
@POST
public Response index(Fruit fruit) throws IOException {
if (fruit.id == null) {
fruit.id = UUID.randomUUID().toString();
}
fruitService.index(fruit);
return Response.created(URI.create("/fruits/" + fruit.id)).build();
}
@GET
@Path("/{id}")
public Fruit get(String id) throws IOException {
return fruitService.get(id);
}
@GET
@Path("/search")
public List<Fruit> search(@RestQuery String name, @RestQuery String color) throws IOException {
if (name != null) {
return fruitService.searchByName(name);
} else if (color != null) {
return fruitService.searchByColor(color);
} else {
throw new BadRequestException("Should provide name or color query parameter");
}
}
}
实现方式非常简单,你只需使用 Jakarta REST 注释定义端点,并使用 FruitService
来列出/添加新水果。
Configuring Elasticsearch
要配置的主要属性是用于与 Elasticsearch 集群连接的 URL。
对于一个典型的集群 Elasticsearch 服务,示例配置应如下所示:
# configure the Elasticsearch client for a cluster of two nodes
quarkus.elasticsearch.hosts = elasticsearch1:9200,elasticsearch2:9200
在我们的例子中,我们使用在 localhost: 上运行的一个实例:
# configure the Elasticsearch client for a single instance on localhost
quarkus.elasticsearch.hosts = localhost:9200
如果你需要更高级的配置,你可以在此指南的末尾找到支持的配置属性的完整列表。
Dev Services
Quarkus 支持一项称为 Dev Services 的功能,它允许你在没有配置的情况下启动各种容器。在 Elasticsearch 的情况下,这种支持扩展到了默认的 Elasticsearch 连接。这意味着如果你没有配置 quarkus.elasticsearch.hosts
,Quarkus 将在运行测试或开发模式时自动启动一个 Elasticsearch 容器,并自动配置连接。
在运行应用程序的生产版本时,需要像往常一样配置 Elasticsearch 连接,因此如果你想在 application.properties
中包含生产数据库配置,并继续使用 Dev Services,我们建议你使用 %prod.
配置文件来定义 Elasticsearch 设置。
更多信息,你可以阅读 Dev Services for Elasticsearch guide。
Programmatically Configuring Elasticsearch
在参数化配置之上,你还可以通过实现 RestClientBuilder.HttpClientConfigCallback
并使用 ElasticsearchClientConfig
对其进行注释,以编程方式将其他配置应用于客户端。你可以提供多个实现,并且每个实现提供的配置将以随机排序级联方式应用。
例如,在访问在 HTTP 层上针对 TLS 设置的 Elasticsearch 集群时,客户端需要信任 Elasticsearch 使用的证书。以下是设置客户端信任由 Elasticsearch 使用的证书颁发机构 (CA) 的示例,当时该 CA 证书在 PKCS#12 密钥库中。
import io.quarkus.elasticsearch.restclient.lowlevel.ElasticsearchClientConfig;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.SSLContexts;
import org.elasticsearch.client.RestClientBuilder;
import jakarta.enterprise.context.Dependent;
import javax.net.ssl.SSLContext;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyStore;
@ElasticsearchClientConfig
public class SSLContextConfigurator implements RestClientBuilder.HttpClientConfigCallback {
@Override
public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) {
try {
String keyStorePass = "password-for-keystore";
Path trustStorePath = Paths.get("/path/to/truststore.p12");
KeyStore truststore = KeyStore.getInstance("pkcs12");
try (InputStream is = Files.newInputStream(trustStorePath)) {
truststore.load(is, keyStorePass.toCharArray());
}
SSLContextBuilder sslBuilder = SSLContexts.custom()
.loadTrustMaterial(truststore, null);
SSLContext sslContext = sslBuilder.build();
httpClientBuilder.setSSLContext(sslContext);
} catch (Exception e) {
throw new RuntimeException(e);
}
return httpClientBuilder;
}
}
有关此特定示例的更多详细信息,请参见 Elasticsearch documentation。
默认情况下,标有 |
Running an Elasticsearch cluster
由于默认情况下,Elasticsearch 客户端配置为访问端口 9200(默认 Elasticsearch 端口)上的本地 Elasticsearch 集群,如果你在这个端口上有一个本地运行实例,那么在测试之前不需要执行任何其他操作!
如果你想使用 Docker 运行 Elasticsearch 实例,可以使用以下命令启动一个:
docker run --name elasticsearch -e "discovery.type=single-node" -e "ES_JAVA_OPTS=-Xms512m -Xmx512m"\
-e "cluster.routing.allocation.disk.threshold_enabled=false" -e "xpack.security.enabled=false"\
--rm -p 9200:9200 {elasticsearch-image}
Running the application
我们在开发模式下启动应用程序:
quarkus dev
./mvnw quarkus:dev
./gradlew --console=plain quarkusDev
你可以通过以下 curl 命令向列表中添加新水果:
curl localhost:8080/fruits -d '{"name": "bananas", "color": "yellow"}' -H "Content-Type: application/json"
并通过以下 curl 命令按名称或颜色搜索水果:
curl localhost:8080/fruits/search?color=yellow
Using the Elasticsearch Java Client
这里有一个 FruitService
版本,其中使用 Elasticsearch Java 客户端,而不是底层版本:
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders;
import co.elastic.clients.elasticsearch.core.*;
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.acme.elasticsearch.Fruit;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
@ApplicationScoped
public class FruitService {
@Inject
ElasticsearchClient client; (1)
public void index(Fruit fruit) throws IOException {
IndexRequest<Fruit> request = IndexRequest.of( (2)
b -> b.index("fruits")
.id(fruit.id)
.document(fruit)); (3)
client.index(request); (4)
}
public Fruit get(String id) throws IOException {
GetRequest getRequest = GetRequest.of(
b -> b.index("fruits")
.id(id));
GetResponse<Fruit> getResponse = client.get(getRequest, Fruit.class);
if (getResponse.found()) {
return getResponse.source();
}
return null;
}
public List<Fruit> searchByColor(String color) throws IOException {
return search("color", color);
}
public List<Fruit> searchByName(String name) throws IOException {
return search("name", name);
}
private List<Fruit> search(String term, String match) throws IOException {
SearchRequest searchRequest = SearchRequest.of(
b -> b.index("fruits")
.query(QueryBuilders.match().field(term).query(FieldValue.of(match)).build()._toQuery()));
SearchResponse<Fruit> searchResponse = client.search(searchRequest, Fruit.class);
HitsMetadata<Fruit> hits = searchResponse.hits();
return hits.hits().stream().map(hit -> hit.source()).collect(Collectors.toList());
}
}
1 | 我们在服务中插入`ElasticsearchClient`。 |
2 | 我们使用构建器创建 Elasticsearch 索引请求。 |
3 | 我们直接将对象传递给请求,因为 Java API 客户端具有序列化层。 |
4 | 我们将请求发送给 Elasticsearch。 |
Hibernate Search Elasticsearch
Quarkus 通过 `quarkus-hibernate-search-orm-elasticsearch`扩展支持使用 Elasticsearch 的 Hibernate 搜索。
Hibernate Search Elasticsearch 允许将您的 Jakarta Persistence 实体同步到 Elasticsearch 集群,并提供通过 Hibernate Search API 查询 Elasticsearch 集群的方法。
如果您对此感兴趣,请查阅 Hibernate Search with Elasticsearch guide。
Cluster Health Check
如果您使用 `quarkus-smallrye-health`扩展,这两个扩展将自动添加就绪性健康检查以验证集群的运行状况。
因此,当您访问 `/q/health/ready`应用程序的端点时,您将获得有关集群状态的信息。它使用集群运行状况端点,如果集群的状态是 red,或者集群不可用,则检查将关闭。
可以通过在 application.properties
中将 `quarkus.elasticsearch.health.enabled`属性设置为 `false`来禁用此行为。
Building a native executable
您可以在本机可执行文件中使用这两个客户端。
您可以使用以下常用命令构建一个本机可执行文件:
quarkus build --native
./mvnw install -Dnative
./gradlew build -Dquarkus.native.enabled=true
只需要执行 ./target/elasticsearch-low-level-client-quickstart-1.0.0-SNAPSHOT-runner
即可运行它。
然后您可以将浏览器指向 http://localhost:8080/fruits.html
,并使用您的应用程序。