Elasticsearch(九)elasticsearch数据输入和输出二 -- 批量操作

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/cc907566076/article/details/78623433

取回多个文档

Elasticsearch 的速度已经很快了,但甚至能更快。 将多个请求合并成一个,避免单独处理每个请求花费的网络时延和开销。 如果你需要从 Elasticsearch 检索很多文档,那么使用 multi-get 或者 mget API 来将这些检索请求放在一个请求中,将比逐个文档请求更快地检索到全部文档。

mget API 要求有一个 docs 数组作为参数,(必须参数,否则报错,如图,而且不知是否为版本原因,必使用post才可以)
这里写图片描述
每个 元素包含需要检索文档的元数据, 包括 _index 、 _type 和 _id 。如果你想检索一个或者多个特定的字段,那么你可以通过 _source 参数来指定这些字段的名字:

GET /_mget
{
“docs” : [
{
“_index” : “website”,
“_type” : “blog”,
“_id” : 2
},
{
“_index” : “website”,
“_type” : “pageviews”,
“_id” : 1,
“_source”: “views”
}
]
}

View in Sense

该响应体也包含一个 docs 数组 , 对于每一个在请求中指定的文档,这个数组中都包含有一个对应的响应,且顺序与请求中的顺序相同。 其中的每一个响应都和使用单个 get request 请求所得到的响应体相同:

{
“docs” : [
{
“_index” : “website”,
“_id” : “2”,
“_type” : “blog”,
“found” : true,
“_source” : {
“text” : “This is a piece of cake…”,
“title” : “My first external blog entry”
},
“_version” : 10
},
{
“_index” : “website”,
“_id” : “1”,
“_type” : “pageviews”,
“found” : true,
“_version” : 2,
“_source” : {
“views” : 2
}
}
]
}
这里写图片描述
如果想检索的数据都在相同的 _index 中(甚至相同的 _type 中),则可以在 URL 中指定默认的 /_index 或者默认的 /_index/_type 。

你仍然可以通过单独请求覆盖这些值:

GET /website/blog/_mget
{
“docs” : [
{ “_id” : 2 },
{ “_type” : “pageviews”, “_id” : 1 }
]
}
这里写图片描述
事实上,如果所有文档的 _index 和 _type 都是相同的,你可以只传一个 ids 数组,而不是整个 docs 数组:

GET /website/blog/_mget
{
“ids” : [ “2”, “1” ]
}

这里写图片描述

注意,我们请求的第二个文档是不存在的。我们指定类型为 blog ,但是文档 ID 1 的类型是 pageviews ,这个不存在的情况将在响应体中被报告:

{
“docs” : [
{
“_index” : “website”,
“_type” : “blog”,
“_id” : “2”,
“_version” : 10,
“found” : true,
“_source” : {
“title”: “My first external blog entry”,
“text”: “This is a piece of cake…”
}
},
{
“_index” : “website”,
“_type” : “blog”,
“_id” : “1”,
“found” : false
}
]
}
这里写图片描述
未找到该文档。

事实上第二个文档未能找到并不妨碍第一个文档被检索到。每个文档都是单独检索和报告的。

Note
即使有某个文档没有找到,上述请求的 HTTP 状态码仍然是 200 。事实上,即使请求 没有 找到任何文档,它的状态码依然是 200 –因为 mget 请求本身已经成功执行。 为了确定某个文档查找是成功或者失败,你需要检查 found 标记。

Client程序演示

/**
     * 一次请求多个文档 Muti-get(即mget)
     * 将多个请求合并成一个,避免单独处理每个请求花费的网络时延和开销。
     *  如果你需要从 Elasticsearch 检索很多文档,那么使用 multi-get 或者 mget API 来将这些检索请求放在一个请求中,将比逐个文档请求更快地检索到全部文档。
     * @param client
     */
    private static void findBlogsOneTime(Client client) {
        MultiGetRequestBuilder builder = client.prepareMultiGet()
                .add("website","blog","1")    //单一ID       
                .add("website","blog","1","2") //多ID
                .add("logstash-car-msg", "carmsg","BCAAAD0005");     //甚至可以取另一个索引里面的哟     

        Item item1 = new Item("website","blog","1");
        builder.add(item1);
        Item item2 = new Item("website1","blog1","1");
        item2.fetchSourceContext(new FetchSourceContext(true,new String[] {"tags"},null));//返回固定字段
        builder.add(item2);

        MultiGetResponse multiGetItemResponses =builder.get();

        for (MultiGetItemResponse itemResponse : multiGetItemResponses) { 
            GetResponse response = itemResponse.getResponse();
            System.out.println(response.isExists());
            if (response.isExists()) {   //如果存在    
                String json = response.getSourceAsString(); //返回查找的结果
                System.out.println(response.getIndex()+"-"+response.getType()+"-"+response.getId()+"--->"+json);
            }
        }
    }

调用:
// 11.获取多个文档 Muti-get
findBlogsOneTime(client);

结果显示:
连接成功…
true
website-blog-1—>{“doc”:{“tags”:[“testing”],”views”:0}}
true
website-blog-1—>{“doc”:{“tags”:[“testing”],”views”:0}}
false
true
logstash-car-msg-carmsg-BCAAAD0005—>{“host”:”YFCSPT-SUSE-86”,”vehicleId”:”BCAAAD0005”,”vehicleName”:”巴斯达BBL5054XJE1公共安全监测车5”,”searchCode”:”BSD-BBL5054XJE5”,”vehicleClass”:”特种车类11”,”vehicleClassPicc”:”C49”,”vehicleType”:”1”,”brandNameNew”:”巴斯达5”,”useYears”:5,”makeDate”:”2017-01-01 11:23:01”}
true
website-blog-1—>{“doc”:{“tags”:[“testing”],”views”:0}}
true
website1-blog1-1—>{“tags”:[“testing”,”search”]}

代价较小的批量操作

代价较小的批量操作

与 mget 可以使我们一次取回多个文档同样的方式, bulk API 允许在单个步骤中进行多次 create 、 index 、 update 或 delete 请求。 如果你需要索引一个数据流比如日志事件,它可以排队和索引数百或数千批次。

格式
bulk 与其他的请求体格式稍有不同,如下所示:

{ action: { metadata }}\n
{ request body }\n
{ action: { metadata }}\n
{ request body }\n

这种格式类似一个有效的单行 JSON 文档 流 ,它通过换行符(\n)连接到一起。注意两个要点:

• 每行一定要以换行符(\n)结尾, 包括最后一行 。这些换行符被用作一个标记,可以有效分隔行。
• 这些行不能包含未转义的换行符,因为他们将会对解析造成干扰。这意味着这个 JSON 不 能使用 pretty 参数打印。

1.action/metadata 行指定 哪一个文档 做 什么操作 。

action 必须是以下选项之一:
create 如果文档不存在,那么就创建它。详情请见 创建新文档。
index 创建一个新文档或者替换一个现有的文档。详情请见 索引文档 和 更新整个文档。 update 部分更新一个文档。详情请见 文档的部分更新。
delete 删除一个文档。详情请见 删除文档。

metadata 应该 指定被索引、创建、更新或者删除的文档的 _index 、 _type 和 _id 。

例如,一个 delete 请求看起来是这样的:
{ “delete”: { “_index”: “website”, “_type”: “blog”, “_id”: “123” }}

2.request body 行由文档的 _source 本身组成–文档包含的字段和值。
它是 index 和 create 操作所必需的,这是有道理的:你必须提供文档以索引。
它也是 update 操作所必需的,并且应该包含你传递给 update API 的相同请求体:
doc 、 upsert 、 script 等等。
删除操作不需要 request body 行。

{ “create”: { “_index”: “website”, “_type”: “blog”, “_id”: “123” }}
{ “title”: “My first blog post” }

如果不指定 _id ,将会自动生成一个 ID :
{ “index”: { “_index”: “website”, “_type”: “blog” }}
{ “title”: “My second blog post” }

为了把所有的操作组合在一起,一个完整的 bulk 请求 有以下形式:

POST /_bulk
{ “delete”: { “_index”: “website”, “_type”: “blog”, “_id”: “123” }}
{ “create”: { “_index”: “website”, “_type”: “blog”, “_id”: “123” }}
{ “title”: “My first blog post” }
{ “index”: { “_index”: “website”, “_type”: “blog” }}
{ “title”: “My second blog post” }
{ “update”: { “_index”: “website”, “_type”: “blog”, “_id”: “123”, “_retry_on_conflict” : 3} }
{ “doc” : {“title” : “My updated blog post”} }

请注意 delete 动作不能有请求体,它后面跟着的是另外一个操作。
谨记最后一个换行符不要落下。
这个 Elasticsearch 响应包含 items 数组, 这个数组的内容是以请求的顺序列出来的每个请求的结果。

{
“took”: 4,
“errors”: false,
“items”: [
{ “delete”: {
“_index”: “website”,
“_type”: “blog”,
“_id”: “123”,
“_version”: 2,
“status”: 200,
“found”: true
}},
{ “create”: {
“_index”: “website”,
“_type”: “blog”,
“_id”: “123”,
“_version”: 3,
“status”: 201
}},
{ “create”: {
“_index”: “website”,
“_type”: “blog”,
“_id”: “EiwfApScQiiy7TIKFxRCTw”,
“_version”: 1,
“status”: 201
}},
{ “update”: {
“_index”: “website”,
“_type”: “blog”,
“_id”: “123”,
“_version”: 4,
“status”: 200
}}
]
}

所有的子请求都成功完成。

每个子请求都是独立执行,因此某个子请求的失败不会对其他子请求的成功与否造成影响。 如果其中任何子请求失败,最顶层的 error 标志被设置为 true ,并且在相应的请求报告出错误明细:

POST /_bulk
{ “create”: { “_index”: “website”, “_type”: “blog”, “_id”: “123” }}
{ “title”: “Cannot create - it already exists” }
{ “index”: { “_index”: “website”, “_type”: “blog”, “_id”: “123” }}
{ “title”: “But we can update it” }

在响应中,我们看到 create 文档 123 失败,因为它已经存在。但是随后的 index 请求,也是对文档 123 操作,就成功了:

{
“took”: 3,
“errors”: true,
“items”: [
{ “create”: {
“_index”: “website”,
“_type”: “blog”,
“_id”: “123”,
“status”: 409,
“error”: “DocumentAlreadyExistsException
[[website][4] [blog][123]:
document already exists]”
}},
{ “index”: {
“_index”: “website”,
“_type”: “blog”,
“_id”: “123”,
“_version”: 5,
“status”: 200
}}
]
}

一个或者多个请求失败。
这个请求的HTTP状态码报告为 409 CONFLICT 。
解释为什么请求失败的错误信息。
第二个请求成功,返回 HTTP 状态码 200 OK 。
这也意味着 bulk 请求不是原子的: 不能用它来实现事务控制。每个请求是单独处理的,因此一个请求的成功或失败不会影响其他的请求。

Client程序演示

/**
     * 批处理 Bulk
     * @param client
     * @throws IOException 
     * @throws InterruptedException 
     */
    private static void dealDataWithBulk(Client client) throws IOException, InterruptedException {
        // (1)新建批处理器
        BulkProcessor bulkProcessor = BulkProcessor.builder(
            client,  //客户端
            new BulkProcessor.Listener() {
                //顾名思义,在执行Bulk之前调用
                public void beforeBulk(long executionId, BulkRequest request) {
                    System.out.println("待请求数量:"+request.numberOfActions());
                }

                //批量执行后调用此操作
                public void afterBulk(long executionId, BulkRequest request, BulkResponse response) {
                    if(!response.hasFailures()) {
                        System.out.println("执行成功!");
                    }else {
                        System.out.println("执行失败!");
                    }
                }

                //批量执行失败时被调用,并提出了一个Throwable
                public void afterBulk(long executionId, BulkRequest request, Throwable failure) {
                    System.out.println("失败的错误信息:"+failure.getMessage());
                }

            })
            .setBulkActions(10000) //每一万个请求执行一次,默认1000
            .setBulkSize(new ByteSizeValue(5, ByteSizeUnit.MB)) //每5MB冲刷一次,默认5mb
            .setFlushInterval(TimeValue.timeValueSeconds(5)) //无论请求数量多少,我们都希望5秒刷新一次,默认不设置,即不刷新
            .setConcurrentRequests(1) //设置并发请求的数量,0意味着只有一个请求被允许执行,1意味着在累积新的批量请求时允许执行1个并发请求,默认1
            .setBackoffPolicy(//设置一个退避策略,最初等待100ms,成倍增长,重试三次,每当有一个或多个批量项目请求失败时,就会尝试重试,默认8次重试,50ms的延迟
                BackoffPolicy.exponentialBackoff(TimeValue.timeValueMillis(100), 3)) //EsRejectedExecutionException 这表示计算资源可用于处理请求的计算资源太少。要禁用退避,请传递BackoffPolicy.noBackoff()。
            .build();

        //(2)然后就可以使用批处理器了,可以添加你的请求
        //delete
        bulkProcessor.add(new DeleteRequest("website","blog","2"));
        //create
        bulkProcessor.add(client.prepareIndex("website","blog","1")
                .setSource(XContentFactory.jsonBuilder()
                        .startObject()
                            .field("title", "My first blog post")
                        .endObject()
                      )
            .setOpType(OpType.CREATE) //已存在不可以创建,不设置的话如果已存在会默认更新
            .request() );
        //index
        bulkProcessor.add(client.prepareIndex("website","blog")
                .setSource(XContentFactory.jsonBuilder()
                        .startObject()
                            .field("title", "My first blog post2")
                        .endObject()
                      )
                .request()
            );
        //update
        bulkProcessor.add(new UpdateRequest("website","blog","1")
                .doc(XContentFactory.jsonBuilder()
                        .startObject()
                            .field("title", "My updated blog post3")
                        .endObject())
                .retryOnConflict(5)  //失败的话重试的次数,默认0,这里设置为5
             );


        //(3)关闭批处理器 -- 两种关闭方式皆可

         /* 如果他们通过设置计划 flushInterval,两种方法都刷新任何剩余的文件,并关闭所有其它调度刷新
         * 如果启用了并发请求,则awaitClose方法将等待所有批量请求完成的指定超时true,如果在完成所有批量请求之前超过了指定的等待时间, 则返回false。
         * close方法不会等待任何剩余的批量请求完成并立即退出。*/

        bulkProcessor.awaitClose(10, TimeUnit.MINUTES);
        bulkProcessor.close();
}
调用:
// 12.批处理
dealDataWithBulk(client);

我们先让此”website”,”blog”中无任何数据,调用结果:
待请求数量:4
执行成功!

再次调用,注意这时_id为1的数据已存在:
待请求数量:4
执行失败!

虽然结果是执行失败,但是看我们的数据:

这里写图片描述

不要重复指定Index和Type

也许你正在批量索引日志数据到相同的 index 和 type 中。 但为每一个文档指定相同的元数据是一种浪费。相反,可以像 mget API 一样,在 bulk 请求的 URL 中接收默认的 /_index 或者 /_index/_type :

POST /website/_bulk
{ “index”: { “_type”: “log” }}
{ “event”: “User logged in” }

你仍然可以覆盖元数据行中的 _index 和 _type , 但是它将使用 URL 中的这些元数据值作为默认值:

POST /website/log/_bulk
{ “index”: {}}
{ “event”: “User logged in” }
{ “index”: { “_type”: “blog” }}
{ “title”: “Overriding the default type” }

多大是太大了?

整个批量请求都需要由接收到请求的节点加载到内存中,因此该请求越大,其他请求所能获得的内存就越少。 批量请求的大小有一个最佳值,大于这个值,性能将不再提升,甚至会下降。 但是最佳值不是一个固定的值。它完全取决于硬件、文档的大小和复杂度、索引和搜索的负载的整体情况。

幸运的是,很容易找到这个 最佳点 :通过批量索引典型文档,并不断增加批量大小进行尝试。 当性能开始下降,那么你的批量大小就太大了。一个好的办法是开始时将 1,000 到 5,000 个文档作为一个批次, 如果你的文档非常大,那么就减少批量的文档个数。

密切关注你的批量请求的物理大小往往非常有用,一千个 1KB 的文档是完全不同于一千个 1MB 文档所占的物理大小。 一个好的批量大小在开始处理后所占用的物理大小约为 5-15 MB。

猜你喜欢

转载自blog.csdn.net/cc907566076/article/details/78623433