ElasticSearch简单使用总结

目录

一、简介

二、基本概念

三、整合

四、es组合查询

五、图形化界面

六、es部分RESTful-api


一、简介

ElasticSearch是一个基于Lucene的搜索服务器,它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful-web接口。ElasticSearch是面向文档的,这意味着它可以存储整个对象或文档,同时索引每个文档的内容使之可以被搜索(每个字段都拥有一个倒排索引),在ElasticSearch中,你可以对文档进行索引、搜索、排序、过滤,这正是它能够执行复杂的全文搜索的原因之一。

上面是官话,选择使用es的一个很大原因就是pg库出现了查询性能瓶颈,由于历史原因,sql需要进行大量的关联查询且耗时较慢,因此需要启用es来存储一张业务宽表,冗余部分数据,数据更新时通过mq同步到es,然后将之前的查询操作全部转往es即可。

二、基本概念

这里记录下ES中常常出现的几个概念

  • 节点(node):一个ES实例就是一个节点,一个机器可以有多个实例
  • 索引(index):即一系列文档的集合,是相关文档存储的地方,一个es节点不建议建太多索引,否则搜索性能会受到影响
  • 分片(shard):ES是分布式搜索引擎,每个索引有一个或多个分片,索引的数据被分配到各个分片上,相当于一桶水,用了N个杯子装;分片的存在有助于横向扩展,N个分片会被尽可能平均地分配在不同的节点上;每个分片都是一个最小工作单元,承载部分数据,lucene实例,完整的建立索引和处理请求的能力
  • 副本(replica):可以理解为备份分片,相应的就有主分片,每个分片可以有多个副本;主分片和备分片不会出现在同一节点,默认情况下一个索引创建5个分片一个备份(即5主+5备=10个分片)
  • 类型(type):在ElasticSearch中,一个索引对象可以存储很多不同用途的对象,而文档类型就可以让我们轻易地区分单个索引中的不同对象,每个文档可以有不同的结构,但要注意不同的文档类型不能为相同的属性设置不同的类型(例如在同一索引中的所有文档类型中,一个叫title的字段必须具有相同的类型);ES6已经不推荐单索引多类型结构,但依然保持兼容,到了ES7时已经完全不支持。
  • 文档(documents):每个类型可以包含多个文档,每个文档包含多个字段(field)

ES存在两个端口号,9200和9300,9200作为http协议,主要用于外部通讯,一般我们使用的时候均为9200;9300作为tcp协议,jar之间就是通过tcp协议通讯,ES集群之间也是通过9300进行通讯。

关于字段的数据类型这里则不作记录,只扯一句,es5.0以后,string字段被拆分成两种新的数据类型:

  • text:根据分词器进行分词,根据分词后的内容建立倒排索引
  • keyword:不分词,直接根据字符串内容建立倒排索引

如果你在建立索引的时候未指定字段的数据类型,es会对该字段进行动态映射,即入库前result字段为数值型,es则映射为long,若后续result变为字符串,es会抛错

三、整合

由于项目需要,这里选取的es版本为6.7.2,记录与springboot的基本整合及使用

首先引入pom文件:

        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
            <version>6.7.2</version>
        </dependency>
        <dependency>
            <groupId>org.elasticsearch</groupId>
            <artifactId>elasticsearch</artifactId>
            <version>6.7.2</version>
        </dependency>

然后在配置文件中加入es的基本配置信息

es.ip=
es.port=9200
es.enabled=true
es.index.name=
es.authentication.active=false // 是否需要密码校验
es.user.name=
es.user.password=

es客户端启动代码

    private RestHighLevelClient client;
    private BulkProcessor bulkProcessor;

    @PostConstruct
    public void establishElasticConnection() throws ServiceException {
        if (!appEnv.isEsEnabled()){
            return;
        }

        try {
            if (appEnv.isEsAuthActive()){
                final CredentialsProvider credentialsProvider =
                        new BasicCredentialsProvider();
                credentialsProvider.setCredentials(AuthScope.ANY,
                        new UsernamePasswordCredentials(appEnv.getEsUserName(), appEnv.getEsPassword()));

                RestClientBuilder builder = RestClient.builder(
                        new HttpHost(appEnv.getElasticSearchIp(), appEnv.getElasticSearchPort()))
                        .setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {
                            @Override
                            public HttpAsyncClientBuilder customizeHttpClient(
                                    HttpAsyncClientBuilder httpClientBuilder) {
                                return httpClientBuilder
                                        .setDefaultCredentialsProvider(credentialsProvider);
                            }
                        });
                client = new RestHighLevelClient(builder);
            } else {
                client = new RestHighLevelClient(
                        RestClient.builder(
                                new HttpHost(appEnv.getElasticSearchIp(), appEnv.getElasticSearchPort(), "http")));
            }

            // 初始化批量处理器
            initBulkProcessor();

            GetIndexRequest getIndexRequest = new GetIndexRequest(appEnv.getEsIndexName());
            boolean result = client.indices().exists(getIndexRequest, RequestOptions.DEFAULT);
            if (!result) {
                // 指定索引结构并创建
               createIndexStructure(appEnv.getEsIndexName());
            }

        } catch (ElasticsearchException ex){
            throw new ServiceException(ErrorCode.INTERNAL_SERVER_ERROR, new String[] {ex.getMessage()});
        } catch (Exception ex){
            logger.error("Fail to establish ES connection, error:{}", ex.getMessage());
        }
    }


private void createIndexStructure(String indexName){
        CreateIndexRequest request = new CreateIndexRequest(indexName);

        request.settings(Settings.builder()
                .put("index.number_of_shards", 1)
                .put("index.number_of_replicas", 1)
        );

        String[] booleanFields = Constant.ES_BOOLEAN_FIELDS;
        String[] keywordFields = Constant.ES_KEYWORD_TYPE_FIELDS;
        String[] textFields = Constant.ES_TEXT_TYPE_FIELDS;

        Map<String, Object> properties = new HashMap<>();
        Map<String, Object> mapping = new HashMap<>();

        assembleBooleanFields(booleanFields, properties);
        assembleKeywordFields(keywordFields, properties);
        assembleTextFields(textFields, properties);

        mapping.put("properties", properties);
        request.mapping(indexName, mapping);

        try {
            CreateIndexResponse createIndexResponse = client.indices().create(request, RequestOptions.DEFAULT);
            if (!createIndexResponse.isAcknowledged()) {
                logger.error("Aux costTracking Item Index Creation Failed, createIndexResponse:{}", createIndexResponse.toString());
            }
        } catch (IOException ioEx){
            logger.info("Aux costTracking Item Index Creation Failed");
        }
    }

// 标识该字段既是text又是keyword,es的默认映射也是这种类型
private void assembleTextFields(String[] stringFields, Map<String, Object> properties){
        for (String fieldName : stringFields) {
            Map<String, Object> fieldMeta = new HashMap<>();
            Map<String, Object> Fields = new HashMap<>();
            Map<String, Object> KeyWord = new HashMap<>();

            KeyWord.put(Constant.ES_KEYWORD_TYPE, Constant.ES_KEYWORD_KEYWORD);
            KeyWord.put(Constant.ES_KEYWORD_IGNORE_ABOVE, 256);

            Fields.put(Constant.ES_KEYWORD_KEYWORD, KeyWord);

            fieldMeta.put(Constant.ES_KEYWORD_TYPE, Constant.ES_KEYWORD_TEXT);
            fieldMeta.put(Constant.ES_KEYWORD_FIELDS, Fields);

            properties.put(fieldName, fieldMeta);
        }
    }

// 批量处理器
private void initBulkProcessor() {
        BulkProcessor.Listener listener = new BulkProcessor.Listener() {
            @Override
            public void beforeBulk(long executionId, BulkRequest request) {
                logger.info("Try to bulk {} data", request.numberOfActions());
            }

            @Override
            public void afterBulk(long executionId, BulkRequest request, BulkResponse response) {
                logger.info("{} data bulk success", request.numberOfActions());
            }

            @Override
            public void afterBulk(long executionId, BulkRequest request, Throwable failure) {
                logger.error("{} data bulk failed, reason:{}", request.numberOfActions(), failure);
            }
        };
        BiConsumer<BulkRequest, ActionListener<BulkResponse>> bulkConsumer =
                (request, bulkListener) ->
                        client.bulkAsync(request, RequestOptions.DEFAULT, bulkListener);
        bulkProcessor = BulkProcessor.builder(bulkConsumer, listener)
                .setBulkActions(10000)  // 每添加1w条数据执行一次操作
                .setBulkSize(new ByteSizeValue(5, ByteSizeUnit.MB))  // 每达到5m请求size执行一次操作
                .setFlushInterval(TimeValue.timeValueSeconds(5))  // 每5s执行一次操作
                .setConcurrentRequests(1)  // 允许并发
                .setBackoffPolicy(BackoffPolicy.exponentialBackoff(TimeValue.timeValueMillis(100), 3))  // 100ms后执行,最大请求3次
                .build();
    }

上面是采用api的方式指定字段的数据类型,如果大部分索引的数据结构均相似,则可以采用索引模板的方式创建,这里不再演示,上面的映射类型如下:

              "properties": {
               "DstIp": {
                  "type": "text",
                  "fields": {
                     "keyword": {
                        "type": "keyword",
                        "ignore_above": 256
                     }
                  }
               },

关于es的增删改查api则参考文档即可,当需要批量处理时建议使用上面的 BulkProcessor ,而不是 BulkRequest,关于查询时所用到的部分逻辑处理下面会介绍

es api文档地址:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/6.7/java-rest-high-document-index.html

四、es组合查询

这也是我们选择使用es的最大原因之一:优秀的搜索性能。这里只介绍es的组合查询:bool查询。bool查询对应lucene中的BooleanQuary,它由一个或多个字句组成,每个子句都有特定的类型,先介绍组合查询的四种逻辑组合:

  • must:返回的文档必须满足must子句的条件,并且参与计算分值
  • filter:返回的文档必须满足filter子句的条件。但是不会像must一样,参与计算分值
  • should:返回的文档可能满足should子句的条件。在一个bool查询中,如果没有must或者filter,有一个或者多个should子句,那么只要满足一个就可以返回,minimum_should_match参数定义了至少满足几个子句
  • must_nout:返回的文档必须不满足must_not定义的条件

使用上面四种组合基本可以满足任何复杂查询,如果一个查询既有filter又有should,那么至少包含一个should子句,下面介绍具体的查询方式:(下文括号中的类名对应es的api)

  • match(matchQuery):匹配查询,在执行查询时,会对搜索词进行分词,只要文档中有匹配到任何一个分词结果则认为匹配
  • match_phrase(matchPhraseQuery):短语匹配查询,搜索词不会被分词,而是直接以一个短语的形式查询,只要字段的分词结果顺序连接在一起与搜索词匹配则认为满足
  • term(termQuery):精确查询,不会对搜索词进行分词,而是作为一个整体与目标字段进行匹配,若完全匹配,则可查询到
  • terms(termsQuery):查找多个精确值,term查询对于查找单个值非常有用,但通常我们可能想搜索多个值,terms是包含操作,满足其一即可
  • range(rangeQuery):范围查询,一般用于数字和日期字段
  • exists(existsQuery):非空查询,es在对文档进行序列化时,空值默认不进行序列化,如果想查询某个字段不为空的数据,则可以使用exists进行查询
  • wildcard(wildcardQuery):模糊查询,类似关系数据库中的like,尽量使用match或match_phrase匹配(有索引)

termQuery与matchPhraseQuery的区别:前者不会考虑分词内容,只会按照搜索词完全匹配字段内容,若相等则满足;后者会将对应字段分词后的各情况进行组合,若能组合成搜索词且间隔为0,则认为满足

五、图形化界面

平常开发建议直接使用chrome的head插件即可,简单易用,形如:

或者使用es的配套工具kibana,mac下可直接通过brew进行下载安装:(注意kibana的版本与es的版本需要匹配,我这里是6.7.0的kibana)

brew install kibana

注意kibana是依赖node.js的,kibana默认连接的es地址是本地,可以进到kibana的安装目录修改其配置文件,然后直接在命令行输入kibana即可启动,启动成功如下:

然后访问localhost:5601即可

不同版本的kibana界面还都不一样,因此只要你知道基本的api查询就行

六、es部分RESTful-api

1、删除索引:curl -XDELETE http://xxx:9200/aux_tracking_item-team4

支持正则,如 curl -XDELETE http://xxx:9200/*_tracking_item-team4

2、查看某个字段数据的分词结果:GET /${index}/${type}/${id}/_termvectors?fields=${fields_name}

3、测试分词接口:post _analyze

{
  "analyzer": "standard",
  "text": "hello world"
}

猜你喜欢

转载自blog.csdn.net/m0_38001814/article/details/109415240