ES从入门到实战

发布于:2023-01-02 ⋅ 阅读:(285) ⋅ 点赞:(0)

分布式搜索引擎

1.了解elasticsearch

1.1 elasticsearch的作用

elasticsearch是一款非常强大的开源搜索引擎,具备非常多强大功能,可以帮助我们从海量数据中快速找到需要的内容。

例如:

  • 在GitHub搜索代码

     

  • 在电商网站搜索商品

     

  • 在百度搜索答案

     

1.2 ELK技术栈

elasticsearch结合kibana、Logstash、Beats,也就是elastic stack(ELK)。被广泛应用在日志数据分析、实时监控等领域:

 

而elasticsearch是elastic stack的核心,负责存储、搜索、分析数据。

 

1.3 elasticsearch和luncene

elasticsearch底层是基于lucene来实现的。

Lucene是一个Java语言的搜索引擎类库,是Apache公司的顶级项目,由DougCutting于1999年研发。官网地址:Apache Lucene - Welcome to Apache Lucene/ 。

1.4 总结

 

什么是elasticsearch?

  • 一个开源的分布式搜索引擎,可以用来实现搜索、日志统计、分析、系统监控等功能

什么是elastic stack(ELK)?

  • 是以elasticsearch为核心的技术栈,包括beats、Logstash、kibana、elasticsearch

什么是Lucene?

  • 是Apache的开源搜索引擎类库,提供了搜索引擎的核心API。

2.倒排索引

倒排索引的概念是基于MySQL这样的正向索引而言的。

2.1正向索引

那么什么是正向索引呢?例如给下表(tb_goods)中的id创建索引:

 

如果是根据id查询,那么直接走索引,查询速度非常快。

但如果是基于title做模糊查询,只能是逐行扫描数据,流程如下:

1)用户搜索数据,条件是title符合"%手机%"

2)逐行获取数据,比如id为1的数据

3)判断数据中的title是否符合用户搜索条件

4)如果符合则放入结果集,不符合则丢弃。回到步骤1

逐行扫描,也就是全表扫描,随着数据量增加,其查询效率也会越来越低。当数据量达到数百万时,就是一场灾难。

2.2 倒排索引

倒排索引中有两个非常重要的概念:

  • 文档(Document):用来搜索的数据,其中的每一条数据就是一个文档。例如一个网页、一个商品信息

  • 词条(Term):对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条。例如:我是中国人,就可以分为:我、是、中国人、中国、国人这样的几个词条

创建倒排索引是对正向索引的一种特殊处理,流程如下:

  • 将每一个文档的数据利用算法分词,得到一个个词条

  • 创建表,每行数据包括词条、词条所在文档id、位置等信息

  • 因为词条唯一性,可以给词条创建索引,例如hash表结构索引

如图:

 

倒排索引的搜索流程如下(以搜索"华为手机"为例):

1)用户输入条件"华为手机"进行搜索。

2)对用户输入内容分词,得到词条:华为手机

3)拿着词条在倒排索引中查找,可以得到包含词条的文档id:1、2、3。

4)拿着文档id到正向索引中查找具体文档。

如图:

 

虽然要先查询倒排索引,再查询倒排索引,但是无论是词条、还是文档id都建立了索引,查询速度非常快!无需全表扫描。

2.3 正向和倒排总结

那么为什么一个叫做正向索引,一个叫做倒排索引呢?

  • 正向索引是最传统的,根据id索引的方式。但根据词条查询时,必须先逐条获取每个文档,然后判断文档中是否包含所需要的词条,是根据文档找词条的过程

  • 倒排索引则相反,是先找到用户要搜索的词条,根据词条得到保护词条的文档的id,然后根据id获取文档。是根据词条找文档的过程

是不是恰好反过来了?

那么两者方式的优缺点是什么呢?

正向索引

  • 优点:

    • 可以给多个字段创建索引

    • 根据索引字段搜索、排序速度非常快

  • 缺点:

    • 根据非索引字段,或者索引字段中的部分词条查找时,只能全表扫描。

倒排索引

  • 优点:

    • 根据词条搜索、模糊搜索时,速度非常快

  • 缺点:

    • 只能给词条创建索引,而不是字段

    • 无法根据字段做排序

3 es的一些概念

3.1 文档和字段

elasticsearch是面向文档(Document)存储的,可以是数据库中的一条商品数据,一个订单信息。文档数据会被序列化为json格式后存储在elasticsearch中:

 

 

而Json文档中往往包含很多的字段(Field),类似于数据库中的列。

3.2 索引和映射

索引(Index),就是相同类型的文档的集合。

例如:

  • 所有用户文档,就可以组织在一起,称为用户的索引;

  • 所有商品的文档,可以组织在一起,称为商品的索引;

  • 所有订单的文档,可以组织在一起,称为订单的索引;

  •  

因此,我们可以把索引当做是数据库中的表。

数据库的表会有约束信息,用来定义表的结构、字段的名称、类型等信息。因此,索引库中就有映射(mapping),是索引中文档的字段约束信息,类似表的结构约束。

映射注释概述使用MappingElasticsearchConverter元数据来驱动对象到文档的映射。元数据取自可以注释的实体属性

可以使用以下注释:

@Document:在类级别应用以指示该类是映射到数据库的候选对象。其中比较重要的属性是:indexName:存储此实体的索引的名称。这可以包含一个 SpEL 模板表达式,如"log-#{T(java.time.LocalDate).now().toString()}"type:映射类型。如果未设置,则使用类的小写简单名称。(自 4.0 版起已弃用)shards:索引的分片数。replicas:索引的副本数。refreshIntervall:索引的刷新间隔。用于创建索引。默认值为“1s”indexStoreType:索引的索引存储类型。用于创建索引。默认值为“1s”createIndex: 标记是否在存储库引导时创建索引。默认值为true。versionType:版本管理的配置。默认值为外部。

@Id:应用于字段级别以标记用于标识目的的字段。

@Transient:默认情况下,所有字段在存储或检索时都映射到文档,此注释不包括该字段。@PersistenceConstructor:标记一个给定的构造函数–甚至是一个包保护的构造函数–再从数据库中实例化对象时使用。构造函数参数按名称映射到检索到的Document中的键值。

@Field:应用于字段级别并定义字段的属性,大部分属性映射到各自的Elasticsearch Mapping定义(以下列表不完整,查看注释Javadoc以获得完整参考):name:将在 Elasticsearch 文档中表示的字段名称,如果未设置,则使用 Java 字段名称。type:字段类型,可以是Text、Keyword、Long、Integer、Short、Byte、Double、Float、Half_Float、Scaled_Float、Date、Date_Nanos、Boolean、Binary、Integer_Range、Float_Range、Long_Range、Double_Range、Date_Range、Ip_Range、Object 之一, 嵌套, Ip, TokenCount, Percolator, Flattened, Search_As_You_Type。format以及Date类型pattern的定义。store: 标记原始字段值是否应该存储在 Elasticsearch 中,默认值为false。analyzer, searchAnalyzer,normalizer用于指定自定义分析器和规范器。

@GeoPoint: 将字段标记为geo_point数据类型。如果字段是类的实例,则可以省略GeoPoint。

3.3 mysql与elasticsearch

我们统一的把mysql与elasticsearch的概念做一下对比:

MySQL Elasticsearch 说明
Table Index 索引(index),就是文档的集合,类似数据库的表(table)
Row Document 文档(Document),就是一条条的数据,类似数据库中的行(Row),文档都是JSON格式
Column Field 字段(Field),就是JSON文档中的字段,类似数据库中的列(Column)
Schema Mapping Mapping(映射)是索引中文档的约束,例如字段类型约束。类似数据库的表结构(Schema)
SQL DSL DSL是elasticsearch提供的JSON风格的请求语句,用来操作elasticsearch,实现CRUD

是不是说,我们学习了elasticsearch就不再需要mysql了呢?

并不是如此,两者各自有自己的擅长支出:

  • Mysql:擅长事务类型操作,可以确保数据的安全和一致性

  • Elasticsearch:擅长海量数据的搜索、分析、计算

因此在企业中,往往是两者结合使用:

  1. 对安全性要求较高的写操作,使用mysql实现

  2. 对查询性能要求较高的搜索需求,使用elasticsearch实现

  3. 两者再基于某种方式,实现数据的同步,保证一致性(可以使用mq、binlog、logstash、beats,根据项目要求选择即可)

 

 

4 索引库操作

4.1mapping映射属性

mapping是对索引库中文档的约束,常见的mapping属性包括:

  • type:字段数据类型,常见的简单类型有:

    • 字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)

    • 数值:long、integer、short、byte、double、float、

    • 布尔:boolean

    • 日期:date

    • 对象:object

  • index:是否创建索引,默认为true

  • analyzer:使用哪种分词器

  • properties:该字段的子字段

  • {
        "age": 21,
        "weight": 72.1,
        "isMarried": false,
        "info": "江苏汇运晶达",
        "email": "huiyun@qq.com",
        "score": [99.1,99.5,98.9],
        "name": {
            "firstName": "汇",
            "lastName": "运"
        }
    }

    对应的每个字段映射(mapping):

  • age:类型为 integer;参与搜索,因此需要index为true;无需分词器

  • weight:类型为float;参与搜索,因此需要index为true;无需分词器

  • isMarried:类型为boolean;参与搜索,因此需要index为true;无需分词器

  • info:类型为字符串,需要分词,因此是text;参与搜索,因此需要index为true;分词器可以用ik_smart

  • email:类型为字符串,但是不需要分词,因此是keyword;不参与搜索,因此需要index为false;无需分词器

  • score:虽然是数组,但是我们只看元素的类型,类型为float;参与搜索,因此需要index为true;无需分词器

  • name:类型为object,需要定义多个子属性

    • name.firstName;类型为字符串,但是不需要分词,因此是keyword;参与搜索,因此需要index为true;无需分词器

    • name.lastName;类型为字符串,但是不需要分词,因此是keyword;参与搜索,因此需要index为true;无需分词器

4.2索引库的CRUD

  • 创建索引库:PUT /索引库名

  • 查询索引库:GET /索引库名

  • 删除索引库:DELETE /索引库名

  • 添加字段:PUT /索引库名/_mapping

4.3 文档操作

文档操作有哪些?

  • 创建文档:POST /{索引库名}/_doc/文档id { json文档 }

  • 查询文档:GET /{索引库名}/_doc/文档id

  • 删除文档:DELETE /{索引库名}/_doc/文档id

  • 修改文档:

    • 全量修改:PUT /{索引库名}/_doc/文档id { json文档 }

    • 增量修改:POST /{索引库名}/_update/文档id { "doc": {字段}}

 

5 springboot整合ES

目前所使用过的有两种方式,当然还有其它的。

5.1 Rest client

Rest client分为:Java Low Level Rest Client和Java High Level Rest Client .

Rest Client:官方推荐使用,所以我们采用这个方式,这个分为两个Low Level REST Client和High Level REST Client,Low Level REST Client是早期出的API比较简陋了,还需要自己去拼写Query DSL,High Level REST Client使用起来更好用,更符合面向对象的感觉。

pom.xml

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    </dependency>
</dependencies>

ElasticSearchConfig.java

@Configuration
public class ElasticSearchConfig {

    @Bean
    public RestHighLevelClient restHighLevelClient(){
        return new RestHighLevelClient(
               RestClient.builder(new HttpHost("127.0.0.1", 9200, "http")));
    }
}

Mapping.java

public class UserIndexConstants {
    public static final String MAPPING_TEMPLATE = "{\n" +
            "  \"mappings\": {\n" +
            "    \"properties\": {\n" +
            "      \"name\": {\n" +
            "        \"type\": \"keyword\"\n" +
            "      },\n" +
            "      \"age\": {\n" +
            "        \"type\": \"keyword\"\n" +
            "    }\n" +
            "  }\n" +
            "}";
}

RestHighTest.java

@RunWith(SpringRunner.class)
@SpringBootTest
public class RestHighTest {
 
    @Resource
    private RestHighLevelClient restHighLevelClient;
    
/**************************************创建索引*****************************************/  
    @Test
    public void CreateIndex() throws IOException{
        //创建索引的请求
        CreateIndexRequest request = new CreateIndexRequest("index_user");
        //添加索引的mapping规则
        request.source(UserIndexConstants.MAPPING_TEMPLATE,XContentType.JSON);
        //发送请求
        restHighLevelClient.indices().create(request,RequestOptions.DEFAULT);
    }
 /*************************************测试索引是否存在**********************************/
    @Test
    public void ExitIndex() throws IOException{
        // 获取索引的请求
        GetIndexRequest request = new GetIndexRequest("index_user");
        // 执行请求
    	restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
    }
 /*************************************测试删除索引*************************************/ 
    @Test
    public void DeleteIndex() throws IOException {
        // 删除索引的请求
        DeleteIndexRequest nan_index = new DeleteIndexRequest("index_user");
        // 执行删除的请求
        restHighLevelClient.indices().delete(nan_index,RequestOptions.DEFAULT);
	}
/*************************************测试文档的添加*************************************/ 
    @Test
    public void testCreateDoc() throws IOException {
        // 准备好数据
        User user = new User("小杰", 23);
        // 创建好index请求
        IndexRequest indexRequest = new IndexRequest("index_user");
        // 设置索引
        indexRequest.id("1");
        // 设置超时时间(默认)
        indexRequest.timeout(TimeValue.timeValueSeconds(5));
        // 往请求中添加数据
        indexRequest.source(JSON.toJSONString(user), XContentType.JSON);
        //执行添加请求
        IndexResponse indexResponse = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
        System.out.println(indexResponse);
    }
/*************************************测试文档的修改*******************************/
        // 测试文档的更新(id存在就是更新,id不存在就是添加)(用下面这个方法更新时,是全局更新,就是说里面的字段全部被覆盖新对象里没有的字段就没有了)
    @Test
    public void testUpdateDoc() throws IOException {
        User user = new User("xiaoer", 18);
        IndexRequest indexRequest = new IndexRequest("index_user");
        indexRequest.id("2");
        indexRequest.source(JSON.toJSONString(user),XContentType.JSON);
        IndexResponse indexResponse = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
        System.out.println(indexResponse);
    }
/*************************************文档数据的更新(局部更新)***********************/
    // 测试文档的更新(id存在就是更新,id不存在就是添加)(用下面这个方法更新时,是局部更新,就是说只会覆盖其中有的字段)
    @Test
    public void testUpdateBetterDoc() throws IOException {
        //准备好修改的数据
        User user = new User("xiaoer", 18);
        // 创建更新请求
        UpdateRequest updateRequest = new UpdateRequest("index_user", "1");
        // 把要更新的数据装进去
        updateRequest.doc(JSON.toJSONString(user),XContentType.JSON);
        // 执行更新语句
        UpdateResponse updateResponse = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
        System.out.println(updateResponse);
    }
/*************************************文档数据的删除************************/
    @Test
    public void testDelDoc() throws IOException {
        DeleteRequest deleteRequest = new DeleteRequest("index_user","2");
        DeleteResponse deleteResponse = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
        System.out.println(deleteResponse);
    }
/*************************************根据id获取文档数据(简单)************************/
    @Test
    public void testGetDoc() throws IOException {
        GetRequest getRequest = new GetRequest("index_user");
        getRequest.id("1");
        GetResponse getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
        System.out.println(getResponse);
    }
 /*************************************批量添加文档数据************************/ 
	// 测试文档批量添加(添加会了,批量删除、更新、修改是一样的)
    @Test
    public void testBulkAdd() throws IOException {
        // 准备要添加的数据
        List<User> users = new ArrayList<>();
        users.add(new User("xiaofei",25));
        users.add(new User("xiaohua",22));
        users.add(new User("xiaoer",23));
        users.add(new User("xiaoge",22));
        users.add(new User("xiaomei",23));
        // 创建批量请求
        BulkRequest bulkRequest = new BulkRequest();
        // 利用循环将每一个add请求添加到bulkRequest请求中
        for (int i = 0; i < users.size(); i++) {
            IndexRequest indexRequest = new IndexRequest("index_user").id(""+i);
            indexRequest.source(JSON.toJSONString(users.get(i)),XContentType.JSON);
            bulkRequest.add(indexRequest);
        }
        // 执行批量请求
        BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
        System.out.println(bulkResponse);
    }
 /****************************文档数据的查询(带条件)重点(后面的都很重要)******************/ 
    // 测试文档列表的查询(带条件)
    // 这是ElasticSearch最重要的地方
    @Test
    public void testGetListDoc() throws IOException {
        SearchRequest searchRequest = new SearchRequest("index_user");
        // 构建搜索builder
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        // 构建查询条件(查询所有)
        //  MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();

        // 构建查询条件(精确匹配)
        TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery("name", "xiaomei");

        // 把查询条件设置给搜索builder
        searchSourceBuilder.query(termsQueryBuilder);

        // 设置分页查询(跟sql语句的limit一样)
        searchSourceBuilder.from(0); // 开始下标(当前页码-1)*每页显示条数
        searchSourceBuilder.size(3); // 要查多少个

        // 设置排序规则
        searchSourceBuilder.sort("age",SortOrder.DESC);

        // 把所有条件设置给查询请求
        searchRequest.source(searchSourceBuilder);

        // 开始查询
        SearchResponse searchResponse =
                restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        // 查看结果(结果很多,所以循环)
        SearchHits hits = searchResponse.getHits();
        for (SearchHit hit: hits) {
            System.out.println(hit.getSourceAsString());
        }
    }
/**********字段过滤(当我们想要查询的结果中的字段不是所有的,只有表中的部分字段)*************/  
    // 测试过滤查询出来的字段(也就是当我们不想把表中所有的字段查出来)
    @Test
    public void testFilterDoc() throws IOException {
        // 构建搜索查询请求
        SearchRequest searchRequest = new SearchRequest().indices("index_user");

        // 构建查询条件builder
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        // 构建真正的查询条件(这里是全部查询)
        MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();

        // 把查询条件设置给builder
        searchSourceBuilder.query(matchAllQueryBuilder);

        // 设置过滤字段
        String[] excludes = {};
        String[] includes = {"name"};
        searchSourceBuilder.fetchSource(includes,excludes);

        // 把所有的查询条件builder设置给查询请求
        searchRequest.source(searchSourceBuilder);

        //执行请求
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

        //打印结果
        SearchHits hits = searchResponse.getHits();
        for (SearchHit hit:hits) {
            System.out.println(hit.getSourceAsString());
        }
    }
/*******************************多条件查询(也叫组合查询)*****************************/
    // 多条件查询,也叫组合查询
    @Test
    public void testBoolqueryDoc() throws IOException {
        // 构建查询请求
        SearchRequest searchRequest = new SearchRequest("index_user");

        // 构建搜索条件builder
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        // 构建多条件builder
        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
        // 在多条件builder中设置满足条件
        // must 就是必须满足这个条件(相当于mysql中column = 值)
        // mustnot 就是必须不满足这个条件(相当于mysql中column!=值)
        // should 就是或者的意思(相当于mysql中的or)
        boolQueryBuilder.must(QueryBuilders.termsQuery("name","xiaofei"));
//        boolQueryBuilder.must(QueryBuilders.termsQuery("age","24"));
        boolQueryBuilder.should(QueryBuilders.termsQuery("age","24"));
        boolQueryBuilder.should(QueryBuilders.termsQuery("age","28"));
        // 把多条件查询条件放到builder中
        searchSourceBuilder.query(boolQueryBuilder);

        // 把所有搜索条件设置到查询请求中
        searchRequest.source(searchSourceBuilder);
        // 执行请求
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        // 打印结果
        SearchHits hits = searchResponse.getHits();
        for(SearchHit hit :hits){
            System.out.println(hit.getSourceAsString());
        }
    }
/*******************************范围查询(就是某个字段在什么范围内)*****************************/
    // 范围查询
    @Test
    public void testRangeDoc() throws IOException {
        // 构建查询请求
        SearchRequest searchRequest = new SearchRequest("index_user");

        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        // 设置到范围的字段
        RangeQueryBuilder rangeQueryBuilder = new RangeQueryBuilder("age");
        // 设置范围()gte就是大于等于
        rangeQueryBuilder.gte(24);
        rangeQueryBuilder.lte(30);
        // 把范围查询设置到条件中
        searchSourceBuilder.query(rangeQueryBuilder);
        searchRequest.source(searchSourceBuilder);
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        SearchHits hits = searchResponse.getHits();
        for(SearchHit hit :hits){
            System.out.println(hit.getSourceAsString());
        }
    }
/*******************************模糊查询*************************************/
    // 模糊查询
    @Test
    public void testLikeDoc() throws IOException {
        SearchRequest searchRequest = new SearchRequest("index_user");
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        FuzzyQueryBuilder fuzzyQueryBuilder = QueryBuilders.fuzzyQuery("name", "xiao").fuzziness(Fuzziness.TWO);
        searchSourceBuilder.query(fuzzyQueryBuilder);
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        SearchHits hits = searchResponse.getHits();
        for(SearchHit hit :hits){
            System.out.println(hit.getSourceAsString());
        }
    }
/*******************************高亮查询*************************************/
    //高亮查询
    @Test
    public void testHighLightDoc() throws IOException {
        // 构建搜索请求
        SearchRequest searchRequest = new SearchRequest("index_user");

        // 构建搜索条件构造器(也就是总的搜索条件)
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        // 构建单独的一个高亮构建器
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        // 设置高亮字段
        highlightBuilder.preTags("<font color='red'>"); //前缀
        highlightBuilder.postTags("</font>");   // 后缀
        highlightBuilder.field("name");
        // 把单独的高亮构建器设置给总构建器
        searchSourceBuilder.highlighter(highlightBuilder);
        searchSourceBuilder.query(QueryBuilders.matchAllQuery());
        // 把总的搜索条件给到搜索请求中
        searchRequest.source(searchSourceBuilder);
        // 执行请求
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        // 打印结果
        SearchHits hits = searchResponse.getHits();
        for(SearchHit hit :hits){
            System.out.println(hit.getSourceAsString());
            // 获取对应的高亮域
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            System.out.println(highlightFields);
            // 获取对应的高亮字段
            HighlightField highlightField = highlightFields.get("name");
            if(highlightField != null) {
                // 拿到高亮字段的文本域
                Text[] texts = highlightField.getFragments();
                String name = "";
                for (Text text : texts) {
                    name += text;
                    // 打印高亮字段
                    System.out.println(name);
                }
            }
        }
    }
/******************************最大值、平均值、最小值查询*************************************/
    // 最大值、平均值、最小值
    @Test
    public void testAggraDoc() throws IOException {
        SearchRequest searchRequest = new SearchRequest("index_user");
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        // 构建一个最大值builder
        MaxAggregationBuilder maxAggregationBuilder = AggregationBuilders.max("MAXAGE").field("age");
        // 把最大值builder设置给总查询条件
        searchSourceBuilder.aggregation(maxAggregationBuilder);

        searchRequest.source(searchSourceBuilder);
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        SearchHits hits = searchResponse.getHits();
        for(SearchHit hit : hits){
            System.out.println(hit.getSourceAsString());
        }
    }
/******************************分组查询*************************************/
    // 分组查询
    @Test
    public void testAggraGroupDoc() throws IOException {
        SearchRequest searchRequest = new SearchRequest("index_user");
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        // 构建一个分组builder
        // terms里面的参数是给分组取的名字、后面field是要分组的字段
        TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms("AGEGROUP").field("age");
        // 把分组builder设置给总查询条件
        searchSourceBuilder.aggregation(termsAggregationBuilder);

        searchRequest.source(searchSourceBuilder);
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        SearchHits hits = searchResponse.getHits();
        for(SearchHit hit : hits){
            System.out.println(hit.getSourceAsString());
        }
    }

}

用过es的朋友应该都了解es的分页模式总共有两种,一种是from,size模式,也就是常说的浅分页,这种分页模式使用起来很简单,和mysql的十分类似,但是这种方式的最大弊端是,在集群模式下,如果查询的分页数很深,很容易造成查询效率极慢甚至内存的异出的问题,解决这个问题就要提到深分页,深分页的大概思路是每一页查询会给你返回一个scrollId,类似于一个游标,记录本次查询的位置,下次使用这个有游标再去查下一页的数据,所有不会出现跨度大,查询数据多导致溢出的问题,但是它也有一个弊端就是不支持跳页。还有就是es默认的查询(也就是不加from,size)是会返回第一页10条数据,当然这里可以设置size的长度最大为10000。

SearchSourceBuilder sourceBuilder = new SearchSourceBuilder().size(10000);

浅分页

 private List<SearchHit> docSearch(Date time, String title) {
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder builder = QueryBuilders.boolQuery();
        builder.must(QueryBuilders.matchAllQuery())
                .filter(QueryBuilders.rangeQuery("publish_time").gt(time.getTime()));
        try {
            searchSourceBuilder.query(builder)
                    .sort("id", SortOrder.ASC)
                    .sort("publish_time", SortOrder.ASC)
                    .from(0)
                    .size(10);
            SearchRequest searchRequest = new SearchRequest("索引名称").source(searchSourceBuilder);
            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            SearchHit[] hits = searchResponse.getHits().getHits();
            if (hits.length > 0) {
                return Arrays.asList(hits);
            } else {
                return null;
            }
        } catch (IOException e) {
            log.error("doc分页查询异常:{} ", e);
        }
        return null;
    }     

from+size查询在10000-50000条数据(1000到5000页)以内的时候还是可以的,但是如果数据过多的话,就会出现深分页问题。为了这个问题,es提出了scroll滚动查询方式scroll滚动搜索,会在第一次搜索的时候,保存一个当下的快照。之后只会基于该快照提供数据搜索。在这个期间数据如果发生变动,是不会让用户看到的。推荐非实时处理大量数据的情况可以使用不适用于有跳页的情景 .

	@Autowired
    private RestHighLevelClient restHighLevelClient;
 
    public void scrollDemo() {
        //构造查询条件
        SearchRequest searchRequest = new SearchRequest("索引库");
        SearchSourceBuilder builder = new SearchSourceBuilder();
        //设置查询超时时间
        Scroll scroll = new Scroll(TimeValue.timeValueMinutes(1L));
        builder.query(QueryBuilders.rangeQuery("字段").gte("开始时间").lte("结束时间"));
        //设置最多一次能够取出10000笔数据,从第10001笔数据开始,将开启滚动查询  PS:滚动查询也属于这一次查询,只不过因为一次查不完,分多次查
        builder.size(10000);
        searchRequest.source(builder);
        //将滚动放入
        searchRequest.scroll(scroll);
        SearchResponse searchResponse = null;
        try {
            searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("查询索引库失败", e.getMessage(), e);
        }
        SearchHits hits = searchResponse.getHits();
        //记录要滚动的ID
        String scrollId = searchResponse.getScrollId();
 
        //TODO 对结果集的处理
 
        //滚动查询部分,将从第10001笔数据开始取
        SearchHit[] hitsScroll = hits.getHits();
        while (hitsScroll != null && hitsScroll.length > 0 ) {
            //构造滚动查询条件
            SearchScrollRequest searchScrollRequest = new SearchScrollRequest(scrollId);
            searchScrollRequest.scroll(scroll);
            try {
                //响应必须是上面的响应对象,需要对上一层进行覆盖
                searchResponse = restHighLevelClient.scroll(searchScrollRequest, RequestOptions.DEFAULT);
            } catch (IOException e) {
                log.error("滚动查询失败",e.getMessage(),e);
            }
            scrollId = searchResponse.getScrollId();
            hits = searchResponse.getHits();
            hitsScroll = hits.getHits();
 
           //TODO 同上面完全一致的结果集处理
        }
 
        //清除滚动,否则影响下次查询
        ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
        clearScrollRequest.addScrollId(scrollId);
        ClearScrollResponse clearScrollResponse = null;
        try {
            clearScrollResponse = restHighLevelClient.clearScroll(clearScrollRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("滚动查询清除失败",e.getMessage(),e);
        }
        //清除滚动是否成功
        boolean succeeded = clearScrollResponse.isSucceeded();
    }

5.2 Spring Data Repository

pom.xml

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    </dependency>
 </dependencies>

application.properties


spring:
  application:
    name: search
  data:
    elasticsearch:
      cluster-name: my-application
      cluster-nodes: 192.168.10.12:9300

定义一个User实体类


@Document(indexName = "content", type = "doc")
@Data
public class User {
    @Id
    @Field(type = FieldType.Auto)
    private String id;
    @Field(analyzer = "ik_max_word",type = FieldType.Text)
    private String name;
    @Firld(type = FieldType.Keyword)
    private Integer age;
 }

自定义UserRepository并集成ElasticsearchRepository

 
public interface UserRepository extends ElasticsearchRepository<User, String> {
 
    User findByName(String name);
}

定义UserService.java

public interface UserService {
 
    void save(User user);
 
    void update(User user);
 
    User getById(String id);
 
    User getByName(String name);
 
    void delete(String id);
}

定义UserService实现类UserServiceImp.java


@Service
public class UserServiceImpl implements UserService {
 
    @Resource
    private UserRepository userRepository;
 
    @Override
    public User getById(String id) {
        return userRepository.findById(id).get();
    }
 
    @Override
    public void save(User user) {
        userRepository.save(user);
    }
 
    @Override
    public User getByName(String name) {
        return userRepository.findByName(name);
    }
 
    @Override
    public void delete(String id) {
        userRepository.deleteById(id);
    }
 
    @Override
    public void update(User user) {
        userRepository.delete(user);
        userRepository.save(user);
    }
}


网站公告

今日签到

点亮在社区的每一天
去签到