ElasticSearch(十二)——无文档ID的Json文件批量导入(Java/Python)

版权声明:本文为博主原创文章,转载请注明出处,欢迎交流,qq911283415。 https://blog.csdn.net/HaixWang/article/details/80816360

现在有这么一个需求:
近1T的JSON文件,每份文件的字段不一定完全相同。
【大约5亿条数据,测试了下,在设置为
"number_of_shards": 5,
"number_of_replicas": 2
】时,140万条数据耗时9分40秒,太差劲了。TODO:优化
对于一个索引来说,number_of_shards只能设置一次,而number_of_replicas可以使用索引更新设置API在任何时候被增加或者减少。

1.最初的思路是:
- 先直接使用_bulk API导入ES,
- 借鉴自动导入的mapping,修改mapping
- 再次导入

2.结果使用_bulk API导入ES时报错:
curl -H 'Content-Type: application/x-ndjson' -XPOST '172.21.******:9200/mapper_/automatic_/_bulk?pretty' --data-binary @mag_papers_0.json

 "type" : "illegal_argument_exception",
    "reason" : "Malformed action/metadata line [1], expected START_OBJECT or END                                                                                                                _OBJECT but found [VALUE_STRING]"
  },
  "status" : 400

错误原因:
_bulk API导入ES的JSON文件需要满足一定的格式,每条记录之前,需要有文档ID且每一行\n结束
这里写图片描述
这里有相同问题的讨论
大致讨论结果,两条思路:

  • 使用logstash
  • 老实加上ID

暂时采用老实加ID方法:

先导部分数据,查看使用自动生成mapping

public class JsonToES {
    private static final Logger LOGGER = LoggerFactory.getLogger(OpenClose.class);

    public void jsonToES(File[] files) {
        Map<String, Object> hashMap = EsPropertiesUtils.getConf();
        TransportClient client = OpenClose.getInstance(hashMap);
        int idNum = 0;
        for (File f : files) {
            // 一、绝对路径读取文件
            System.out.println(f.getAbsolutePath());
            File file = new File(f.getAbsolutePath());
            // 二、开始一行一行的写
            try (BufferedReader br = new BufferedReader(new FileReader(file))) {
                BulkRequestBuilder bulkRequest = client.prepareBulk();
                Map<String, Object> valuesMap = new HashMap<>(64, 0.6f);
                String line;
                while ((line = br.readLine()) != null) {
                    bulkRequest.add(client.prepareIndex("mapper", "automatic", Integer.toString(idNum)).setSource(line, XContentType.JSON));
                    valuesMap.clear();
                    if (idNum % 2000 == 0) {
                        System.out.println("++++++++++++++++++++++++++++++++++++");
                        // 三、批量导入
                        bulkRequest.execute().actionGet();
                    }
                    idNum++;
                }
                // 四、导入每个文件最后不足2000条的数据(但是必须得有数据,否则报错: no requests added)
                // 查阅源码可知,可通过拿到父类requests属性,判断其size来解决[子类调用父类方法获得属性]
                if (bulkRequest.request().requests().size() > 0 ) {
                    bulkRequest.get();
                }
                // 五、操作下一个文件
            } catch (IOException e) {
                LOGGER.error(":ERROR:堆栈信息====={}", e.getMessage());
            }
        }
        OpenClose.closeClient(client);
    }

    public static void main(String[] args) {
        JsonToES jsonToES = new JsonToES();
        File file = new File("D:\\JavaStudy\\elasticsearch-try\\src\\main\\resources\\json\\");
        // 目录下只有我需要读取的文件,故不再进行进一步处理
        File[] files = file.listFiles();
        jsonToES.jsonToES(files);
    }
}

修改mapping

“ignore_above”: 256

对超过 ignore_above 的字符串,analyzer 不会进行处理;所以就不会索引起来。【256足够用了,一句话超过256个字还是几乎不太可能的】

我是否需要修改自动生成的以下mapping?

{
  "your_field": {
    "type" "text",
    "fields": {
      "keyword": {
        "type": "keyword",
        "ignore_above": 256
      }
    }
  }
}

As a consequence, it will both be possible to perform full-text search on foo, and keyword search and aggregations using the foo.keyword field.

【这样的mapping,你既可以使用your_field进行全文检索(你搜索“海水产品”,可能会出现”蓝色的海水“,”水产品“),也可以使用your_field.keyword进行精确检索,还可以进行聚合操作】
所以需不需要修改该mapping得视情况而定。

对于确定的,需要精确检索的字段(比如姓名、手机号等)

{
    "your_field": {
        "type": "keyword",
        "index": true
    }
}

对于上面的index属性, 因为在新的定义中我们不需要三种状态(在以前的string定义中可以是analyzed, not_analyzed和no), 所以只简单的定义成了boolean值, 以告知ElasticSearch是否可在该字段上进行搜索.

【对应分词器的功能:ik分词器有ik-smart模式和ik-max_word两种模式,前者为粗粒度的划分,后者为细粒度】

因为我这里的文档全英文,所以我暂时为所有的字段设置忽略大小写

- 安装分词器,设置忽略大小写。相关分词器安装以及部分参数讲解可参考
自带的standard analyzer就是忽略大小写的所以暂时先不安装设置别的

修改副本数

"fields":{
      "addr":{
        "fragment_size":4,
        "number_of_fragments":2,
        "fragmenter": "span"
      }
    }

代码变动记录:

Java

  1. put(“client.transport.sniff”, true)
  2. 使用setSource(String source, XContentType xContentType)而不是setSource(String source),官方
    这里写图片描述

Python

  1. 嗅探参考
es = Elasticsearch(["***:9200", "***:9200"],
                       sniff_on_start=True,
                       sniff_on_connection_fail=True,
                       sniffer_timeout=60)
  1. bulk解析以及跳过出错文档可参考
suc, err = helpers.bulk(es, actions, chunk_size=2000, raise_on_error=False, stats_only=True)
errors += err
success += suc
  1. 【完整示例代码】
#!/Users/wanghai/anaconda2/bin/python
# -*- coding: utf-8 -*-
# @Time    : 2018/6/28 上午9:17
# @Author  : wanghai
# @Site    :
# @File    : import_es.py
# @Software: PyCharm

import time
import os
from elasticsearch import Elasticsearch
from elasticsearch import helpers


def get_files_to_import(path):
    f_list = os.listdir(path)
    files_ = []
    for i in f_list:
        if os.path.splitext(i)[1] == '.json':
            print(i)
            files_.append(i)
    return files_


# es = Elasticsearch('http://elastic:[email protected]:443')
if __name__ == '__main__':

    # 默认不开启嗅探功能 es = Elasticsearch()
    es = Elasticsearch(["***:9200", "***:9200"],
                       sniff_on_start=True,
                       sniff_on_connection_fail=True,
                       sniffer_timeout=60)
    actions = []
    workspace = u'./files/'
    files = get_files_to_import(workspace)

    id_num, errors, success = 0, 0, 0
    for json in files:
        json = workspace + json
        print(time.strftime('%y-%m-%d %H:%M:%S', time.localtime()))
        this_file = open(json)
        for line in this_file:
            action = {
                "_index": "idglab",
                "_type": "pappers",
                "_id": id_num,
                "_source": line
            }
            id_num += 1
            # if id_num == 900000:
            #     print("++++++++++++++++++++++")
            actions.append(action)
            if len(actions) == 2000:
                # print("======================")
                err, suc = helpers.bulk(es, actions, chunk_size=2000, raise_on_error=False, stats_only=True)
                errors += err
                success += suc
                del actions[0:len(actions)]

        if len(actions) > 0:
            suc, err = helpers.bulk(es, actions, chunk_size=2000, raise_on_error=False, stats_only=True)
            errors += err
            success += suc
            del actions[0:len(actions)]
        print("finish process file:%s" % json)

    print(" down!\n success_num:\t %d" % success + " \n errors_num:\t %d" % errors)

注:
当你多次运行相同的代码(同时保证每次运行时每个文档的id不变),那么索引数量将不变。但是如果你不能保证id不变,索引中就会有除了id不同外,大量重复的文档

TODO

  1. Elasticsearch写入性能优化

猜你喜欢

转载自blog.csdn.net/HaixWang/article/details/80816360