在版本2.0之前,Elasticsearch作为JAR提供,其中一些(但不是全部)通用依赖项被阴影化并打包在相同的工件中。这有助于将Elasticsearch嵌入到他们自己的应用程序中的Java用户,以避免像Guava,Joda,Jackson等模块的版本冲突。当然,还有一些其他未着色的依赖关系(如Lucene)仍然可能导致冲突。
不幸的是,阴影是一个复杂和容易出错的过程,其解决的问题对于某些人而造成了问题别人。阴影使得开发人员和插件作者很难正确编写和调试代码,因为在构建期间将包重命名。最后,我们用来测试Elasticsearch无阴影,然后发送阴影的jar,我们不喜欢发布任何我们没有测试的东西。
我们已经决定从2.0开始无需渲染Elasticsearch。
处理版本冲突
如果您想在您的Java应用程序中使用Elasticsearch,则可能需要处理与第三方依赖关系(如Guava和Joda)之间的版本冲突。例如,也许Elasticsearch使用Joda 2.8,而你的代码使用Joda 2.1。你有两个选择:
最简单的解决方案是升级。较新的模块版本可能会修复旧的错误。越落后,越难以升级。当然,您可能正在使用第三方依赖关系,而这依赖于依赖于过时的软件包版本,从而阻止您进行升级。
第二种选择是重新定位麻烦的依赖关系,并使用您自己的应用程序或Elasticsearch以及Elasticsearch客户端所需的任何插件来隐藏它们。
如何遮挡Elasticsearch
为了帮助您实现这一目标,我们已经组建了一个示例应用程序,它允许您运行:
· Elasticsearch传输客户端
· 与盾安全插件
· 和Joda的冲突版本。
你的项目pom.xml可能看起来像这样:
上述问题是Joda依赖项:您的项目需要Joda 2.1,但Elasticsearch 2.0.0-beta2需要Joda 2.8。
着色Elasticsearch
为了解决这种情况,我们创建了一个遮蔽Elasticsearch和Shield的新Maven项目。本pom.xml应该是这样的:
阴影和重新安置冲突的软件包
现在通过添加类似下面的内容来遮蔽和重定位与您自己的应用程序冲突的所有包pom.xml。这个例子增加了Joda 2.8:
运行mvn clean install将创建Elasticsearch,Shield和Joda 2.8的着色版本,您可以在应用程序中使用它。
将此jar包嵌入您的应用程序中
在你的项目中,你现在可以依靠:
您可以像以前一样构建和使用Elasticsearch TransportClient:
要使用您自己的Joda版本,只需导入org.joda.time.DateTime。你甚至可以通过导入访问Joda的阴影版本my.elasticsearch.joda.time.DateTime,尽管我们不建议这样做。以下示例显示了如何在同一个JVM中访问这两个版本:
看看这个演示项目,它显示了上面的一个完整的运行示例。
未来的发展
处理版本冲突是一个长期存在的问题,但未来我们希望比现在更容易。减少第三方依赖关系之间冲突的最简单方法是减少Elasticsearch所具有的依赖关系的数量。我们将永远无法删除所有的依赖关系,但是我们通过移除Guava来支持本地Java 8代码,从而开始了一步。我们也将研究用Java.time替代Joda(同样在Java 8中),甚至可能为依赖最小的Elasticsearch创建一个瘦客户端。
当然,这些变化不小。无论我们最终决定的是从长远来看的正确道路,我们需要时间才能到达目的地。与此同时,我们想为当今的问题提供一个相对简单的解决方案。
索引API编辑
索引API允许将类型化的JSON文档编入特定的索引并使其可搜索。
生成JSON文档编辑
生成JSON文档有几种不同的方式:
· 手动(也可以自己动手)使用本地byte[]或作为String
· 使用Map它将被自动转换为与其相当的JSON
· 使用第三方库来序列化你的豆,如 杰克逊
· 使用内置的帮助器XContentFactory.jsonBuilder()
在内部,每种类型都被转换为byte[](所以一个字符串被转换为a byte[])。因此,如果对象已经以这种形式存在,那就使用它。这jsonBuilder是高度优化的JSON生成器,可直接构建一个byte[]。
自己动手
String json = "{" +
"\"user\":\"kimchy\"," +
"\"postDate\":\"2013-01-30\"," +
"\"message\":\"trying out Elasticsearch\"" +
"}";
这里没有什么困难,但请注意,您将不得不根据日期格式对日期进行编码 。
Map<String, Object> json = new HashMap<String, Object>();
json.put("user","kimchy");
json.put("postDate",new Date());
json.put("message","trying out Elasticsearch");
序列化你的bean编辑
您可以使用Jackson将您的bean序列化为JSON。请将Jackson Databind添加 到您的项目中。然后你可以使用ObjectMapper序列化你的bean:
import com.fasterxml.jackson.databind.*;
// instance a json mapper
ObjectMapper mapper = new ObjectMapper(); // create once, reuse
// generate json
byte[] json = mapper.writeValueAsBytes(yourbeaninstance);
使用Elasticsearch助手编辑
Elasticsearch提供了内置的助手来生成JSON内容。
import static org.elasticsearch.common.xcontent.XContentFactory.*;
XContentBuilder builder = jsonBuilder()
.startObject()
.field("user", "kimchy")
.field("postDate", new Date())
.field("message", "trying out Elasticsearch")
.endObject()
请注意,您也可以使用startArray(String)和 endArray()方法添加数组。顺便说一下,该field方法接受许多对象类型。您可以直接传递数字,日期和其他XContentBuilder对象。
如果您需要查看生成的JSON内容,则可以使用该 string()方法。
索引文件编辑
以下示例将一个JSON文档索引为一个名为twitter的索引,名为tweet,类型为id,值为1:
import static org.elasticsearch.common.xcontent.XContentFactory.*;
IndexResponse response = client.prepareIndex("twitter", "tweet", "1")
.setSource(jsonBuilder()
.startObject()
.field("user", "kimchy")
.field("postDate", new Date())
.field("message", "trying out Elasticsearch")
.endObject()
)
.get();
请注意,您还可以将文档编入索引为JSON字符串,并且不必提供ID:
String json = "{" +
"\"user\":\"kimchy\"," +
"\"postDate\":\"2013-01-30\"," +
"\"message\":\"trying out Elasticsearch\"" +
"}";
IndexResponse response = client.prepareIndex("twitter", "tweet")
.setSource(json)
.get();
IndexResponse 对象会给你一个报告:
//索引名称
String _index = response.getIndex();
//类型类型
String _type = response.getType();
//文档ID(生成与否)
String _id = response.getId();
//版本(如果这是您第一次索引此文档,您将得到:1
long _version = response.getVersion();
//状态已经存储了当前的实例语句。
RestStatus status = response.status();
有关索引操作的更多信息,请查看REST 索引文档。
操作线程编辑
索引API允许设置在同一节点上执行API的实际执行时(该API在同一服务器上分配的碎片上执行)时将执行的线程模型。
这些选项是在不同的线程上执行操作,或者在调用线程上执行它(注意API仍然是异步的)。默认情况下,operationThreaded设置为true表示在不同的线程上执行操作。
获取API编辑
get API允许从索引中获取基于其id的类型化JSON文档。以下示例从名为twitter的索引中获取JSON文档,该类型名为tweet,ID为1:
GetResponse response = client.prepareGet("twitter", "tweet", "1").get();
有关获取操作的更多信息,请查看REST 获取文档。
操作线程编辑
get API允许设置在同一节点上执行API的实际执行时(API在同一服务器上分配的分片上执行)时执行操作的线程模型。
选项是在不同的线程上执行操作,或者在调用线程上执行它(请注意API仍然是异步的)。默认情况下,operationThreaded设置为true表示在不同的线程上执行操作。这是一个例子,它将其设置为 false:
GetResponse response = client.prepareGet("twitter", "tweet", "1")
.setOperationThreaded(false)
.get();
删除API编辑
删除API允许从基于id的特定索引中删除一个类型化的JSON文档。以下示例从名为twitter的索引中删除JSON文档,该类型名为tweet,ID为1:
DeleteResponse response = client.prepareDelete(“twitter”,“tweet”,“1”)。get();
有关删除操作的更多信息,请查看 删除API文档。
操作线程编辑
删除API允许设置在同一节点上执行API的实际执行(该API在同一服务器上分配的碎片上执行)时执行操作的线程模型。
选项是在不同的线程上执行操作,或者在调用线程上执行它(请注意API仍然是异步的)。默认情况下,operationThreaded设置为true表示在不同的线程上执行操作。这是一个例子,它将其设置为 false:
DeleteResponse response = client.prepareDelete("twitter", "tweet", "1")
.setOperationThreaded(false)
.get();
通过查询API删除允许根据查询结果删除给定的一组文档:
BulkByScrollResponse response =
DeleteByQueryAction.INSTANCE.newRequestBuilder(client)
.filter(QueryBuilders.matchQuery("gender", "male"))
long deleted = response.getDeleted();
因为它可能是一个长时间运行的操作,所以如果你希望异步执行它,你可以调用,execute而不是get 像下面这样提供一个监听器
DeleteByQueryAction.INSTANCE.newRequestBuilder(client)
.filter(QueryBuilders.matchQuery("gender", "male"))
.execute(new ActionListener<BulkIndexByScrollResponse>() {
@Override
public void onResponse(BulkIndexByScrollResponse response) {
long deleted = response.getDeleted();
}
@Override
public void onFailure(Exception e) {
// Handle the exception
}
});
更新API编辑
您可以创建一个UpdateRequest并将其发送给客户端:
UpdateRequest updateRequest = new UpdateRequest();
updateRequest.index("index");
updateRequest.type("type");
updateRequest.id("1");
updateRequest.doc(jsonBuilder()
.startObject()
.field("gender", "male")
.endObject());
client.update(updateRequest).get();
或者你可以使用prepareUpdate()方法:
client.prepareUpdate("ttl", "doc", "1")
.setScript(new Script("ctx._source.gender = \"male\"" , ScriptService.ScriptType.INLINE, null, null))
.get();
client.prepareUpdate("ttl", "doc", "1")
.startObject()
.field("gender", "male")
.endObject())
.get();
你的脚本。它也可以是本地存储的脚本名称。在这种情况下,你需要使用ScriptService.ScriptType.FILE |
|
将合并到现有文件的文件。 |
请注意,您不能同时提供script和doc。
通过脚本编辑更新
更新API允许根据提供的脚本更新文档:
UpdateRequest updateRequest = new UpdateRequest("ttl", "doc", "1")
.script(new Script("ctx._source.gender = \"male\""));
client.update(updateRequest).get();
通过合并文档编辑进行更新
更新API还支持传递部分文档,该文档将被合并到现有文档中(简单的递归合并,对象的内部合并,替换核心“键/值”和数组)。例如:
UpdateRequest updateRequest = new UpdateRequest("index", "type", "1")
.doc(jsonBuilder()
.startObject()
.field("gender", "male")
.endObject());
client.update(updateRequest).get();
Upsert编辑
也有支持upsert。如果文档不存在,upsert 元素的内容将用于索引新鲜文档:
IndexRequest indexRequest = new IndexRequest("index", "type", "1")
.source(jsonBuilder()
.startObject()
.field("name", "Joe Smith")
.field("gender", "male")
.endObject());
UpdateRequest updateRequest = new UpdateRequest("index", "type", "1")
.doc(jsonBuilder()
.startObject()
.field("gender", "male")
.endObject())
client.update(updateRequest).get();
如果文档不存在,indexRequest则会添加 文档 |
如果文件index/type/1已经存在,我们将在这个操作之后有一个文件,例如:
{
"name" : "Joe Dalton",
}
该字段由更新请求添加 |
如果它不存在,我们将有一个新的文件:
{
"name" : "Joe Smith",
"gender": "male"
}
多获取API编辑
多获取API允许基于它们获取文档列表index,type并且id:
MultiGetResponse multiGetItemResponses = client.prepareMultiGet()
.add("twitter", "tweet", "2", "3", "4")
.add("another", "type", "foo")
.get();
for (MultiGetItemResponse itemResponse : multiGetItemResponses) {
GetResponse response = itemResponse.getResponse();
String json = response.getSourceAsString();
}
}
得到一个单一的ID |
|
或通过相同索引/类型的ID列表 |
|
你也可以从另一个索引获得 |
|
迭代结果集 |
|
您可以检查文档是否存在 |
|
进入该_source领域 |
批量API
批量API允许在单个请求中索引和删除多个文档。以下是一个示例用法:
import static org.elasticsearch.common.xcontent.XContentFactory.*;
BulkRequestBuilder bulkRequest = client.prepareBulk();
// 使用客户端#准备,或使用请求#直接构建索引/删除请求
bulkRequest.add(client.prepareIndex("twitter", "tweet", "1")
.setSource(jsonBuilder()
.startObject()
.field("user", "kimchy")
.field("postDate", new Date())
.field("message", "trying out Elasticsearch")
.endObject()
)
);
bulkRequest.add(client.prepareIndex("twitter", "tweet", "2")
.setSource(jsonBuilder()
.startObject()
.field("user", "kimchy")
.field("postDate", new Date())
.field("message", "another post")
.endObject()
)
);
BulkResponse bulkResponse = bulkRequest.get();
if (bulkResponse.hasFailures()) {
//遍历每个批量响应项来处理失败
}
使用批量处理器编辑
本BulkProcessor类提供了一个简单的界面,自动冲水,基于数或请求的大小批量操作,或在给定的时间。
要使用它,请先创建一个BulkProcessor实例:
import org.elasticsearch.action.bulk.BackoffPolicy;
import org.elasticsearch.action.bulk.BulkProcessor;
import org.elasticsearch.common.unit.ByteSizeUnit;
import org.elasticsearch.common.unit.ByteSizeValue;
import org.elasticsearch.common.unit.TimeValue;
BulkProcessor bulkProcessor = BulkProcessor.builder(
new BulkProcessor.Listener() {
@Override
public void beforeBulk(long executionId,
@Override
public void afterBulk(long executionId,
BulkRequest request,
BulkResponse response) { ... }
@Override
public void afterBulk(long executionId,
BulkRequest request,
})
.setBulkSize(new ByteSizeValue(5, ByteSizeUnit.MB))
.setFlushInterval(TimeValue.timeValueSeconds(5))
.setBackoffPolicy(
BackoffPolicy.exponentialBackoff(TimeValue.timeValueMillis(100), 3))
.build();
添加您的elasticsearch客户端 |
|
这个方法在执行bulk之前被调用。例如,你可以看到numberOfActions request.numberOfActions() |
|
批量执行后调用此方法。你可以例如检查是否有一些失败的请求response.hasFailures() |
|
这个方法在批量失败并被提出时调用 Throwable |
|
我们希望每10万次请求执行一次批量操作 |
|
我们希望每5mb刷新一次散装 |
|
无论请求的数量是多少,我们都希望每5秒刷新一次 |
|
设置并发请求的数量。值为0意味着只有一个请求将被允许执行。值为1表示在累积新的批量请求时允许执行1个并发请求。 |
|
设置自定义退避策略,最初将等待100ms,成倍增加,并重试三次。每当有一个或多个批量项目请求失败时尝试重试,EsRejectedExecutionException 这表示可用于处理请求的计算资源太少。要禁用退避,请传递BackoffPolicy.noBackoff()。 |
默认情况下,BulkProcessor:
· 将bulkActions设置为 1000
· 将bulkSize设置为 5mb
· 不会设置flushInterval
· 将concurrentRequests设置为1,这意味着刷新操作的异步执行。
· 将backoffPolicy设置为指数回退,重试次数为8次,启动延迟时间为50ms。总等待时间大约为5.1秒。
添加请求
然后,您可以简单地将您的请求添加到BulkProcessor:
bulkProcessor.add(new IndexRequest("twitter", "tweet", "1").source(/* your doc here */));
bulkProcessor.add(new DeleteRequest("twitter", "tweet", "2"));
关闭批量处理器编辑
当所有文档都加载到BulkProcessor它时,可以通过使用awaitClose或close方法关闭它:
bulkProcessor.awaitClose(10,TimeUnit.MINUTES);
要么
bulkProcessor.close();
这两种方法都会清除所有剩余的文档,并禁用所有其他计划的刷新,如果这些刷新是通过设置进行的flushInterval。如果启用了并发请求,则awaitClose方法将等待所有批量请求完成的指定超时,然后返回true(如果在完成所有批量请求之前超过了指定的等待时间) false。该close方法不会等待任何剩余的批量请求完成并立即退出。
在测试中使用批量处理器编辑
如果您正在使用elasticsearch运行测试并且正在使用BulkProcessor填充数据集,则应该更好地设置并发请求的数量,0以便以同步方式执行批量刷新操作:
BulkProcessor bulkProcessor = BulkProcessor.builder(client, new BulkProcessor.Listener() { /* Listener methods */ })
.setBulkActions(10000)
.setConcurrentRequests(0)
.build();
//添加你的请求
bulkProcessor.add(/* Your requests */);
//刷新任何剩余的请求
bulkProcessor.flush();
//或者关闭bulkProcessor,如果你不再需要的话
bulkProcessor.close();
//刷新你的索引
client.admin().indices().prepareRefresh().get();
//现在你可以开始搜索!
client.prepareSearch().get();