【api】elasticsearch - 部分更新文档

document 的部分更新

对于solr和elasticsearch, 一般来说我们可能会先获取document, 再更新某个field, 然后reindex这个document。因为document是不可变的, 不能够修改, 只能被替换。

但其实很久之前我觉得, 这个操作对于客户端来说, 如果不考虑version的话, 为何不在服务器完成, 提供一个部分更新的api, 让用户根据场景合理使用api, 比之前的做法可以节省一个请求。

所以说, elasticsearch还是很人性化的, 它提供了这个api, 不过在其内部来说, api实现还是遵循了retrieve-change-reindex的步骤, 操作在shard中完成。

更新的安全问题

那么如果有多个客户端同时更新, 那么并发安全的问题es如何处理呢?
前面提到了, es内部是retrieve-change-reindex的步骤, 在retrieve阶段, es会获取当前的_version, 并在后面的reindex阶段使用。
如果其他进程修改了document, _version将会和update request中的不同, 从而更新将会失败。

这个类似乐观锁的策略, 如果需要重试的话, 可以使用retry_on_conflict设置重试次数,比如:

.../_update?retry_on_conflict=5

不过, 这个重试使用的场景在计数器上用的较多, 因为这个更新的顺序是不那么重要的。

REST api

言归正传, 接下来,介绍以下api的使用吧。

假设有个blog的document, 其中有个field叫做tags, 如果要部分更新这个tags

最简单的方式是使用doc参数, 和已有document合并, 已有的field会被覆盖, 新的field将会新增到document, REST api 如下:

POST /website/blog/1/_update
{
"doc" : {
"tags" : [ "testing" ],
"views": 0
}
}

如果成功, 会收到这样的response:

{
"_index" : "website",
"_id" : "1",
"_type" : "blog",
"_version" : 3
}

再获取一次这个文档, 检查一下是否真的只更新了tags这个field:

{
"_index": "website",
"_type": "blog",
"_id": "1",
"_version": 3,
"found": true,
"_source": {
"title": "My first blog entry",
"text": "Starting to get the hang of this...",
"tags": [ "testing" ],
"views": 0
}
}

java api

如果使用 java api 的话:

UpdateRequest updateRequest = new UpdateRequest("website", "blog", "1")
.doc(XContentFactory.jsonBuilder()
.startObject()
.field("tags", new String[]{"testing"})
.endObject());
client.update(updateRequest).get();

spring data elasticsearch api

如果使用spring-data-elasticsearch的ElasticsearchTemplate


UpdateRequest updateRequest = new UpdateRequest();
updateRequest
.doc(XContentFactory.jsonBuilder()
.startObject()
.field(field, value)
.endObject());
UpdateQuery updateQuery = new UpdateQueryBuilder()
//.withIndexName(indexName).withType(objectType)
.withClass(clz)
.withId(String.valueOf(id))
.withUpdateRequest(updateRequest)
.build();
elasticsearchTemplate.update(updateQuery);
elasticsearchTemplate.refresh(indexName, true);

使用这个api时可能会有这样的疑惑, UpdateQuery中有指定indexName和objectType, 但是@Document的Class中也有声明, 那么使用哪个呢?

我们可以看看elasticsearchTemplate.update(updateQuery)的源码:

   @Override
public UpdateResponse update(UpdateQuery query) {
return this.prepareUpdate(query).execute().actionGet();
}

private UpdateRequestBuilder prepareUpdate(UpdateQuery query) {
String indexName = isNotBlank(query.getIndexName()) ? query.getIndexName() : getPersistentEntityFor(query.getClazz()).getIndexName();
String type = isNotBlank(query.getType()) ? query.getType() : getPersistentEntityFor(query.getClazz()).getIndexType();
Assert.notNull(indexName, "No index defined for Query");
Assert.notNull(type, "No type define for Query");
Assert.notNull(query.getId(), "No Id define for Query");
Assert.notNull(query.getUpdateRequest(), "No IndexRequest define for Query");
UpdateRequestBuilder updateRequestBuilder = client.prepareUpdate(indexName, type, query.getId());

...
}

由上可知, query.getIndexName()query.getIndexName()会被优先使用, 如果未指定, 会从document的Class注解中获取。
如果为了提高性能, 能写上也好, 不过最好统一管理这两个名称, 方便以后修改变更.

参考


https://www.elastic.co/guide/en/elasticsearch/guide/current/partial-updates.html
https://www.elastic.co/guide/en/elasticsearch/client/java-api/1.7/java-update-api-merge-docs.html