1. 介绍

这篇文档是由 Elasticsearch 官方提供的 Java API 客户端文档。该客户端为所有 Elasticsearch API 提供功能强大的请求以及响应。

特性

  • 为所有 Elasticsearch API 提供功能强大的请求以及响应。

  • 所有 API 都提供阻塞以及非阻塞两个版本。

  • 提供了 fluent builders 和 functional patterns ,在创建复杂层级结构时可以更方便的编写简洁易读的代码。

  • 通过使用 JSON 对象 mapper (例如 Jackson 或者是 JSON-B 的其它任何实现)来无缝集成应用类。

  • 将协议委托给 http 客户端处理(如 Java 底层 REST 客户端 ),该客户端负责所有传输相关的问题:HTTP连接池、重试策略、节点嗅探等。

7.15 版本之后的重大变更

7.15版本的 Java API Client 是一个 Beta 版本。我们从用户反馈中吸收了很多经验,最终我们在该版本做出了一些重大的变更:

  • 包含顶级 Elasticsearch API 的 _core 包已经重命名为 core

  • list 和 map 属性的 setter 方法重新可用了。

  • 现在所有 API 的数据类型都支持了,而不再是原来的 JsonValue 属性。

  • 支持的 JSON 框架以及传输类都已经被重构和改进。但是调用高级 API 的方式并没有改变,所以对现有的应用程序应该没有影响。

这个版本的 Java API Client 实现了除了 Vector tile search API 以及 Find structure API 这两个 API 之外的所有 Elasticsearch API。

虽然这个版本我们认为已经很稳定了,但是其中的大多数代码都是根据 Elasticsearch API 规范说明书 自动生成的,其中一些不太常用的 Elasticsearch API 仍然需要进行完整详细的测试。 当然这篇说明书中可能影响到应用的代码,在发生变更后,都会在后续的 Java API client 版本中被明确地标识出来。

兼容性

main 分支的目标是下一个主要版本(8.0),7.x 分支的目标是 7.x 系列的下一个次要版本。

Elasticsearch Java 客户端是向前兼容的;也就是说它支持与次要版本更高或相等的 Elasticsearch 进行通信。 Elasticsearch 语言的的客户端则仅仅向后兼容默认发行版,且不提供任何保证。

2. 安装

要求:

  • Java 8 或更高的版本。

  • 需要一个 JSON 对象映射库来让应用类与 Elasticsearch API 进行无缝的集成。 Java 客户端支持 Jackson 或者是其它的 JSON-B库,例如 Eclipse Yasson

Release 版本托管在 Maven 中央仓库中。 如果你想找 SNAPSHOT 版本,可以在 Elastic Maven Snapshot 仓库中找到 https://snapshots.elastic.co/maven/

在 Gradle 项目中安装并使用 Jackson

dependencies {
    implementation 'co.elastic.clients:elasticsearch-java:7.16.2'
    implementation 'com.fasterxml.jackson.core:jackson-databind:2.12.3'
}

在 Maven 项目中安装并使用 Jackson

在项目的 pom.xml 文件中,添加下面的依赖:

<project>

  <dependencies>
    <dependency>
      <groupId>co.elastic.clients</groupId>
      <artifactId>elasticsearch-java</artifactId>
      <version>7.16.2</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.12.3</version>
    </dependency>
  </dependencies>

</project>

3. 连接

Java API Client 中包含三大重要组件:

  • 客户端API类。 一些为 Elasticsearch API提供强类型数据结构和方法。 由于 Elasticsearch API 非常庞大,所以它会按照功能来进行分组(也可以叫 “命名空间“),每个分组都会有它们特有的客户端类。 Elasticsearch 核心功能会在 ElasticsearchClient 类中实现。

  • JSON对象映射器。 它会将你的应用业务类与一个JSON对象进行映射,这样就能无缝的与 API 客户端进行无缝集成了。

  • 传输层实现。 这是负责处理所有 HTTP 请求的地方。

下面就是创建并连接这三个组件的代码:

// Create the low-level client
RestClient restClient = RestClient.builder(
    new HttpHost("localhost", 9200)).build();

// Create the transport with a Jackson mapper
ElasticsearchTransport transport = new RestClientTransport(
    restClient, new JacksonJsonpMapper());

// And create the API client
ElasticsearchClient client = new ElasticsearchClient(transport);

Java 底层 REST 客户端 这里可以管理身份认证。 有关身份认证的更多配置信息,请参阅 这篇文档

发送第一个请求

下面的代码片中我们会搜索 “product” 索引,并找到 name 匹配 “bicycle” 的所有对象,然后将他们封装为 Product 这个应用业务类返回。

这段代码向我们演示了,如何使用 fluent 函数式构造器,以 DSL 这样的更简洁的方式编写查询语句。 API 规约 中对此有更详细的解释。

SearchResponse<Product> search = client.search(s -> s
    .index("products")
    .query(q -> q
        .term(t -> t
            .field("name")
            .value(v -> v.stringValue("bicycle"))
        )),
    Product.class);

for (Hit<Product> hit: search.hits().hits()) {
    processProduct(hit.source());
}

4. 从 High Level Rest Client 迁移到全新的库

Elasticsearch Java API 客户端是一个全新的库,和之前的 High Level Rest Client (HLRC) 无关。 这是我们深思熟虑之后的选择,它提供一个独立于 Elasticsearch 服务器代码之外的库,并为所有 Elasticsearch 功能提供了一套一致性非常高且更易于使用的 API。

因此,在从之前的 HLRC(High Level Rest Client) 迁移之前,你可能需要重写一部分应用代码。 但是,这个迁移过程可以一点一点的逐步进行,因为在一个应用中可以同时包含新旧两个客户端库,而无需任何额外开销。

迁移攻略

这里提供几种不同的方式,让你的应用顺利地从 HLRC 代码转换为新的代码。

例如:

  • 保持现有的代码不变,在应用开发新功能的时候,使用全新的 Java API 客户端,并在后续逐步迁移现有代码。

  • 根据新客户端比 HLRC 更易于使用的部分重写应用代码,比如所有与搜索相关的代码。

  • 利用新客户端与 JSON 对象的无缝集成,重写那些在 JSON 和应用业务类之间做映射的旧代码。

使用与 HLRC 和新客户端相同的传输层

为了避免应用在同时使用新客户端和旧 HLRC 的过渡阶段出现额外开销,这两个客户端公用一个底层Rest客户端(Low Level Rest Client), 这是管理所有连接、重试策略、节点嗅探等功能的网络层。

下面的代码向我们展示了如何使用同一个 HTTP 客户端初始化两种客户端:

// Create the low-level client
RestClientBuilder httpClientBuilder = RestClient.builder(
    new HttpHost("localhost", 9200)
);

// Create the HLRC
RestHighLevelClient hlrc = new RestHighLevelClient(httpClientBuilder);

// Create the new Java Client with the same low level client
ElasticsearchTransport transport = new RestClientTransport(
    hlrc.getLowLevelClient(),
    new JacksonJsonpMapper()
);

ElasticsearchClient esClient = new ElasticsearchClient(transport);

// hlrc and esClient share the same httpClient

5. API 规约

Java API Client 使用一致性非常强的代码结构,使用这种现代化的编码模式,可以更容易的编写复杂的请求,也可以更容易的处理更加复杂的响应。 本章会向你详细解释,让您快速上手。

包结构和命名空间下的客户端

Elasticsearch API 很庞大,它们被按功能分成多个组,可以在 Elasticsearch API 文档 中查看。

Java API Client 遵循这种结构:名为 “命名空间“ 的功能分组,还有所有命名空间都被放在 co.elastic.clients.elasticsearch 子包下。

命名空间下的每个客户端都可以在顶级 Elasticsearch 客户端下被访问。 唯一例外的是 “search” 和 “document” API, 它们放在 core 子包下,并可以在主 Elasticsearch 客户端对象中被访问。

下面的代码片段展示了如何使用 indices 命名空间客户端来创建 index(lambda 语法 解释在这里 ):

// Create the "products" index
ElasticsearchClient client = ...
client.indices().create(c -> c.index("products"));

命名空间客户端是一个运行时创建的非常轻量级的对象。

方法命名规约

Java API Client 中的类包含两种方法和属性:

  • 方法和属性作为 API 的一部分,比如 ElasticsearchClient.search()SearchResponse.maxScore() , 它们使用标准 Java 驼峰 规约从 Elasticsearch JSON API 中各自的名称中派生而来。

  • 方法和属性作为构建 Java API 客户端框架的一部分,比如 Query._kind() , 这些方法使用下划线作为前缀,来避免与 API 的名称发生冲突,作为区分 API 和框架的简单方法。

不可变对象、构造器以及构造器的 lambda 表达式用法

Java API Client 中的所有数据类型都是不可变的。 对象的创建使用 构造器模式, 该模式于 2008 年出版的 Effective Java 一书中被广泛推广。

ElasticsearchClient client = ...
        CreateIndexResponse createResponse = client.indices().create(
            new CreateIndexRequest.Builder()
                .index("my-index")
                .aliases("foo",
                    new Alias.Builder().isWriteIndex(true).build()
                )
                .build()
        );

需要注意的是,在 build() 方法被调用后,该构造器就不应该被再次使用了。

虽然它可以正常执行,但是不难发现,实例化构造器并调用 build() 方法的代码稍微有点冗长。 因此,Java API Client 中每个属性的 setter 方法也可以传递一个 lambda 表达式,这个表达式会使用新创建的构造器作为参数,并返回处理后的构造器。 所以上面的代码片段也可以这样写:

ElasticsearchClient client = ...
        CreateIndexResponse createResponse = client.indices()
            .create(createIndexBuilder -> createIndexBuilder
                .index("my-index")
                .aliases("foo", aliasBuilder -> aliasBuilder
                    .isWriteIndex(true)
                )
            );

这种方法可以以更简洁的代码,并且还能避开了导入类(甚至连它们的名字都不需要记住),因为这个类型是从方法参数签名中推断出来的。

请注意,上面的示例代码中,构造器变量仅仅是为了链式调用所使用。 因此这些变量的名称就不是很重要了,于是我们就可以缩短变量的命名来提高可读性:

ElasticsearchClient client = ...
        CreateIndexResponse createResponse = client.indices()
            .create(c -> c
                .index("my-index")
                .aliases("foo", a -> a
                    .isWriteIndex(true)
                )
            );

构造器的 lambda 对复杂嵌套查询语句很有用,比如下面的查询代码,它取自 intervals query API 文档

这个示例还强调了在编写深层嵌套结构中,一个很有用的构造器参数命名规约。 对于只有一个参数的 lambda 表达式,Kotlin 提供了一个隐式的 it 参数,并且 Scala 也允许使用 _。 在 Java 中,可以使用下划线前缀,后面跟上代表深度级别的数字(例如 _0_1 等等)。 这样做不仅避免了多次命名一次性变量,而且也能极大地提高代码可读性。 还有正确的缩进也可以突出查询语句的层级结构。

ElasticsearchClient client = ...
        SearchResponse<SomeApplicationData> results = client
            .search(_0 -> _0
                .query(_1 -> _1
                    .intervals(_2 -> _2
                        .field("my_text")
                        .allOf(_3 -> _3
                            .ordered(true)
                            .intervals(_4 -> _4
                                .match(_5 -> _5
                                    .query("my favorite food")
                                    .maxGaps(0)
                                    .ordered(true)
                                )
                            )
                            .intervals(_4 -> _4
                                .anyOf(_5 -> _5
                                    .intervals(_6 -> _6
                                        .match(_7 -> _7
                                            .query("hot water")
                                        )
                                    )
                                    .intervals(_6 -> _6
                                        .match(_7 -> _7
                                            .query("cold porridge")
                                        )
                                    )
                                )
                            )
                        )
                    )
                ),
            SomeApplicationData.class (1)
        );
1 查询结果会映射到 SomeApplicationData 示例,以便应用随时使用。

Lists 和 maps

构造器的链式方法

对象构造器将 ListMap 的属性以链式添加的方法暴露出来,这些方法通过向 lists 或 maps 添加新元素(或替换现有元素)来更新其属性值。

对象构造器会创建一个不可变对象,并且在调用构造函数的时候同样应用到 list 和 map 属性。

// Prepare a list of index names
List<String> names = Arrays.asList("idx-a", "idx-b", "idx-c");

// Prepare cardinality aggregations for fields "foo" and "bar"
Map<String, Aggregation> cardinalities = new HashMap<>();
cardinalities.put("foo-count", Aggregation.of(a -> a.cardinality(c -> c.field("foo"))));
cardinalities.put("bar-count", Aggregation.of(a -> a.cardinality(c -> c.field("bar"))));

// Prepare an aggregation that computes the average of the "size" field
final Aggregation avgSize = Aggregation.of(a -> a.avg(v -> v.field("size")));

SearchRequest search = SearchRequest.of(r -> r
    // Index list:
    // - add all elements of a list
    .index(names)
    // - add a single element
    .index("idx-d")
    // - add a vararg list of elements
    .index("idx-e", "idx-f", "idx-g")

    // Sort order list: add elements defined by builder lambdas
    .sort(s -> s.field(f -> f.field("foo").order(SortOrder.Asc)))
    .sort(s -> s.field(f -> f.field("bar").order(SortOrder.Desc)))

    // Aggregation map:
    // - add all entries of an existing map
    .aggregations(cardinalities)
    // - add a key/value entry
    .aggregations("avg-size", avgSize)
    // - add a key/value defined by a builder lambda
    .aggregations("price-histogram",
        a -> a.histogram(h -> h.field("price")))
);

List 和 map 的值从不为 null

Elasticsearch API 有很多可选属性。对于单独一个值的属性,Java API Client 默认设置其为 null。因此,应用在使用之前必须对其进行非空校验。

但是对于 lists 和 maps,应用一般只会关心它们里面有没有包含值,甚至会对它们的内容进行迭代,这时如果使用 null 就很麻烦。 为了避免这种情况,Java API Client 的集合属性从不为 null,未设置的值会作为空集合返回。

如果你想要区分一个集合是缺省的(未定义的)还是 Elasticsearch 正常返回空集合,可以使用 ApiTypeHelper 类,该类提供了一个非常使用的方法来区分它们:

NodeStatistics stats = NodeStatistics.of(b -> b
    .total(1)
    .failed(0)
    .successful(1)
);

// The `failures` list was not provided.
// - it's not null
assertNotNull(stats.failures());
// - it's empty
assertEquals(0, stats.failures().size());
// - and if needed we can know it was actually not defined
assertFalse(ApiTypeHelper.isDefined(stats.failures()));

可变类型

Elasticsearch API 有很多可变类型:查询(queries)、聚合(aggregations)、字段映射(field mappings)、分析器(analyzers)等等。 在这么多的名字中找到正确的类名可能是一个挑战。

Java API Client 构造器简化了这一过程:可变类型(如 Query)的构造器拥有其所有可用的方法。 我们已经在上面代码中的 intervals (一种查询语句)和 allOfmatch 以及 anyOf(各种类型的 intervals) 中看到这一点。

这是因为 Java API Client 中的可变对象是 “标签联合“(tagged union) 的实现:它们包含了变量的 id(或标签)以及该变量的值。 例如,Query(查询)对象就包含 IntervalsQueryintervals 方法)、TermQueryterm 方法)等等。 使用这种方法可以快速流畅地编写代码,让IDE自动提示的功能帮助您构建复杂的嵌套结构:

所有可用的方法,可变类型的构造器都有其调用方法。 它们与常规属性使用了相同的约定,并接受 lambda 表达式和内置的可变类型参数。 下面是一个构造查询条件语句的例子:

Query query = new Query.Builder()
    .term(t -> t                          (1)
        .field("name")                    (2)
        .value(v -> v.stringValue("foo"))
    )
    .build();                             (3)
1 使用 term 可变类型来构建条件语句。
2 通过 lambda 表达式构建一个查询语句。
3 构建 Query,其中包含一个 term 类型的 TermQuery 对象。

可变对象对所有可用的实现都有getter方法。这些方法会检测对象是否拥有该类型的可变类型,并返回正确的类型。 否则会抛出 IllegalStateException 异常。使用这种方法可以更流畅的编写代码来遍历可变对象。

assertEquals("foo", query.term().value().stringValue());

可变对象还保存它们中变体类型的信息:

  • 所有可变类型都有其 is 方法: isTerm()isIntervals()isFuzzy() 等。

  • 带有 Kind 嵌套枚举的可变类型定义。

在检查它们的实际类型后,可以使用这些信息找到指定的可变对象:

if (query.isTerm()) { (1)
    doSomething(query.term());
}

switch(query._kind()) { (2)
    case Term:
        doSomething(query.term());
        break;
    case Intervals:
        doSomething(query.intervals());
        break;
    default:
        doSomething(query._kind(), query._get()); (3)
}
1 判断可变对象是指定类型。
2 判断可变对象的可变类型定义。
3 获取可变对象的 Kind 以及值。

阻塞式客户端以及异步客户端

API客户端有两种风格:阻塞式以及异步式。 异步客户端的所有方法都会返回标准的 CompletableFuture

你可以根据你的需要选择你喜欢的方式,它们使用相同的传输对象:

ElasticsearchTransport transport = ...

        // Synchronous blocking client
        ElasticsearchClient client = new ElasticsearchClient(transport);

        if (client.exists(b -> b.index("products").id("foo")).value()) {
            logger.info("product exists");
        }

        // Asynchronous non-blocking client
        ElasticsearchAsyncClient asyncClient =
            new ElasticsearchAsyncClient(transport);

        asyncClient
            .exists(b -> b.index("products").id("foo"))
            .thenAccept(response -> {
                if (response.value()) {
                    logger.info("product exists");
                }
            });

异常

客户端方法会抛出下面两种类型的异常:

  • Elasticsearch 服务器接受但是拒绝的请求(比如验证错误、服务器内部超市等)会抛出 ElasticsearchException 异常。 这个异常是由 Elasticsearch 提供的,其中包含错误的详情。

  • 没有发送到服务器的请求(网络错误、服务器无法访问等)会抛出 TransportException 这种异常,这个异常是底层实现方法抛出的。 对于 RestClientTransport ,会包含底层HTTP响应的 ResponseException

对象的生命周期

Java API Client 对象有五种不同的生命周期:

Object mapper

无状态并且线程安全,但其创建所消耗的资源很高。 通常在应用程序启动时会创建一个单例,用于传输层(transport)的创建。

Transport

线程安全,通过底层的HTTP客户端持有网络资源。 Transport(传输对象)与 Elasticsearch 集群相关联,必须显式地将其关闭来释放底层资源,例如网络连接。

Clients

不可变、无状态以及线程安全。 是非常轻量级的对象,其中包含传输层并提供API接口的调用方法。

Builders

可变的、非线程安全。 Builders(构造器)是临时对象,在调用 build() 方法后就不应该再重用了。

Requests & other API objects

不可变、线程安全。 如果应用来来回回反复的使用相同的请求或请求的相同不分,则可以提前将这部分对象准备出来,并通过不同的传输对象来在多个客户端之间调用时进行重用。

6. Java 底层 REST 客户端

底层客户端的功能包括:

  • 最小化依赖

  • 跨节点的负载均衡

  • 在节点故障以及特定响应代码之间进行故障切换

  • 连接失败后的重试机制(对失败节点的重试取决于其连接失败的次数; 失败次数越多,客户端在再次尝试重连该节点之前所等待的时间就越长)

  • 长连接

  • 追踪请求和响应的日志

  • 可选的嗅探功能: 自动发现集群节点

6.1. 快速入门

本章将向您介绍,如何快速的上手底层REST客户端,从获取artifact并在应用中使用它。

6.1.1. Javadoc

底层REST客户端的javadoc可以在这里找到 {rest-client-javadoc}/index.html。

6.1.2. Maven仓库

底层Java REST客户端已经托管在 Maven中央仓库 中。最低支持的Java版本为 1.8

底层REST客户端的发布周期与Elasticsearch相同。 客户端版本根据Elasticsearch版本而来,第一个版本是 5.0.0-alpha4。 客户端和它正在进行通讯的Elasticsearch版本之间没有关系。 底层REST客户端与Elasticsearch的所有版本都兼容。

如果您正在寻找SNAPSHOT版本,可以在 Elastic Maven Snapshot仓库 中找到。

Maven 配置

下面是在使用maven作为依赖管理器时的配置,将下面的内容添加到 pom.xml 文件中:

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-client</artifactId>
    <version>{version}</version>
</dependency>
Gradle 配置

如果您使用gradle作为依赖管理器的话,可以将下面的内容添加到 build.gradle 文件中:

dependencies {
    compile 'org.elasticsearch.client:elasticsearch-rest-client:{version}'
}

6.1.3. 依赖

底层Java REST客户端内部使用 Apache Http Async Client 来发送http请求。 它会依赖下面这些包,也就是异步http客户端以及它自身传递过来的依赖项:

  • org.apache.httpcomponents:httpasyncclient

  • org.apache.httpcomponents:httpcore-nio

  • org.apache.httpcomponents:httpclient

  • org.apache.httpcomponents:httpcore

  • commons-codec:commons-codec

  • commons-logging:commons-logging

6.1.4. Shading

为了防止版本冲突,可以对依赖进行shade处理,然后将其打包到单独的jar包中(有时称其为 "uber JAR" 或 "fat JAR")。 对依赖进行shade处理包括获取它的内容(资源文件以及java class文件)然后重命名后将其放入底层Java REST客户端相同的JAR包里。 通过Gradle以及Maven的第三方插件都可以进行shade处理。

需要注意的是,对JAR包进行的shade处理也有影响。比如,对Commons Logging进行shade处理,意味着第三方后台日志也需要进行shade处理。

Maven 配置

这里是一个使用maven时的 Shade 插件配置,将下面的内容添加到 pom.xml 文件中:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-shade-plugin</artifactId>
            <version>3.1.0</version>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals><goal>shade</goal></goals>
                    <configuration>
                        <relocations>
                            <relocation>
                                <pattern>org.apache.http</pattern>
                                <shadedPattern>hidden.org.apache.http</shadedPattern>
                            </relocation>
                            <relocation>
                                <pattern>org.apache.logging</pattern>
                                <shadedPattern>hidden.org.apache.logging</shadedPattern>
                            </relocation>
                            <relocation>
                                <pattern>org.apache.commons.codec</pattern>
                                <shadedPattern>hidden.org.apache.commons.codec</shadedPattern>
                            </relocation>
                            <relocation>
                                <pattern>org.apache.commons.logging</pattern>
                                <shadedPattern>hidden.org.apache.commons.logging</shadedPattern>
                            </relocation>
                        </relocations>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
Gradle 配置

这里是一个使用gradle时的 ShadowJar 插件配置,将下面的内容添加到 build.gradle 文件中:

shadowJar {
    relocate 'org.apache.http', 'hidden.org.apache.http'
    relocate 'org.apache.logging', 'hidden.org.apache.logging'
    relocate 'org.apache.commons.codec', 'hidden.org.apache.commons.codec'
    relocate 'org.apache.commons.logging', 'hidden.org.apache.commons.logging'
}

6.1.5. 初始化

RestClient 实例可以通过其对应的 RestClientBuilder 构建,使用 RestClient#builder(HttpHost...) 静态方法即可。 仅需要与之进行通讯的主机地址作为参数,以 HttpHost 实例来提供,如下所示:

RestClient restClient = RestClient.builder(
    new HttpHost("localhost", 9200, "http"),
    new HttpHost("localhost", 9201, "http")).build();

RestClient 类是县城安全的,理想情况下它的生命周期应该与使用它的应用相同。 在你不再使用的时候,一定要及时关闭,这样才能释放它所占用的系统资源、底层http客户端实例及其线程。

restClient.close();

RestClientBuilder 同时允许在构建 RestClient 实例的时候,配置下列可选参数:

RestClientBuilder builder = RestClient.builder(
    new HttpHost("localhost", 9200, "http"));
Header[] defaultHeaders = new Header[]{new BasicHeader("header", "value")};
builder.setDefaultHeaders(defaultHeaders); (1)
1 针对所有请求设置默认的请求头,避免在每一个请求中手动的设置它们
RestClientBuilder builder = RestClient.builder(
        new HttpHost("localhost", 9200, "http"));
builder.setFailureListener(new RestClient.FailureListener() {
    @Override
    public void onFailure(Node node) {
        (1)
    }
});
1 设置一个监听器,在节点每次出现故障时收到通知,并做出相应的处理。需要启用故障嗅探功能。
RestClientBuilder builder = RestClient.builder(
    new HttpHost("localhost", 9200, "http"));
builder.setNodeSelector(NodeSelector.SKIP_DEDICATED_MASTERS); (1)
1 设置节点选择器来过滤客户端将要发送请求的节点,也包括客户端本身的节点。在嗅探功能开启时,这会有助于避免向主节点发送请求。默认情况下,客户端会向已配置的所有节点发送请求。
RestClientBuilder builder = RestClient.builder(
        new HttpHost("localhost", 9200, "http"));
builder.setRequestConfigCallback(
    new RestClientBuilder.RequestConfigCallback() {
        @Override
        public RequestConfig.Builder customizeRequestConfig(
                RequestConfig.Builder requestConfigBuilder) {
            return requestConfigBuilder.setSocketTimeout(10000); (1)
        }
    });
1 设置针对默认请求配置修改后的回调函数(例如请求超时、身份认证、或者是其它在 org.apache.http.client.config.RequestConfig.Builder 中允许设置的值)
RestClientBuilder builder = RestClient.builder(
    new HttpHost("localhost", 9200, "http"));
builder.setHttpClientConfigCallback(new HttpClientConfigCallback() {
        @Override
        public HttpAsyncClientBuilder customizeHttpClient(
                HttpAsyncClientBuilder httpClientBuilder) {
            return httpClientBuilder.setProxy(
                new HttpHost("proxy", 9000, "http"));  (1)
        }
    });
1 设置针对http客户端配置修改后的回调函数(例如ssl的加密,或者是其它在 org.apache.http.impl.nio.client.HttpAsyncClientBuilder 中允许设置的值)

6.1.6. 执行请求

RestClient 被创建后,可以通过执行 performRequestperformRequestAsync 方法发送请求。 performRequest 是同步的,当请求成功时会阻塞线程并返回 Response,当请求失败的时候会抛出异常。 而 performRequestAsync 是异步的,可以提前传入一个 ResponseListener 参数, 在请求成功后会用 Response 作为参数调用该函数,在失败后会用 Exception 作为参数调用。

这是同步执行的代码:

Request request = new Request(
    "GET",  (1)
    "/");   (2)
Response response = restClient.performRequest(request);
1 HTTP请求类型 (GET, POST, HEAD, 等等)
2 服务器地址

然后这是异步的:

Request request = new Request(
    "GET",  (1)
    "/");   (2)
Cancellable cancellable = restClient.performRequestAsync(request,
    new ResponseListener() {
        @Override
        public void onSuccess(Response response) {
            (3)
        }

        @Override
        public void onFailure(Exception exception) {
            (4)
        }
});
1 HTTP请求类型 (GET, POST, HEAD, 等等)
2 服务器地址
3 响应处理
4 失败处理

你可以像这样向请求对象中添加参数:

request.addParameter("pretty", "true");

你可以像这样将请求参数设置为任意的 HttpEntity

request.setEntity(new NStringEntity(
        "{\"json\":\"text\"}",
        ContentType.APPLICATION_JSON));
HttpEntity 指定 ContentType 很重要,因为在设置请求头的时候会用到它,这样 Elasticsearch 才能正确的进行解析。

你也可以将其设置成 String 类型,因为json的默认 ContentTypeapplication/json

request.setJsonEntity("{\"json\":\"text\"}");
请求的可选配置

RequestOptions 类会将请求的部分内容保存下来,这些内容可以在同一应用下的多个请求之间共享。 可以创建一个单例并在所有请求之间共享它:

private static final RequestOptions COMMON_OPTIONS;
static {
    RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
    builder.addHeader("Authorization", "Bearer " + TOKEN); (1)
    builder.setHttpAsyncResponseConsumerFactory(           (2)
        new HttpAsyncResponseConsumerFactory
            .HeapBufferedResponseConsumerFactory(30 * 1024 * 1024 * 1024));
    COMMON_OPTIONS = builder.build();
}
1 为所有请求统一的添加headers
2 定义响应的消费者

addHeader 用于身份授权所需的headers,还有在Elasticsearch之前设置代理。 这里不用设置 Content-Type,因为客户端会自动从 HttpEntity 中读取。

你可以设置 NodeSelector 来控制哪些节点会接受哪些请求。 推荐使用 NodeSelector.SKIP_DEDICATED_MASTERS

你还可以为异步响应的缓存来自定义消费者。 默认情况下,消费者会对JVM堆上100MB大小响应进行缓存。 如果响应超过了这个限制,则请求就会失败。 当然,如果在你的环境中不允许像上面的示例(30G缓存)一样设置的话,你也可以降低这个最大值。

在创建单例后,你就可以在请求的时候使用它了:

request.setOptions(COMMON_OPTIONS);

你也可以根据每个请求自定义这些选项。 例如,下面就是一个添加header的例子:

RequestOptions.Builder options = COMMON_OPTIONS.toBuilder();
options.addHeader("cats", "knock things off of other things");
request.setOptions(options);
多个异步操作的并行处理

客户端更愿意并行的处理多个操作。 下面的示例就并行的对很多文档进行索引。 在真实项目中,你可以需要使用 _bulk API 替换它,这个例子仅仅是用来展示的。

final CountDownLatch latch = new CountDownLatch(documents.length);
for (int i = 0; i < documents.length; i++) {
    Request request = new Request("PUT", "/posts/doc/" + i);
    //let's assume that the documents are stored in an HttpEntity array
    request.setEntity(documents[i]);
    restClient.performRequestAsync(
            request,
            new ResponseListener() {
                @Override
                public void onSuccess(Response response) {
                    (1)
                    latch.countDown();
                }

                @Override
                public void onFailure(Exception exception) {
                    (2)
                    latch.countDown();
                }
            }
    );
}
latch.await();
1 处理返回的响应
2 处理由于通信异常,或者是响应码异常而返回的错误
取消异步请求

performRequestAsync 方法会返回一个 Cancellable(可以取消)的对象,它会暴露一个名为 cancel 的方法。 调用这个方法可以取消正在进行的请求。 取消请求操作会在底层http客户端中取消http请求。 而在服务器端,这一步操作不会自动地转换为取消操作,也就是说还需要通过API具体手动实现。

Cancellable 的实例是可选项,如果不需要的话,也可以忽略它,这不会影响安全。 一个典型用例就是将它与类似 Rx Java 或者是 Kotlin 中的 suspendCancellableCoRoutine 一起使用。 取消不再需要的请求可以避免 Elasticsearch 进行不必要的负担。

Request request = new Request("GET", "/posts/_search");
Cancellable cancellable = restClient.performRequestAsync(
    request,
    new ResponseListener() {
        @Override
        public void onSuccess(Response response) {
            (1)
        }

        @Override
        public void onFailure(Exception exception) {
            (2)
        }
    }
);
cancellable.cancel();
1 处理返回的响应,防止在请求被取消之前就已经完成
2 处理返回的异常,很大可能是取消请求所引起的 CancellationException

6.1.7. 读取响应

Response 对象是执行同步 performRequest 方法或者是作为 ResponseListener#onSuccess(Response) 的参数所返回的, 其中包含http客户端以及一些额外的信息。

Response response = restClient.performRequest(new Request("GET", "/"));
RequestLine requestLine = response.getRequestLine(); (1)
HttpHost host = response.getHost(); (2)
int statusCode = response.getStatusLine().getStatusCode(); (3)
Header[] headers = response.getHeaders(); (4)
String responseBody = EntityUtils.toString(response.getEntity()); (5)
1 关于已经执行的请求信息
2 响应返回的Host
3 响应状态行,你可以从中查看状态码
4 响应headers,也可以通过 getHeader(String) 方法根据header的名字查看
5 响应体被封装在 org.apache.http.HttpEntity 对象中

在执行请求的过程中,以下情况会抛出异常(或者是在 ResponseListener#onFailure(Exception) 中以参数的形式接受到):

IOException

通信问题 (例如 SocketTimeoutException)

ResponseException

接受到响应信息,但是状态码提示错误(非 2xx)。ResponseException 异常是从有效http响应中得到的,因此其中可以获取返回的 Response 对象,让你读取返回的响应信息。

对于 404 状态码的 HEAD 请求是 不会 抛出 ResponseException 异常的,因为这是一个预期的 HEAD 响应,只不过表示未找到资源。 其它所有HTTP方法(比如 GET)针对 404 状态码的响应都会抛出 ResponseException 异常,除非使用 ignore 参数将 404 排除在外了。 ignore 是一个特殊的客户端参数,它不会发送到 Elasticsearch,并且包含一个以逗号分割的错误状态码列表。 它可以控制是否将某些错误状态码视为预期响应,而不是抛出异常。 例如,有时候一些get api在返回 404 状态码的时候,代表这没有找到该文档, 这时的响应体中不包含错误信息,而是正常返回的get api响应,只不过因为没有找到而没有带上文档。

请注意,底层客户端不会暴露任何对json的构造以及解析工具。 用户可以自由的选择他们喜欢的库来实现。

底层的Apache Async Http Client根据不同的 org.apache.http.HttpEntity 实现而提供不同格式(流、字节数组、字符串等)的请求体。 至于读取响应体,HttpEntity#getContent 方法可以非常方便的从之前返回的缓存响应体中读取 InputStream。 作为替代方案,可以提供一个自定义的 org.apache.http.nio.protocol.HttpAsyncResponseConsumer 用来控制字节的读取和缓冲方式。

6.1.8. 日志

Java REST 客户端使用的日志库和 Apache Async Http Client 使用的相同,都是 Apache Commons Logging,同时它还支持许多流行的日志实现方案。 启用Jar包中包含的日志,其中 org.elasticsearch.client 包对应客户端本身,org.elasticsearch.client.sniffer 包对应嗅探功能。

还可以对请求启用日志追踪功能,以curl格式记录每个请求及其响应。 这在debug的时候很方便,比如执行一个请求后,查看它和之前的响应是否相同。 启用日志追踪 tracer 包来追踪日志记录,并将之打印出来。 注意这种类型的日志对性能有影响,不建议在生产环境中启用,而是仅在需要的时候临时开启。

6.2. 常用配置

初始化 中所述,RestClientBuilder 同时支持提供 RequestConfigCallbackHttpClientConfigCallback,允许 Apache Async Http Client 暴露的任何自定义内容。 这些回调可以在不覆盖 RestClient 初始化配置的前提下,使修改客户端的特定行为成为可能。 本章节表述了一些需要为底层Java Rest客户端进行额外配置的常见场景。

6.2.1. 超时

通过构造器构建 RestClient 的时候,可以提供 RequestConfigCallback 的实例来配置请求的超时时间。 该接口有一个方法接受参数 org.apache.http.client.config.RequestConfig.Builder 作为实例,然后返回相同的类型。 请求配置构造器可以被修改然后返回。 在下面的示例中,我们添加了一个连接超时(默认为1秒)的配置以及一个socket超时(默认30秒)配置。

RestClientBuilder builder = RestClient.builder(
    new HttpHost("localhost", 9200))
    .setRequestConfigCallback(
        new RestClientBuilder.RequestConfigCallback() {
            @Override
            public RequestConfig.Builder customizeRequestConfig(
                    RequestConfig.Builder requestConfigBuilder) {
                return requestConfigBuilder
                    .setConnectTimeout(5000)
                    .setSocketTimeout(60000);
            }
        });

超时时间也可以在发送每个请求的时候配置,这样做会覆盖 RestClient customizeRequestConfig。

RequestConfig requestConfig = RequestConfig.custom()
    .setConnectTimeout(5000)
    .setSocketTimeout(60000)
    .build();
RequestOptions options = RequestOptions.DEFAULT.toBuilder()
    .setRequestConfig(requestConfig)
    .build();

6.2.2. 线程数量

Apache Http Async Client 在启动时默认情况会使用一个调度线程,连接管理器也会使用几个工作线程,这个数量相当于本地监测到的处理器数量 (取决于 Runtime.getRuntime().availableProcessors() 返回的值)。 这个数量可以修改,如下:

RestClientBuilder builder = RestClient.builder(
    new HttpHost("localhost", 9200))
    .setHttpClientConfigCallback(new HttpClientConfigCallback() {
        @Override
        public HttpAsyncClientBuilder customizeHttpClient(
                HttpAsyncClientBuilder httpClientBuilder) {
            return httpClientBuilder.setDefaultIOReactorConfig(
                IOReactorConfig.custom()
                    .setIoThreadCount(1)
                    .build());
        }
    });

6.2.3. 基本身份认证

在通过构造器构建 RestClient 的时候,可以提供一个 HttpClientConfigCallback 来配置基本身份认证。 这个接口有一个方法接受 org.apache.http.impl.nio.client.HttpAsyncClientBuilder 示例作为参数,然后返回相同的类型。 HTTP 客户端构造器可以被修改并返回。 下面的示例中,我们为基本身份认证提供了一个默认的凭证。

final CredentialsProvider credentialsProvider =
    new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY,
    new UsernamePasswordCredentials("user", "test-user-password"));

RestClientBuilder builder = RestClient.builder(
    new HttpHost("localhost", 9200))
    .setHttpClientConfigCallback(new HttpClientConfigCallback() {
        @Override
        public HttpAsyncClientBuilder customizeHttpClient(
                HttpAsyncClientBuilder httpClientBuilder) {
            return httpClientBuilder
                .setDefaultCredentialsProvider(credentialsProvider);
        }
    });

抢占式身份认证可以禁用,禁用后每个请求都将在没有授权header的情况下发送,这样就能查看它是否被接受,在收到 401 HTTP 响应码后, 它将使用基本身份认证header重新发送相同的请求。 如果你想这样执行操作,可以通过 HttpAsyncClientBuilder 来禁用它:

final CredentialsProvider credentialsProvider =
    new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY,
    new UsernamePasswordCredentials("user", "test-user-password"));

RestClientBuilder builder = RestClient.builder(
    new HttpHost("localhost", 9200))
    .setHttpClientConfigCallback(new HttpClientConfigCallback() {
        @Override
        public HttpAsyncClientBuilder customizeHttpClient(
                HttpAsyncClientBuilder httpClientBuilder) {
            httpClientBuilder.disableAuthCaching(); (1)
            return httpClientBuilder
                .setDefaultCredentialsProvider(credentialsProvider);
        }
    });
1 禁用抢占式身份认证

6.2.4. 其它身份认证方法

Elasticsearch 访问令牌服务

如果希望客户端使用Elasticsearch访问令牌来进行身份认证,请设置相关的HTTP请求头。 如果客户端仅为一个用户发送请求,则可以一个必要的 授权 header作为默认展示的请求header,如下所示:

RestClientBuilder builder = RestClient.builder(
    new HttpHost("localhost", 9200, "http"));
Header[] defaultHeaders =
    new Header[]{new BasicHeader("Authorization",
        "Bearer u6iuAxZ0RG1Kcm5jVFI4eU4tZU9aVFEwT2F3")};
builder.setDefaultHeaders(defaultHeaders);
Elasticsearch API 密钥

如果希望客户端使用Elasticsearch API密钥来进行身份认证,请设置相关的HTTP请求头。 如果客户端仅为一个用户发送请求,则可以一个必要的 授权 header作为默认展示的请求header,如下所示:

String apiKeyId = "uqlEyn8B_gQ_jlvwDIvM";
String apiKeySecret = "HxHWk2m4RN-V_qg9cDpuX";
String apiKeyAuth =
    Base64.getEncoder().encodeToString(
        (apiKeyId + ":" + apiKeySecret)
            .getBytes(StandardCharsets.UTF_8));
RestClientBuilder builder = RestClient.builder(
    new HttpHost("localhost", 9200, "http"));
Header[] defaultHeaders =
    new Header[]{new BasicHeader("Authorization",
        "ApiKey " + apiKeyAuth)};
builder.setDefaultHeaders(defaultHeaders);

6.2.5. 加密通讯

您也可以通过配置 HttpClientConfigCallback 使用TLS加密通讯。 org.apache.http.impl.nio.client.HttpAsyncClientBuilder 会作为参数接受并暴露一些配置加密通讯的方法: setSSLContext, setSSLSessionStrategysetConnectionManager, 根据顺序越来越重要。

当在HTTP层的基础上为Elasticsearch集群安装TLS时,客户端需要信任Elasticsearch正在使用的证书。 下面的例子展示了,当PKCS#12密钥库中的CA证书可用时,将客户端设置为信任Elasticsearch正在使用的CA证书:

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);
final SSLContext sslContext = sslBuilder.build();
RestClientBuilder builder = RestClient.builder(
    new HttpHost("localhost", 9200, "https"))
    .setHttpClientConfigCallback(new HttpClientConfigCallback() {
        @Override
        public HttpAsyncClientBuilder customizeHttpClient(
                HttpAsyncClientBuilder httpClientBuilder) {
            return httpClientBuilder.setSSLContext(sslContext);
        }
    });

下面的例子展示了,当PEM编码文件可用时,将客户端设置为信任Elasticsearch正在使用的CA证书:

Path caCertificatePath = Paths.get("/path/to/ca.crt");
CertificateFactory factory =
    CertificateFactory.getInstance("X.509");
Certificate trustedCa;
try (InputStream is = Files.newInputStream(caCertificatePath)) {
    trustedCa = factory.generateCertificate(is);
}
KeyStore trustStore = KeyStore.getInstance("pkcs12");
trustStore.load(null, null);
trustStore.setCertificateEntry("ca", trustedCa);
SSLContextBuilder sslContextBuilder = SSLContexts.custom()
    .loadTrustMaterial(trustStore, null);
final SSLContext sslContext = sslContextBuilder.build();
RestClient.builder(
    new HttpHost("localhost", 9200, "https"))
    .setHttpClientConfigCallback(new HttpClientConfigCallback() {
        @Override
        public HttpAsyncClientBuilder customizeHttpClient(
            HttpAsyncClientBuilder httpClientBuilder) {
            return httpClientBuilder.setSSLContext(sslContext);
        }
    });

当Elasticsearch被配置为需要客户端TLS认证,例如当配置了PKI realm的时候,这时客户端需要在TLS握手期间提供证书来进行身份认证。 下面的例子展示了,使用存储在PKCS#12密钥库中的证书和私钥为客户端配置TLS身份认证:

Path trustStorePath = Paths.get("/path/to/your/truststore.p12");
Path keyStorePath = Paths.get("/path/to/your/keystore.p12");
KeyStore trustStore = KeyStore.getInstance("pkcs12");
KeyStore keyStore = KeyStore.getInstance("pkcs12");
try (InputStream is = Files.newInputStream(trustStorePath)) {
    trustStore.load(is, trustStorePass.toCharArray());
}
try (InputStream is = Files.newInputStream(keyStorePath)) {
    keyStore.load(is, keyStorePass.toCharArray());
}
SSLContextBuilder sslBuilder = SSLContexts.custom()
    .loadTrustMaterial(trustStore, null)
    .loadKeyMaterial(keyStore, keyStorePass.toCharArray());
final SSLContext sslContext = sslBuilder.build();
RestClientBuilder builder = RestClient.builder(
    new HttpHost("localhost", 9200, "https"))
    .setHttpClientConfigCallback(new HttpClientConfigCallback() {
        @Override
        public HttpAsyncClientBuilder customizeHttpClient(
            HttpAsyncClientBuilder httpClientBuilder) {
            return httpClientBuilder.setSSLContext(sslContext);
        }
    });

如果客户端证书和密钥在密钥库中不可用,而是使用PEM编码的文件,这时候就不能直接使用它们来构建 SSLContext。 你必须使用外部库来将PEM密钥解析为私有密钥实例。 或者,你也可以使用外部工具用PEM文件构建密钥库,如下面的例子:

openssl pkcs12 -export -in client.crt -inkey private_key.pem \
        -name "client" -out client.p12

如果没有提供明确的配置,则会使用 系统默认配置

6.2.6. 其它

如果您需要其它的配置,可以参考Apache HttpAsyncClient的文档: https://hc.apache.org/httpcomponents-asyncclient-4.1.x/

如果你的应用运行在security manager下,则可能会收到JVM默认策略的约束, 即针对解析成功的主机名进行无限期的缓存,针对解析失败的则缓存10秒。 如果要将客户端连接到主机的的地址随时间的变化而变化,那么可能需要修改JVM的默认行为。 如果需要对其进行修改的话,可以通过添加 networkaddress.cache.ttl=<timeout> 以及 networkaddress.cache.negative.ttl=<timeout> 选项到你的 Java security policy 中。

6.2.7. 节点选择器

客户端以循环的方式,将每个请求发送到已配置的节点中的一个上面。 在初始化客户端时,可以提供节点选择器来对节点进行筛选。 在启用嗅探功能时,这一点非常有用,可以防止HTTP请求只命中专用主节点 对每个请求,客户端都会运行最后一个配置的节点选择器对候选节点进行筛选,然后从剩余的节点中选择下一个。

RestClientBuilder builder = RestClient.builder(
        new HttpHost("localhost", 9200, "http"));
builder.setNodeSelector(new NodeSelector() { (1)
    @Override
    public void select(Iterable<Node> nodes) {
        /*
         * Prefer any node that belongs to rack_one. If none is around
         * we will go to another rack till it's time to try and revive
         * some of the nodes that belong to rack_one.
         */
        boolean foundOne = false;
        for (Node node : nodes) {
            String rackId = node.getAttributes().get("rack_id").get(0);
            if ("rack_one".equals(rackId)) {
                foundOne = true;
                break;
            }
        }
        if (foundOne) {
            Iterator<Node> nodesIt = nodes.iterator();
            while (nodesIt.hasNext()) {
                Node node = nodesIt.next();
                String rackId = node.getAttributes().get("rack_id").get(0);
                if ("rack_one".equals(rackId) == false) {
                    nodesIt.remove();
                }
            }
        }
    }
});
1 Set an allocation aware node selector that allows to pick a node in the local rack if any available, otherwise go to any other node in any rack. It acts as a preference rather than a strict requirement, given that it goes to another rack if none of the local nodes are available, rather than returning no nodes in such case which would make the client forcibly revive a local node whenever none of the nodes from the preferred rack is available.
Node selectors that do not consistently select the same set of nodes will make round-robin behaviour unpredictable and possibly unfair. The preference example above is fine as it reasons about availability of nodes which already affects the predictability of round-robin. Node selection should not depend on other external factors or round-robin will not work properly.

6.3. 嗅探

嗅探功能就是一个从运行中的Elasticsearch集群中自动发现节点,并将其设置为现有的 RestClient 实例的最小化库。 默认情况下,它使用节点信息api来查询集群的节点,然后使用jackson来解析其所获得的json响应。

与Elasticsearch 2.x及以上版本兼容。

6.3.1. Javadoc

有关REST客户端嗅探功能的javadoc可以在 {rest-client-sniffer-javadoc}/index.html 找到。

6.3.2. Maven 仓库

REST客户端嗅探功能的发布周期与Elasticsearch相同。 可以使用Elasticsearch的版本号替换所需的嗅探功能版本号,嗅探功能第一个发布的版本是 5.0.0-alpha4。 嗅探功能的版本和与客户端通信的Elasticsearch版本之前没有关系。 嗅探功能支持从Elasticsearch 2.x及以上的版本中获取节点列表。

如果你想找SNAPSHOT版本,可以在Elastic Maven Snapshot仓库 https://snapshots.elastic.co/maven/ 中找到。

Maven 配置

以下内容展示了如何在使用maven作为依赖包管理器的时候配置依赖项。将下面的内容添加到 pom.xml 文件中:

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-client-sniffer</artifactId>
    <version>{version}</version>
</dependency>
Gradle 配置

以下内容展示了如何在使用gradle作为依赖包管理器的时候配置依赖项。将下面的内容添加到 build.gradle 文件中:

dependencies {
    compile 'org.elasticsearch.client:elasticsearch-rest-client-sniffer:{version}'
}

6.3.3. 使用方法

初始化 所示,在 RestClient 实例被创建后,就可以将 嗅探 功能与之相关联了。 嗅探 功能将会利用 RestClient 定期(默认情况下每5分钟)从集群中获取的当前节点列表, 然后调用 RestClient#setNodes 方法对其进行更新。

RestClient restClient = RestClient.builder(
    new HttpHost("localhost", 9200, "http"))
    .build();
Sniffer sniffer = Sniffer.builder(restClient).build();

在不使用 嗅探 功能的时候,将其关闭很重要,这样它的后台线程才可以正常关闭,其占用的所有资源才能被释放。 嗅探 对象的生命周期与 RestClient 相同,并在客户端关闭之前关闭:

sniffer.close();
restClient.close();

默认情况下,嗅探 功能每5分钟更新一次节点。 在此期间可以通过下面的方式对其(以毫秒为单位)进行自定义:

RestClient restClient = RestClient.builder(
    new HttpHost("localhost", 9200, "http"))
    .build();
Sniffer sniffer = Sniffer.builder(restClient)
    .setSniffIntervalMillis(60000).build();

也可以启动故障嗅探模式,在每次发生故障后,节点会立刻更新,而不是在接下来的常规嗅探中进行更新。 这种情况下,需要先创建 SniffOnFailureListener,并在 RestClient 创建时提供。 此外,在 Sniffer 实例被创建后,它需要与 SniffOnFailureListener 实例相关联,该实例在每次失败后收到通知, 并使用 嗅探 功能执行一次如前面所描述的额外嗅探。

SniffOnFailureListener sniffOnFailureListener =
    new SniffOnFailureListener();
RestClient restClient = RestClient.builder(
    new HttpHost("localhost", 9200))
    .setFailureListener(sniffOnFailureListener) (1)
    .build();
Sniffer sniffer = Sniffer.builder(restClient)
    .setSniffAfterFailureDelayMillis(30000) (2)
    .build();
sniffOnFailureListener.setSniffer(sniffer); (3)
1 RestClient 实例配置故障监听
2 当嗅探功能出现故障时,不仅节点会立刻更新,而且下一次嗅探也会比正常情况下提前进行, 默认情况下实在故障发生1分钟之后,如果情况恢复正常,我们希望尽快监测到。 这个间隔可以在创建 Sniffer 的时候通过 setSniffAfterFailureDelayMillis 方法进行自定义。 注意如果没有启用故障嗅探功能,那么这个配置参数将不起作用。
3 为故障监听器配置 Sniffer 实例

Elasticsearch节点信息api在连接到节点时不返回要使用的协议,而是只返回它们的 host:port 键值对,因此默认情况下使用 http 协议。 如果想使用 https 协议,则必须手动创建 ElasticsearchNodesSniffer 实例,并按下面的方式提供:

RestClient restClient = RestClient.builder(
        new HttpHost("localhost", 9200, "http"))
        .build();
NodesSniffer nodesSniffer = new ElasticsearchNodesSniffer(
        restClient,
        ElasticsearchNodesSniffer.DEFAULT_SNIFF_REQUEST_TIMEOUT,
        ElasticsearchNodesSniffer.Scheme.HTTPS);
Sniffer sniffer = Sniffer.builder(restClient)
        .setNodesSniffer(nodesSniffer).build();

同样的,也可以自定义 sniffRequestTimeout ,默认为1秒。 timeout 可以在调用节点信息api的时候,作为查询字符串参数来提供,因此在超时参数在服务器端过期时, 仍然会返回有效的响应信息,尽管它可能只包含属于集群的一部分节点的子集,即再次之前已经响应的节点。

RestClient restClient = RestClient.builder(
    new HttpHost("localhost", 9200, "http"))
    .build();
NodesSniffer nodesSniffer = new ElasticsearchNodesSniffer(
    restClient,
    TimeUnit.SECONDS.toMillis(5),
    ElasticsearchNodesSniffer.Scheme.HTTP);
Sniffer sniffer = Sniffer.builder(restClient)
    .setNodesSniffer(nodesSniffer).build();

此外,还可以为可能需要从外部源而非Elasticsearch获取的节点特殊用例,提供一个自定义的 节点嗅探 实现:

RestClient restClient = RestClient.builder(
    new HttpHost("localhost", 9200, "http"))
    .build();
NodesSniffer nodesSniffer = new NodesSniffer() {
        @Override
        public List<Node> sniff() throws IOException {
            return null; (1)
        }
    };
Sniffer sniffer = Sniffer.builder(restClient)
    .setNodesSniffer(nodesSniffer).build();
1 从外部源获取 hosts

6.4. 许可证

Copyright 2013-2019 Elasticsearch

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

7. 其它

本文旨在帮助中文用户学习ElasticSearch,商业用途请提前联系作者,其余用途你随便整。

以下内容和ElasticSearch官方无关,爱看不看2333

7.2. 对比关系型数据库

名词
  • index: 索引(名词),类似于关系型数据库(例如MySQL/SQLServer等)中的数据库(database)

  • type: 类型,类似于关系型数据库中的表格(table)

  • document: 文档,类似于关系型数据库中的一条数据

  • id: 唯一标示,类似于关系型数据库中的主键

  • bulk:批处理接口的关键字,在实际项目中你可能会经常用到它,类似于关系型数据库中的 insert into table values(x),(x),(x) 这种批处理写法。当然ES还支持批量删除和修改

动词
  • index: 索引(动词),类似于关系型数据库中的insert

  • get: 查询,类似于关系型数据库中的select

  • delete: 删除,类似于关系型数据库中的delete

  • update: 更新,类似于关系型数据库中的update

注意,名词解释只是方便理解,实现原理以及使用方法和关系型数据库有很大的区别。

7.3. 翻译源码地址

欢迎小伙伴贡献和指点

7.4. 更新日志

2022-02-24

7.16.0版本初稿完成, 在线地址。 接下来的计划是8.x版本。

2021-12-14

7.16.0版本正在施工, 在线地址。 之前的7.12版本由于没时间搞,所以GG了,这次希望能坚持下来。

2021-04-07

7.12.0版本正在施工, 在线地址。 欢迎小伙伴们参与贡献

2019-05-28

6.8.0版本初稿完成, 在线地址

2019-05-22

6.8.0版本正在施工, 在线地址。 7.x在计划中,欢迎小伙伴们参与贡献