ElasticSearch 应用开发(七)Java Low Level Rest Client——Java API


 1.概述

Java rest client分为两种:Java Low Level Rest Client (本节介绍重点)、Java High Level Rest Client(下节介绍)。

Java Low Level Rest Client

Elasticsearch 官方低级客户端: 通过 http协议 与Elasticsearch服务进行通信。请求编码和响应解码保留给用户实现。与所有 Elasticsearch 版本兼容。

功能

  • 最小依赖
  • 负载均衡
  • 故障转移
  • 故障连接策略 (是否重新连接故障节点取决于连续失败多少次;失败次数越多,在再次尝试同一个节点之前,客户端等待的时间越长)
  • 持久化连接
  • 跟踪记录请求和响应
  • 自动发现集群节点

2.Java Low Level Rest Client 

Java low level Rest 客户端兼容所有 Elasticsearch 版本,客户端和集群的版本,并不强烈要求一致,但是根据经验还是当版本一致时,出现问题能够快速定位。

2.1 起步

2.1.1Javadoc

https://artifacts.elastic.co/javadoc/org/elasticsearch/client/elasticsearch-rest-client/6.6.0/index.html

2.1.2Maven Repository

Maven 托管在 Maven Central

Java 版本最低要求 1.7

low-level REST 客户端与 elasticsearch 的发布周期相同。可以使用版本替换,但必须是 5.0.0-alpha4 之后的版本。客户端版本与 Elasticsearch 服务版本之间没有关联。low-level REST 客户端兼容所有 Elasticsearch 版本。

Maven 配置

使用 Maven 作依赖管理,将下列内容添加到你的 pom.xml 文件里:

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-client</artifactId>
    <version>5.6.4</version>
</dependency>

2.1.3 依赖

低级 Java REST 客户端内部使用 Apache Http Async Client 发送 http 请求。它依赖以下工具,即Apache Http Async Client依赖:

  • 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

2.1.4 Shading

如果,版本没有冲突,此项不用考虑

2.1.5初始化

RestClient 实例可以通过相应的 RestClientBuilder 类来构建,通过静态方法 RestClient#builder(HttpHost...) 创建。唯一必需的参数是服务的host和端口(默认9200,切记不要使用9300),以 HttpHost 实例的方式提供给建造者。参考之前的内容:

ElasticSearch 应用开发(二)Java Client 连接ElasticSearch集群

2.1.6 发送请求

一旦创建了 RestClient,就可以通过调用其中一个performRequestperformRequestAsync方法来发送请求。 

(1)同步请求 

performRequest 方法是同步的,并直接返回Response,这意味着客户端将阻塞并等待返回的响应。简单示例:

请求中加入参数:

(a)
Map<String, String> params = Collections.singletonMap("pretty", "true");
 //发送一个带参数的请求
Response response = restClient.performRequest("GET", "/", params); 
(b)
Map<String, String> params = Collections.emptyMap();
String jsonString = "{" +
            "\"user\":\"kimchy\"," +
            "\"postDate\":\"2013-01-30\"," +
            "\"message\":\"trying out Elasticsearch\"" +
        "}";
// org.apache.http.HttpEntity  为了让Elasticsearch 能够解析,需要设置ContentType。
HttpEntity entity = new NStringEntity(jsonString, ContentType.APPLICATION_JSON);
Response response = restClient.performRequest("PUT", "/posts/doc/1", params, entity);
(c)
Map<String, String> params = Collections.emptyMap();
HttpAsyncResponseConsumerFactory.HeapBufferedResponseConsumerFactory consumerFactory =
        new HttpAsyncResponseConsumerFactory.HeapBufferedResponseConsumerFactory(30 * 1024 * 1024); 
Response response = restClient.performRequest("GET", "/posts/_search", params, null, consumerFactory);

(2)异步请求

performRequestAsync 返回 void,并接受一个额外的ResponseListener作为参数,这意味着它们是异步执行的。 提供的监听器将在请求完成或失败时通知。简单示例:

发送带参数的异步请求

(a)
Map<String, String> params = Collections.singletonMap("pretty", "true");
 // 发送带参数的异步请求
restClient.performRequestAsync("GET", "/", params, responseListener);
(b)
String jsonString = "{" +
        "\"user\":\"kimchy\"," +
        "\"postDate\":\"2013-01-30\"," +
        "\"message\":\"trying out Elasticsearch\"" +
        "}";
HttpEntity entity = new NStringEntity(jsonString, ContentType.APPLICATION_JSON);
restClient.performRequestAsync("PUT", "/posts/doc/1", params, entity, responseListener);
(c)
HttpAsyncResponseConsumerFactory.HeapBufferedResponseConsumerFactory consumerFactory =
        new HttpAsyncResponseConsumerFactory.HeapBufferedResponseConsumerFactory(30 * 1024 * 1024);
restClient.performRequestAsync("GET", "/posts/_search", params, null, consumerFactory, responseListener);

(3)RequestOptions

在同一个应用中的多个request请求,RequestOptions类持有其中每一个request的多个部分,因此可以使用代理模式共享全部的requests

private static final RequestOptions COMMON_OPTIONS;
static {
    RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
    builder.addHeader("Authorization", "Bearer " + TOKEN); 
    builder.setHttpAsyncResponseConsumerFactory(           
        new HttpAsyncResponseConsumerFactory
            .HeapBufferedResponseConsumerFactory(30 * 1024 * 1024 * 1024));
    COMMON_OPTIONS = builder.build();
}

Add any headers needed by all requests.

Customize the response consumer

addHeader是在ElasticSearch集群前,设置代理授权使用的。Coneten-Type header的默认值HttpEntity 

能够通过设置NodeSelector 决定哪个节点接收请求,NodeSelector.NOT_MASTER_ONLY是一个好选择

能够自定义异步Response响应所占用buffer的空间大小,默认值是100M java Heap.如果大于该值,将会响应失败。当然,heap收到限制的时候,可以降低该值。

Once you’ve created the singleton you can use it when making requests:

request.setOptions(COMMON_OPTIONS);

在每一个request基础上,可以自定义options。例如,增加额外的 header:

RequestOptions.Builder options = COMMON_OPTIONS.toBuilder();
options.addHeader("cats", "knock things off of other things");
request.setOptions(options);

(4)并行异步执行

client善于处理并行处理多个行为,开发人员更喜欢使用 _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) {
                    
                    latch.countDown();
                }

                @Override
                public void onFailure(Exception exception) {
                    
                    latch.countDown();
                }
            }
    );
}
latch.await();

Process the returned response

Handle the returned exception, due to communication error or a response with status code that indicates an error

2.1.7读取响应

返回Response对象(performRequest,方法返回,ResponseListener#onSuccess(Response)接收)

Response response = restClient.performRequest("GET", "/");
//请求信息
RequestLine requestLine = response.getRequestLine();
//返回response host信息
HttpHost host = response.getHost();
//返回状态行,获取状态码
int statusCode = response.getStatusLine().getStatusCode();
//response headers ,也可以通过名字获取 `getHeader(String)`
Header[] headers = response.getHeaders();
//response  org.apache.http.HttpEntity 对象
String responseBody = EntityUtils.toString(response.getEntity()); 

请求时可能抛出一下异常(或者 ResponseListener#onFailure(Exception) 参数接收错误信息)

  • IOException

通信问题(例如:SocketTimeoutException

  • ResponseException

返回了一个 response,但是它的状态码显示了一个错误(不是2xx)。ResponseException 说明连接是通的。

对于返回404状态码的请求,不会抛出 ResponseException,因为这是一个预期的响应,只是表示找不到该资源。 除非ignore参数包含404,否则所有其他HTTP方法(例如GET)都会为404响应抛出 ResponseExceptionignore是一个特殊的客户端参数,不会发送到Elasticsearch,并且包含以逗号分隔的错误状态码列表。 它允许控制某些错误状态代码是否应该被视为预期的响应,而不是一个例外。 这对get api来说很有用,因为它可以在缺少文档时返回404,在这种情况下,响应主体不会包含错误,而是通常的get api响应,而不是文档,因为它没有找到。

注意: 低级别的客户端不公开任何 helper jsonmarshalling和un-marshalling。 用户可以自由使用他们喜欢的库。

底层的Apache异步Http客户端附带不同的org.apache.http.HttpEntity实现,可以使用不同的格式(流,字节数组,字符串等)提供请求主体。 至于读取响应主体,HttpEntity#getContent方法很方便,它返回来自先前缓冲的响应主体InputStream。 可以提供一个自定义的org.apache.http.nio.protocol.HttpAsyncResponseConsumer来控制如何读取和缓冲字节,作为替代。

 2.1.7 log

Java REST 客户端使用和Apache Async Http客户端使用的相同日志记录库:Apache Commons Logging,它支持许多流行的日志记录实现。 用于启用日志记录的java包是客户端本身的org.elasticsearch.client,嗅探器是org.elasticsearch.client.sniffer

请求 tracer 日志记录可以开启以curl格式记录。 这在调试时非常方便,例如在需要手动执行请求的情况下,检查是否仍然产生相同的响应。 请注意,这种类型的日志记录非常消耗资源,不应一直在生产环境中启用,而只是在需要时暂时使用。

2.2 通用配置

正如初始化中所解释的那样,RestClientBuilder支持同时提供RequestConfigCallbackHttpClientConfigCallback,它们允许任何定制的Apache Async Http Client。 这些回调可以修改客户端的某些特定行为,而不会覆盖RestClient初始化的其他任何默认配置。 本节介绍了一些需要对低级Java REST客户端进行额外配置的常见方案。

2.2.1 超时 Timeout

配置请求超时可以通过构建器构建RestClient时提供RequestConfigCallback实例来完成。 该接口有一个方法接收org.apache.http.client.config.RequestConfig.Builder的一个实例作为参数,并具有相同的返回类型。 请求配置生成器可以修改,然后返回。 在下面的例子中,我们增加了连接超时(默认为1秒)和socket超时(默认为30秒)。 也调整最大重试超时时间(默认为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);
            }
        })
        .setMaxRetryTimeoutMillis(60000);

2.2.2 线程数

Apache Http Async Client 默认启动一个调度线程,连接管理器使用多个worker线程,线程的数量和CPU核数量相同(等于 Runtime.getRuntime().availableProcessors()返回的数量),线程数可以修改如下:

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

2.2.3 基本认证

构建RestClient时配置HttpClientConfigCallback来配置基本认证。 该接口有一个方法接收org.apache.http.impl.nio.client.HttpAsyncClientBuilder的一个实例作为参数,并具有相同的返回类型。 httpClientBuilder 被修改,然后返回。 在以下示例中,设置了基本身份验证。

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

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

Preemptive 身份验证可以禁用,这意味着每个发送出去的请求没有授权头,当收到HTTP 401响应时,将重新发送与基本身份验证头完全相同的请求。 可以通过HttpAsyncClientBuilder来禁用:

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

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

2.2.4 加密传输

可以通过HttpClientConfigCallback配置加密传输。 参数org.apache.http.impl.nio.client.HttpAsyncClientBuilder 公开了多个方法来配置加密传输:setSSLContextsetSSLSessionStrategysetConnectionManager,以下是一个例子:


KeyStore truststore = KeyStore.getInstance("jks");
try (InputStream is = Files.newInputStream(keyStorePath)) {
    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 RestClientBuilder.HttpClientConfigCallback() {
            @Override
            public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) {
                return httpClientBuilder.setSSLContext(sslContext);
            }
        });

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

2.2.5Node selector

The client sends each request to one of the configured nodes in round-robin fashion. Nodes can optionally be filtered through a node selector that needs to be provided when initializing the client. This is useful when sniffing is enabled, in case only dedicated master nodes should be hit by HTTP requests. For each request the client will run the eventually configured node selector to filter the node candidates, then select the next one in the list out of the remaining ones.

客户机以循环方式将每个请求发送到配置的节点之一。在节点初始化的时候可以选择通过node selector选择器过滤哪些节点。启用嗅探时很有用,以防HTTP请求只应命中专用的主节点。对于每个请求,客户机将运行最终配置的节点选择器来筛选节点候选,然后从剩余的节点中选择列表中的下一个。

RestClientBuilder builder = RestClient.builder(
        new HttpHost("localhost", 9200, "http"));
builder.setNodeSelector(new NodeSelector() { 
    @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();
                }
            }
        }
    }
});

设置一个分配感知节点选择器,允许在本地机架中选择一个节点(如果有),否则转到任何机架中的任何其他节点。它作为首选项而不是严格的要求,因为如果没有任何本地节点可用,它将转到另一个机架,而不是返回任何节点,在这种情况下,当首选机架中没有任何节点可用时,客户端将强制恢复本地节点

Warning

Node selectors 并不会一直选择同一个节点,否则将使循环行为不可预测,并且可能不公平。上面的首选项示例很好解释了节点可用性,将如何影响了循环的可预测性。节点选择不应依赖于其他外部因素,否则循环将无法正常工作。

2.3 嗅探器

2.3.1Javadoc

REST 客户端嗅探器的 javadoc https://artifacts.elastic.co/javadoc/org/elasticsearch/client/elasticsearch-rest-client-sniffer/6.6.2/index.html

2.3.2

REST 客户端嗅探器与 elasticsearch 的发行周期相同。可以使用期望的嗅探器版本替换,但必须是 5.0.0-alpha4 之后的版本。嗅探器版本与其通信的 Elasticsearch 版本之间没有关联。嗅探器支持从 elasticsearch 2.x 及以上的版本上获取节点列表。

Maven 配置

若使用 Maven 作依赖管理,你可以这样配置依赖。将下列内容添加到你的 pom.xml 文件里:


<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-client-sniffer</artifactId>
    <version>5.6.4</version>
</dependency>

2.3.3用法

一旦创建了RestClient实例,如 初始化中所示,可以将嗅探器与之相关联。 Sniffer 将使用关联的 RestClient 定期(默认为每5分钟)从集群中获取当前可用的节点列表,并通过调用 RestClient#setHosts 来更新它们。

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

关闭 Sniffer 非常重要,如此嗅探器后台线程才能正取关闭并释放他持有的资源。 Sniffer 对象应该与 RestClient 具有相同的生命周期,并在客户端之前关闭:

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

Sniffer 默认每5分钟更新一次节点列表。这个周期可以如下方式通过提供一个参数(毫秒数)自定义设置:

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

也可以在发生故障是启用嗅探,这意味着每次故障后将直接获取并更新节点列表,而不是等到下一次正常的更新周期。此种情况时, SniffOnFailureListener 需要首先被创建,并将实例在 RestClient 创建时提供给它。 同样的,在之后创建 Sniffer 时,他需要被关联到同一个 SniffOnFailureListener 实例上,这个实例将在每个故障发生后被通知到,然后调用 Sniffer 去执行额外的嗅探行为。

SniffOnFailureListener sniffOnFailureListener = new SniffOnFailureListener();
RestClient restClient = RestClient.builder(new HttpHost("localhost", 9200))
        .setFailureListener(sniffOnFailureListener) //为 RestClient 实例设置故障监听器
        .build();
Sniffer sniffer = Sniffer.builder(restClient
        .setSniffAfterFailureDelayMillis(30000) /* 故障后嗅探,不仅意味着每次故障后会更新节点,也会添加普通计划外的嗅探行为,默认情况是故障之后1分钟后,假设节点将恢复正常,那么我们希望尽可能快的获知。如上所述,周期可以通过 `setSniffAfterFailureDelayMillis` 方法在创建 Sniffer 实例时进行自定义设置。 需要注意的是,当没有启用故障监听时,这最后一个配置参数不会生效 */
        .build();
sniffOnFailureListener.setSniffer(sniffer); // 将 嗅探器关联到嗅探故障监听器上

Elasticsearch Nodes Info api 不会返回连接节点使用的协议,而只有他们的 host:port键值对,因此默认使用 http。如果需要使用 https ,必须手动创建和提供 ElasticsearchHostsSniffer 实例,如下所示:

RestClient restClient = RestClient.builder(
        new HttpHost("localhost", 9200, "http"))
        .build();
HostsSniffer hostsSniffer = new ElasticsearchHostsSniffer(
        restClient,
        ElasticsearchHostsSniffer.DEFAULT_SNIFF_REQUEST_TIMEOUT,
        ElasticsearchHostsSniffer.Scheme.HTTPS);
Sniffer sniffer = Sniffer.builder(restClient)
        .setHostsSniffer(hostsSniffer).build();

使用同样的方式,可以自定义设置 sniffRequestTimeout参数,该参数默认值为 1 秒。这是一个调用 Nodes Info api 时作为 querystring 参数的超时参数,这样当服务端超市时,仍然会返回一个有效响应,虽然它可能仅包含属于集群的一部分节点,其他节点会在随后响应。

RestClient restClient = RestClient.builder(
        new HttpHost("localhost", 9200, "http"))
        .build();
HostsSniffer hostsSniffer = new ElasticsearchHostsSniffer(
        restClient,
        TimeUnit.SECONDS.toMillis(5),
        ElasticsearchHostsSniffer.Scheme.HTTP);
Sniffer sniffer = Sniffer.builder(restClient)
        .setHostsSniffer(hostsSniffer).build();

同样的,一个自定义的 HostsSniffer 实现可以提供一个高级用法功能,比如可以从 Elasticsearch 之外的来源获取主机:

RestClient restClient = RestClient.builder(
        new HttpHost("localhost", 9200, "http"))
        .build();
HostsSniffer hostsSniffer = new HostsSniffer() {
    @Override
    public List<HttpHost> sniffHosts() throws IOException {
        return null; // 从外部源获取主机
    }
};
Sniffer sniffer = Sniffer.builder(restClient)
        .setHostsSniffer(hostsSniffer).build();

3.总结

Java Low Level Rest Client是基于Http请求和ElasticSearch集群进行交互,兼容所有Elasticsearch版本,可以通过Http发送请求和接收返回结果,对应的API如下图:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值