Java rest client分为两种:Java Low Level Rest Client (本节介绍重点)、Java High Level Rest Client(下节介绍)。
Elasticsearch 官方低级客户端: 通过 http
协议 与Elasticsearch服务进行通信。请求编码和响应解码保留给用户实现。与所有 Elasticsearch 版本兼容。
功能
Java low level
Rest 客户端兼容所有 Elasticsearch 版本,客户端和集群的版本,并不强烈要求一致,但是根据经验还是当版本一致时,出现问题能够快速定位。
https://artifacts.elastic.co/javadoc/org/elasticsearch/client/elasticsearch-rest-client/6.6.0/index.html
Maven 托管在 Maven Central
Java 版本最低要求 1.7
low-level
REST 客户端与elasticsearch
的发布周期相同。可以使用版本替换,但必须是 5.0.0-alpha4 之后的版本。客户端版本与Elasticsearch
服务版本之间没有关联。low-level
REST 客户端兼容所有 Elasticsearch 版本。
Maven 配置
使用 Maven 作依赖管理,将下列内容添加到你的 pom.xml 文件里:
org.elasticsearch.client
elasticsearch-rest-client
5.6.4
低级 Java REST
客户端内部使用 Apache Http Async Client
发送 http
请求。它依赖以下工具,即Apache Http Async Client
依赖:
如果,版本没有冲突,此项不用考虑
RestClient
实例可以通过相应的 RestClientBuilder
类来构建,通过静态方法 RestClient#builder(HttpHost...)
创建。唯一必需的参数是服务的host和端口(默认9200,切记不要使用9300),以 HttpHost
实例的方式提供给建造者。参考之前的内容:
ElasticSearch 应用开发(二)Java Client 连接ElasticSearch集群
一旦创建了 RestClient
,就可以通过调用其中一个performRequest
或performRequestAsync
方法来发送请求。
(1)同步请求
performRequest
方法是同步的,并直接返回Response
,这意味着客户端将阻塞并等待返回的响应。简单示例:
请求中加入参数:
(a)
Map params = Collections.singletonMap("pretty", "true");
//发送一个带参数的请求
Response response = restClient.performRequest("GET", "/", params);
(b)
Map 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 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 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 |
返回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响应抛出ResponseException
。ignore
是一个特殊的客户端参数,不会发送到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
来控制如何读取和缓冲字节,作为替代。
Java REST 客户端使用和Apache Async Http
客户端使用的相同日志记录库:Apache Commons Logging
,它支持许多流行的日志记录实现。 用于启用日志记录的java包是客户端本身的org.elasticsearch.client
,嗅探器是org.elasticsearch.client.sniffer
。
请求 tracer
日志记录可以开启以curl格式记录。 这在调试时非常方便,例如在需要手动执行请求的情况下,检查是否仍然产生相同的响应。 请注意,这种类型的日志记录非常消耗资源,不应一直在生产环境中启用,而只是在需要时暂时使用。
正如初始化中所解释的那样,RestClientBuilder
支持同时提供RequestConfigCallback
和HttpClientConfigCallback
,它们允许任何定制的Apache Async Http Client
。 这些回调可以修改客户端的某些特定行为,而不会覆盖RestClient
初始化的其他任何默认配置。 本节介绍了一些需要对低级Java REST客户端进行额外配置的常见方案。
配置请求超时可以通过构建器构建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);
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());
}
});
构建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);
}
});
可以通过HttpClientConfigCallback
配置加密传输。 参数org.apache.http.impl.nio.client.HttpAsyncClientBuilder
公开了多个方法来配置加密传输:setSSLContext
,setSSLSessionStrategy
和setConnectionManager
,以下是一个例子:
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);
}
});
如果没有提供明确的配置,则使用系统默认配置。
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 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 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();
}
}
}
}
});
设置一个分配感知节点选择器,允许在本地机架中选择一个节点(如果有),否则转到任何机架中的任何其他节点。它作为首选项而不是严格的要求,因为如果没有任何本地节点可用,它将转到另一个机架,而不是返回任何节点,在这种情况下,当首选机架中没有任何节点可用时,客户端将强制恢复本地节点 |
Node selectors 并不会一直选择同一个节点,否则将使循环行为不可预测,并且可能不公平。上面的首选项示例很好解释了节点可用性,将如何影响了循环的可预测性。节点选择不应依赖于其他外部因素,否则循环将无法正常工作。
REST 客户端嗅探器的 javadoc
https://artifacts.elastic.co/javadoc/org/elasticsearch/client/elasticsearch-rest-client-sniffer/6.6.2/index.html
REST 客户端嗅探器与 elasticsearch 的发行周期相同。可以使用期望的嗅探器版本替换,但必须是 5.0.0-alpha4 之后的版本。嗅探器版本与其通信的 Elasticsearch 版本之间没有关联。嗅探器支持从 elasticsearch 2.x 及以上的版本上获取节点列表。
Maven 配置
若使用 Maven 作依赖管理,你可以这样配置依赖。将下列内容添加到你的 pom.xml 文件里:
org.elasticsearch.client
elasticsearch-rest-client-sniffer
5.6.4
一旦创建了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 sniffHosts() throws IOException {
return null; // 从外部源获取主机
}
};
Sniffer sniffer = Sniffer.builder(restClient)
.setHostsSniffer(hostsSniffer).build();
Java Low Level Rest Client是基于Http请求和ElasticSearch集群进行交互,兼容所有Elasticsearch版本,可以通过Http发送请求和接收返回结果,对应的API如下图: