翻译:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-low.html
0 综述
Java REST Client 有两种风格:
Java Low Level REST Client
:ElasticSearch
官方的低级客户端。它允许通过http
与一个Elasticsearch
集群通信。将请求的编组和响应的反编组工作留给用户自己处理。它兼容所有的Elasticsearch
的版本。Java High Level REST Client
:Elasticsearch
官方的高级客户端。它基于低级客户端,它暴露了API
特定的方法,并负责处理请求编组和响应非编组。
1 Java Low Level REST Client
低级客户端的功能包括:
- 最小的依赖
- 跨所有可用节点的负载平衡
- 节点故障和特定响应代码时的故障转移
- 连接惩罚失败(是否重试失败的节点取决于它失败的连续次数;失败次数越多,客户端在再次尝试同一节点之前等待的时间越长)
- 持久连接
- 跟踪请求和响应的日志记录
- 可选自动发现集群节点
1.1 开始
本节介绍如何开始使用Java Low Level REST Client
。
1.1.1 Javadoc文档
Java Low Level REST Client
的Javadoc
文档可以在这里找到。
1.1.2 Maven仓库
Java Low Level REST Client
所需要的最低Java
版本是1.7
。
1.1.2.1 Maven配置
以下是如何使用maven作为依赖关系管理器来配置依赖关系。将以下内容添加到您的pom.xml
文件中:
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>6.5.1</version>
</dependency>
1.1.2.2 Gradle配置
以下是使用gradle作为依赖关系管理器配置依赖关系的方法。将以下内容添加到您的build.gradle
文件中:
dependencies {
compile 'org.elasticsearch.client:elasticsearch-rest-client:6.5.1'
}
1.1.3 依赖
Java Low Level REST Client
内部使用 Apache Http Async Client 发送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
1.1.4 初始化
通过RestClient#builder(HttpHost...)
静态方法可以构建一个RestClent
实例。唯一需要的参数是客户端与之通信的一个或多个服务器主机。
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)
设置需要随每个请求一起发送的默认Headers
,以防止必须为每个请求指定它们。
RestClientBuilder builder = RestClient.builder(
new HttpHost("localhost", 9200, "http"));
builder.setMaxRetryTimeoutMillis(10000); // (1)
(1)
设置在多次尝试同一请求时应该遵守的超时时间。默认值为30秒,与默认套接字超时相同。如果自定义套接字超时,则应相应地调整最大重试超时。
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
允许设置的任何内容 ) 。
1.1.5 如何发送请求
一旦RestClient
被创建,可以通过调用 performRequest
或performRequestAsync
来发送请求。performRequest
是同步的,它将会阻塞调用线程,并在请求成功时或者由于失败而抛出异常时返回Response
对象。performRequestAsync
是异步的,它接受一个ResponseListener
参数,并在请求成功时或者由于失败而抛出异常时调用对应的方法。
下面是同步方法performRequest
的使用:
// 创建请求对象
Request request = new Request(
"GET", // (1)
"/"); // (2)
// 使用同步方法发送请求
Response response = restClient.performRequest(request);
(1)
这个参数需要填写HTTP
方法(GET
、POST
、HEAD
、等等)。
(2)
服务器上的url
路径。
下面是异步方法performRequestAsync
的使用:
Request request = new Request(
"GET", // (1)
"/"); // (2)
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)
服务器上的url
路径。
(3)
处理响应的回调。
(4)
处理请求失败的回调。
你可以向Request
请求对象中添加请求参数:
request.addParameter("pretty", "true");
你还可以将请求的body
部分设置为任何HttpEntity
对象:
request.setEntity(new NStringEntity(
"{\"json\":\"text\"}",
ContentType.APPLICATION_JSON));
注意:
为
HttpEntity
设置ContentType
非常重要,因为它将会设置对应的http
请求的Content-Type
请求头,以便Elasticsearch
能够正确的解析请求内容。
你也可以直接传入一个json
字符串,它将默认设置为application/json
。
request.setJsonEntity("{\"json\":\"text\"}");
1.1.5.1 请求选项
RequestOptions
类保存了应该在同一个应用程序中的多个请求之间共享的那部分请求数据。您可以创建一个单例实例,并在所有请求之间共享它:
// 单例(静态final对象)
private static final RequestOptions COMMON_OPTIONS;
// 静态初始化
static {
RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
builder.addHeader("Authorization", "Bearer " + TOKEN); // (1)
builder.setHttpAsyncResponseConsumerFactory( // (2)
new HeapBufferedResponseConsumerFactory(30 * 1024 * 1024 * 1024));
// 调用build()方法创建对象
COMMON_OPTIONS = builder.build();
}
(1)
添加所有请求都需要的headers
信息。
(2)
定制响应的消费者。
addHeader
方法是用来设置那些和认证以及代理相关的headers
,没有必要在这里设置Content-Type
,因为客户端会自动的将HttpEntity
中设置的ContentType
绑定到headers
中去。
你可以通过设置NodeSelector
来控制哪些节点将会收到请求。通常NodeSelector.NOT_MASTER_ONLY
是一个好的选择。
你还可以自定义响应的消费者来缓冲异步响应。默认消费者将在JVM
堆上缓冲最多100MB
的响应。如果响应更大,则请求将失败。
一旦你创建了一个 RequestOptions
的单例对象,你可以在创建Request
对象的时候使用它:
request.setOptions(COMMON_OPTIONS);
你还可以为每个请求定制化自己的RequestOptions
对象:
RequestOptions.Builder options = COMMON_OPTIONS.toBuilder();
options.addHeader("cats", "knock things off of other things");
request.setOptions(options);
1.1.5.2 多个并行异步操作
客户端很乐意并行执行许多操作。以下示例展示了并行索引许多文档。在现实世界中,您可能希望使用_bulk
API,但是这个例子很有启发性。
final CountDownLatch latch = new CountDownLatch(documents.length);
for (int i = 0; i < documents.length; i++) {
// 为每一个document创建一个request对象
Request request = new Request("PUT", "/posts/doc/" + i);
// 假设documents被保存在一个HttpEntity数组中
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)
处理返回的异常,这是由于通信错误或者反回了一个response
,它的status code
标识了错误。
1.1.6 如何处理响应
Response
对象,要么是通过调用同步方法performRequest
返回,要么是作为ResponseListener#onSuccess(Response)
的参数。
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)
获取返回响应的主机信息对象。
(3)
响应状态行,你可以获取响应状态码。
(4)
获取响应的headers
,也可以通过getHeader(String)
获取具体的某个header
。
(5)
获取org.apache.http.HttpEntity
对象的字符串形式。
在执行请求时,会在以下场景中抛出异常:
-
IOException
通信问题(比如,
SocketTimeoutException
) -
ResponseException
返回了一个响应,但是它的状态码表明发生了错误(不是
2xx
)
注意:
返回
404
的HEAD
请求不会抛出一个ResponseException
,因为它是一个预期的正确的HEAD
响应,只是没有找到资源而已。所有其他的HTTP方法(例如GET
)抛出ResponseException
了404
回应,除非该ignore
参数包含404
。ignore
是一个特殊的客户端参数,不会发送到Elasticsearch并包含逗号分隔的错误状态代码列表。它允许控制是否应将某些错误状态代码视为预期响应而不是异常。这对于例如get api是有用的,因为它可以返回404
当文档丢失时,在这种情况下,响应正文将不包含错误,而是通常的get api响应,只是没有找到未找到的文档。
请注意Java Low Level REST Client
不会提供json
序列化和反序列化的任何帮助,用户可以选择任何喜欢的第三方库。
底层的Apache Http Client
提供了不同的 org.apache.http.HttpEntity
实现,允许以不同的格式(流,字节数组,字符串等)提供请求主体。至于读取响应体, HttpEntity#getContent
方法很方便,它返回InputStream
来自先前缓冲的响应体的读数。作为替代方案,可以提供org.apache.http.nio.protocol.HttpAsyncResponseConsumer
控制字节读取和缓冲方式的自定义 。
1.1.7 记录日志
Java Low Level REST Client
和Apache Async Http Client
使用的是相同的日志库: Apache Commons Logging 。
1.2 常见配置
如初始化中所述,RestClientBuilder
同时支持提供 RequestConfigCallback
和HttpClientConfigCallback
,允许任何Apache Async Http Client
暴露的自定义配置。这些回调使得可以修改客户端的某些特定行为,而不会覆盖RestClient
初始化的所有其他默认配置。本节介绍一些需要为Java Low Level REST Client
进行其他配置的常见方案。
1.2.1 超时时间
在通过构建器构建RestClient
时,可以通过提供RequestConfigCallback
实例来配置请求超时。该接口有一个方法,它接收org.apache.http.client.config.RequestConfig.Builder
实例作为参数,并具有相同的返回类型。可以修改请求配置生成器,然后返回它。在下面的示例中,我们将增大连接超时时间(默认为1秒)和套接字超时时间(默认为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);
1.2.2 线程数量
Apache Http Async Client
默认启动一个调度线程(dispatcher thread
),一些连接管理器需要使用的工作线程(worker threads
),它们和本地检测到的处理器数量(取决于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());
}
});
1.2.3 基本认证
在通过构建器构建RestClient
时,可以通过提供HttpClientConfigCallback
来配置基本身份验证。该接口有一个方法,它接收org.apache.http.impl.nio.client.HttpAsyncClientBuilder
作为参数,并具有相同的返回类型。可以修改http client builder
,然后返回它。在下面的示例中,我们设置了一个需要基本身份验证的默认凭据提供程序:
final CredentialsProvider credentialsProvider =
new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY,
new UsernamePasswordCredentials("user", "password"));
RestClientBuilder builder = RestClient.builder(
new HttpHost("localhost", 9200))
.setHttpClientConfigCallback(new HttpClientConfigCallback() {
@Override
public HttpAsyncClientBuilder customizeHttpClient(
HttpAsyncClientBuilder httpClientBuilder) {
return httpClientBuilder
.setDefaultCredentialsProvider(credentialsProvider);
}
});
可以禁用身份验证,这意味着每个请求都将在没有携带包含认证的headers
的情况下发送,以查看是否接受该请求,并且在接收到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 HttpClientConfigCallback() {
@Override
public HttpAsyncClientBuilder customizeHttpClient(
HttpAsyncClientBuilder httpClientBuilder) {
httpClientBuilder.disableAuthCaching(); // (1)
return httpClientBuilder
.setDefaultCredentialsProvider(credentialsProvider);
}
});
(1)
禁用身份验证
1.2.4 加密通信
对通信进行加密也可以通过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 HttpClientConfigCallback() {
@Override
public HttpAsyncClientBuilder customizeHttpClient(
HttpAsyncClientBuilder httpClientBuilder) {
return httpClientBuilder.setSSLContext(sslContext);
}
});
如果没有提供显示配置,将使用系统默认配置。
1.2.5 其他
对于其他需要的配置,应该参考Apache HttpAsyncClient
文档:https://hc.apache.org/httpcomponents-asyncclient-4.1.x/
1.2.6 节点选择器
客户端以循环方式将每个请求发送到配置的节点之一。在初始化客户机时,可以通过节点选择器筛选节点。这在启用sniffing
时非常有用,以防只有专用的主节点应该被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.3 嗅探器
最小库允许自动从运行中的Elasticsearch
集群中发现节点,并将节点设置到存在的RestClient
实例中。默认情况下,它使用Nodes Info api
检索属于集群的节点,并使用jackson
解析获得的json
响应。
从Elasticsearch 2.x
开始兼容。
1.3.1 Javadoc文档
REST client sniffer
的Javadoc
文档可以在这里找到:https://artifacts.elastic.co/javadoc/org/elasticsearch/client/elasticsearch-rest-client-sniffer/6.5.2/index.html
1.3.2 Maven仓库
1.3.2.1 Maven配置
下面是如何使用maven作为依赖项管理器来配置依赖项。在pom.xml
中添加以下内容:
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client-sniffer</artifactId>
<version>6.5.2</version>
</dependency>
1.3.2.2 Gradle配置
下面是如何使用gradle作为依赖项管理器来配置依赖项。将以下内容添加到build.gradle
文件:
dependencies {
compile 'org.elasticsearch.client:elasticsearch-rest-client-sniffer:6.5.2'
}
1.3.3 使用
一旦一个RestClient
实例被创建,一个Sniffer
可以与其关联。Sniffer
将利用所提供的RestClient
定期(默认情况下每5分钟)从集群获取当前节点列表,并通过调用RestClient#setNodes
更新它们。
RestClient restClient = RestClient.builder(
new HttpHost("localhost", 9200, "http"))
.build();
Sniffer sniffer = Sniffer.builder(restClient).build();
不使用的时候,显式关闭Sniffer
是非常重要的,这将使其后台线程正确关闭并释放其所有资源。Sniffer
对象应具有与RestClient
客户端相同的生命周期,并在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) // (1)
.build();
Sniffer sniffer = Sniffer.builder(restClient)
.setSniffAfterFailureDelayMillis(30000) // (2)
.build();
sniffOnFailureListener.setSniffer(sniffer); // (3)
(1)
为RestClient
实例设置失败监听器
(2)
当失败时启动嗅探,不仅节点在每次失败后都会更新,而且在失败后的一分钟内,还会比平时更快地安排额外的嗅探轮次,假设一切都会恢复正常我们希望尽快检测到。该间隔可以通过setSniffAfterFailureDelayMillis
方法在嗅探器创建时定制。注意,如果没有像前面解释的那样启用失败时的嗅探,则最后一个配置参数没有任何效果。
(3)
将Sniffer
实例关联到失败监听器。
Elasticsearch Nodes Info 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
,默认为一秒。这是timeout
在调用Nodes Info 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();
此外,NodesSniffer
可以为高级用例提供自定义实现,可能需要从外部源而不是从Elasticsearch获取Node
s:
RestClient restClient = RestClient.builder(
new HttpHost("localhost", 9200, "http"))
.build();
NodesSniffer nodesSniffer = new NodesSniffer() {
@Override
public List<Node> sniff() throws IOException {
return null;
}
};
Sniffer sniffer = Sniffer.builder(restClient)
.setNodesSniffer(nodesSniffer).build();