Elasticsearch

  elasticsearch 是一款非常强大的开源搜索引擎,具备非常多强大功能,可以帮助我们从海量数据中快速找到需要的内容,可以用来实现搜索、日志统计、分析、系统监控等功能。

一、倒排索引

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

  

  • 倒排索引中有两个非常重要的概念:
    • 文档(Document):用来搜索的数据,其中的每一条数据就是一个文档。例如一个网页、一个商品信息
    • 词条(Term):对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条。例如:我是中国人,就可以分为:我、是、中国人、中国、国人这样的几个词条
  • 创建倒排索引是对正向索引的一种特殊处理,流程如下:
    • 将每一个文档的数据利用算法分词,得到一个个词条
    • 创建表,每行数据包括词条、词条所在文档 id、位置等信息
    • 因为词条唯一性,可以给词条创建索引,例如 hash 表结构索引
  • 倒排索引的搜索流程如下(以搜索"华为手机"为例)

    • 用户输入条件"华为手机"进行搜索
    • 对用户输入内容分词,得到词条:华为手机
    • 拿着词条在倒排索引中查找,可以得到包含词条的文档 id 有 1、2、3
    • 拿着文档 id 到正向索引中查找具体文档


      虽然要先查询倒排索引,再查询正向索引,但是词条和文档id 都建立了索引,查询速度非常快!无需全表扫描。
      正向索引是最传统的,根据 id 索引的方式。但根据词条查询时,必须先逐条获取每个文档,然后判断文档中是否包含所需要的词条,是根据文档找词条的过程
      倒排索引则相反,是先找到用户要搜索的词条,根据得到的文档 id 获取该文档。是根据词条找文档的过程

二、索引、映射、文档和字段

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

    例如:

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

       

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

  • 数据库的表会有约束信息,用来定义表的结构、字段的名称、类型等信息。因此,索引库中就有映射(mapping),是索引中文档的字段约束信息,类似表的结构约束。
  • elasticsearch 是面向文档(Document)存储的,可以是数据库中的一条商品数据,一个订单信息。文档数据会被序列化为 json 格式后存储在 elasticsearch。
  • JSON 文档中往往包含很多的字段(Field),类似于数据库中的列。

三、mysql和elasticsearch区别

MySQLElasticsearch说明
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
  • Mysql:擅长事务类型操作,可以确保数据的安全和一致性
  • Elasticsearch:擅长海量数据的搜索、分析、计算

四、IK分词器

  • ik_smart:智能切分,粗粒度
  • ik_max_word:最细切分,细粒度
  • 扩展词词典

五、索引库操作

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

    • type:字段数据类型,常见的简单类型有:
      • 字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)
      • 数值:long、integer、short、byte、double、float、
      • 布尔:boolean
      • 日期:date
      • 对象:object
    • index:是否创建索引,默认为 true
    • analyzer:使用哪种分词器
    • properties:该字段的子字段
      PUT /索引库名称
      {
        "mappings": {
          "properties": {
            "字段名":{
              "type": "text",
              "analyzer": "ik_smart"
            },
            "字段名2":{
              "type": "keyword",
              "index": "false"
            },
            "字段名3":{
              "properties": {
                "子字段": {
                  "type": "keyword"
                }
              }
            }
            // ...略
          }
        }
      }
      PUT /xn2001
      {
        "mappings": {
          "properties": {
            "info":{
              "type": "text",
              "analyzer": "ik_smart"
            },
            "email":{
              "type": "keyword",
              "index": "false"
            },
            "name":{
              "properties": {
                "firstName": {
                  "type": "keyword"
                },
                "lastName": {
                  "type": "keyword"
                }
              }
            }
          }
        }
      }

       

      特殊字段说明:

      • location:地理坐标,里面包含精度、纬度
      • all:一个组合字段,其目的是将多字段的值利用 copy_to 合并,提供给用户搜索,
        这样一来就只需要搜索一个字段就可以得到结果,性能更好。
      PUT /hotel
      {
        "mappings": {
          "properties": {
            "id": {
              "type": "keyword"
            },
            "name":{
              "type": "text",
              "analyzer": "ik_max_word",
              "copy_to": "all"
            },
            "address":{
              "type": "keyword",
              "index": false
            },
            "price":{
              "type": "integer"
            },
            "score":{
              "type": "integer"
            },
            "brand":{
              "type": "keyword",
              "copy_to": "all"
            },
            "city":{
              "type": "keyword",
              "copy_to": "all"
            },
            "starName":{
              "type": "keyword"
            },
            "business":{
              "type": "keyword"
            },
            "location":{
              "type": "geo_point"
            },
            "pic":{
              "type": "keyword",
              "index": false
            },
            "all":{
              "type": "text",
              "analyzer": "ik_max_word"
            }
          }
        }
      }
  2. 修改索引库

    倒排索引结构虽然不复杂,但是一旦数据结构改变(比如改变了分词器),就需要重新创建倒排索引,这简直是灾难。因此索引库一旦创建,无法修改 mapping,虽然无法修改 mapping 中已有的字段,但是却允许添加新的字段到 mapping 中,不会对倒排索引产生影响。
    PUT /索引库名/_mapping
    {
      "properties": {
        "新字段名":{
          "type": "integer"
        }
      }
    }
  3. 删除索引库

    DELETE /索引库名
  4. 查询索引库

    GET /数据库名

六、DSL文档操作

  1. 新增文档

    POST /索引库名/_doc/文档id
    {
        "字段1": "值1",
        "字段2": "值2",
        "字段3": {
            "子属性1": "值3",
            "子属性2": "值4"
        }
        // ...
    }
    POST /xn2001/_doc/1
    {
        "info": "我不会Java",
        "email": "jialna@qq.com",
        "name": {
            "firstName": "",
            "lastName": "弟弟"
        }
    }

     

  2. 修改文档

    PUT /{索引库名}/_doc/id
    {
        "字段1": "值1",
        "字段2": "值2",
        // ... 略
    }

    全量修改是覆盖原来的文档,其本质是:

    • 根据指定的 id 删除文档
    • 新增一个相同 id 的文档

    注意:如果根据 id 删除时,id 不存在,第二步的新增也会执行,也就是变成了新增操作

    PUT /xn2001/_doc/1
    {
        "info": "我也不会敲代码",
        "email": "3300123589@qq.com",
        "name": {
            "firstName": "弟弟",
            "lastName": ""
        }
    }

     

    POST /{索引库名}/_update/文档id
    {
        "doc": {
             "字段名": "新的值",
        }
    }

    增量修改是只修改指定 id 匹配的文档中的部分字段

    POST /xn2001/_update/1
    {
      "doc": {
        "email": "update@qq.com"
      }
    }

     

  3. 查询文档

    GET /{索引库名称}/_doc/{id}
  4. 删除文档

    DELETE /{索引库名}/_doc/{id}

七、RestClint文档操作

  1. 初始化RestClient

    • 引入依赖
      <dependency>
          <groupId>org.elasticsearch.client</groupId>
          <artifactId>elasticsearch-rest-high-level-client</artifactId>
      </dependency>

      注意:springBoot默认es版本会覆盖引入如的es依赖版本,需要覆盖默认es版本

  2. 初始化RestHighLevelClient

    public class HotelIndexTest {
    
        private RestHighLevelClient restHighLevelClient;
    
        @Test
        void testInit(){
            System.out.println(this.restHighLevelClient);
        }
    
      // 初始化 @BeforeEach
    void init(){ this.restHighLevelClient = new RestHighLevelClient(RestClient.builder( HttpHost.create("http://x.x.x.x:9200") )); } @AfterEach void down() throws IOException { this.restHighLevelClient.close(); } }
  3. 创建索引库

    @Test
    void createHotelIndex() throws IOException {
        //指定索引库名
        CreateIndexRequest hotel = new CreateIndexRequest("hotel");
        //写入JSON数据,这里是Mapping映射
        hotel.source(HotelConstants.MAPPING_TEMPLATE, XContentType.JSON);
        //创建索引库
        restHighLevelClient.indices().create(hotel, RequestOptions.DEFAULT);
    }

     

    public class HotelConstants {
        public static String MAPPING_TEMPLATE = "{\n" +
                "  \"mappings\": {\n" +
                "    \"properties\": {\n" +
                "      \"id\": {\n" +
                "        \"type\": \"keyword\"\n" +
                "      },\n" +
                "      \"name\":{\n" +
                "        \"type\": \"text\",\n" +
                "        \"analyzer\": \"ik_max_word\",\n" +
                "        \"copy_to\": \"all\"\n" +
                "      },\n" +
                "      \"address\":{\n" +
                "        \"type\": \"keyword\",\n" +
                "        \"index\": false\n" +
                "      },\n" +
                "      \"price\":{\n" +
                "        \"type\": \"integer\"\n" +
                "      },\n" +
                "      \"score\":{\n" +
                "        \"type\": \"integer\"\n" +
                "      },\n" +
                "      \"brand\":{\n" +
                "        \"type\": \"keyword\",\n" +
                "        \"copy_to\": \"all\"\n" +
                "      },\n" +
                "      \"city\":{\n" +
                "        \"type\": \"keyword\",\n" +
                "        \"copy_to\": \"all\"\n" +
                "      },\n" +
                "      \"starName\":{\n" +
                "        \"type\": \"keyword\"\n" +
                "      },\n" +
                "      \"business\":{\n" +
                "        \"type\": \"keyword\"\n" +
                "      },\n" +
                "      \"location\":{\n" +
                "        \"type\": \"geo_point\"\n" +
                "      },\n" +
                "      \"pic\":{\n" +
                "        \"type\": \"keyword\",\n" +
                "        \"index\": false\n" +
                "      },\n" +
                "      \"all\":{\n" +
                "        \"type\": \"text\",\n" +
                "        \"analyzer\": \"ik_max_word\"\n" +
                "      }\n" +
                "    }\n" +
                "  }\n" +
                "}";
    }
  4. 删除索引库

    @Test
    void deleteHotelIndex() throws IOException {
        DeleteIndexRequest hotel = new DeleteIndexRequest("hotel");
        restHighLevelClient.indices().delete(hotel,RequestOptions.DEFAULT);
    }
  5. 判断索引库

    @Test
    void existHotelIndex() throws IOException {
        GetIndexRequest hotel = new GetIndexRequest("hotel");
        boolean exists = restHighLevelClient.indices().exists(hotel, RequestOptions.DEFAULT);
        System.out.println(exists);
    }
  6. 新增文档

        @Test
        void createHotelIndex() throws IOException {
            Hotel hotel = hotelService.getById(61083L);
            HotelDoc hotelDoc = new HotelDoc(hotel);
            // 1.准备Request对象
            IndexRequest hotelIndex = new IndexRequest("hotel").id(hotelDoc.getId().toString());
            // 2.准备Json文档
            hotelIndex.source(JSON.toJSONString(hotelDoc), XContentType.JSON);
            // 3.发送请求
            restHighLevelClient.index(hotelIndex, RequestOptions.DEFAULT);
        }
  7. 查询文档

    @Test
    void testGetDocumentById() throws IOException {
        // 1.准备Request
        GetRequest hotel = new GetRequest("hotel", "61083");
        // 2.发送请求,得到响应
        GetResponse hotelResponse = restHighLevelClient.get(hotel, RequestOptions.DEFAULT);
        // 3.解析响应结果
        String hotelDocSourceAsString = hotelResponse.getSourceAsString();
        // 4.json转实体类
        HotelDoc hotelDoc = JSON.parseObject(hotelDocSourceAsString, HotelDoc.class);
        System.out.println(hotelDoc);
    }
  8. 删除文档

    @Test
    void testDeleteDocumentById() throws IOException {
        DeleteRequest hotel = new DeleteRequest("hotel", "61083");
        restHighLevelClient.delete(hotel,RequestOptions.DEFAULT);
    }
  9. 修改文档:增量修改

    @Test
    void testUpdateDocument() throws IOException {
        // 1.准备Request
        UpdateRequest request = new UpdateRequest("hotel", "61083");
        // 2.准备请求参数
        request.doc(
            "price", "952",
            "starName", "四钻"
        );
        // 3.发送请求
        restHighLevelClient.update(request, RequestOptions.DEFAULT);
    }
  10. 批量导入

     

八、DSL文档查询

  1. 查询所有:查询出所有数据,一般测试用。例如:match_all

    // 查询所有
    GET /indexName/_search
    {
      "query": {
        "match_all": {
        }
      }
    }
  2. 全文检索(full text)查询:利用分词器对用户输入内容分词,然后去倒排索引库中匹配。例如:

    • match_query:单字段查询
      GET /indexName/_search
      {
        "query": {
          "match": {
            "FIELD": "TEXT"
          }
        }
      }
      GET /hotel/_search
      {
        "query": {
          "match": {
            "all": "7天酒店"
          }
        }
      }
    • multi_match_query:多字段查询,任意一个字段符合条件就算符合查询条件
      GET /indexName/_search
      {
        "query": {
          "multi_match": {
            "query": "TEXT",
            "fields": ["FIELD1", " FIELD12"]
          }
        }
      }
      GET /hotel/_search
      {
        "query": {
          "multi_match": {
            "query": "7天酒店",
            "fields": ["brand","name"]
          }
        }
      }
      注意:搜索字段越多,对查询性能影响越大,因此建议采用 copy_to 将多个字段合并为一个,然后使用单字段查询的方式。
  3. 精确查询:一般是查找 keyword、数值、日期、boolean 等类型字段。所以不会对搜索条件分词。

    • term:根据词条精确值查询
      // term查询
      GET /indexName/_search
      {
        "query": {
          "term": {
            "FIELD": {
              "value": "VALUE"
            }
          }
        }
      }
      GET /hotel/_search
      {
        "query": {
          "term": {
            "brand": {
              "value": "7天酒店"
            }
          }
        }
      }
    • range:根据值的范围查询
      // range查询
      GET /indexName/_search
      {
        "query": {
          "range": {
            "FIELD": {
              "gte": 10, // 这里的gte代表大于等于,gt则代表大于
              "lte": 20 // lte代表小于等于,lt则代表小于
            }
          }
        }
      }
  4. 复合(compound)查询:复合查询可以将其它简单查询组合起来,实现更复杂的搜索逻辑。

    • fuction score:算分函数查询,可以控制文档相关性算分,控制文档排名

      function score 的运行流程如下:
      • 根据原始条件查询搜索文档,并且计算相关性算分,称为原始算分(query score)
      • 根据过滤条件,过滤文档
      • 符合过滤条件的文档,基于算分函数运算,得到函数算分(function score)
      • 将原始算分(query score)和函数算分(function score)基于运算模式做运算,得到最终结果,作为相关性算分。
    • bool query:布尔查询,利用逻辑关系组合多个其它的查询,实现复杂搜索
      GET /hotel/_search
      {
        "query": {
          "bool": {
            "must": [
              {"term": {"city": "上海" }}
            ],
            "should": [
              {"term": {"brand": "皇冠假日" }},
              {"term": {"brand": "华美达" }}
            ],
            "must_not": [
              { "range": { "price": { "lte": 500 } }}
            ],
            "filter": [
              { "range": {"score": { "gte": 45 } }}
            ]
          }
        }
      }
      子查询的组合方式有:
      • must:必须匹配每个子查询,类似“与”
      • should:选择性匹配子查询,类似“或”
      • must_not:必须不匹配,不参与算分,类似“非”
      • filter:必须匹配,不参与算分
      注意:搜索时,参与打分的字段越多,查询的性能也越差。因此这种多条件查询时,建议这样做:
      • 搜索框的关键字搜索,是全文检索查询,使用 must 查询,参与算分
      • 其它过滤条件,采用 filter 查询,不参与算分
  5. 相关性算分

    在后来的5.1版本升级中,elasticsearch 将算法改进为 BM25 算法,公式如下:

九、搜索结果处理

  1. 排序:elasticsearch 默认是根据相关度算分(_score)来排序,但是也支持自定义方式对搜索结果排序。可以排序字段类型有:keyword 类型、数值类型、地理坐标类型、日期类型等

    GET /indexName/_search
    {
      "query": {
        "match_all": {}
      },
      "sort": [
        {
          "FIELD": "desc"  // 排序字段、排序方式ASC、DESC
        }
      ]
    }
  2. 分页:elasticsearch 默认情况下只返回 top10 的数据。而如果要查询更多数据就需要修改分页参数了。

    • elasticsearch 通过修改 from、size 参数来控制要返回的分页结果:
      • from:从第几个文档开始
      • size:总共查询几个文档
        GET /hotel/_search
        {
          "query": {
            "match_all": {}
          },
          "from": 0, // 分页开始的位置,默认为0
          "size": 10, // 期望获取的文档总数
          "sort": [
            {"price": "asc"}
          ]
        }
    • 深度分页
      GET /hotel/_search
      {
        "query": {
          "match_all": {}
        },
        "from": 990, // 分页开始的位置,默认为0
        "size": 10, // 期望获取的文档总数
        "sort": [
          {"price": "asc"}
        ]
      }
      注意:elasticsearch 内部分页时,必须先查询 0~1000条,然后截取其中的 990 ~ 1000 的这10条
    • 分页查询的常见实现方案以及优缺点
      • from + size
        • 优点:支持随机翻页
        • 缺点:深度分页问题,默认查询上限(from + size)是10000
        • 场景:百度、京东、谷歌、淘宝这样的随机翻页搜索
      • after search
        • 优点:没有查询上限(单次查询的size不超过10000)
        • 缺点:只能向后逐页查询,不支持随机翻页
        • 场景:没有随机翻页需求的搜索,例如手机向下滚动翻页
      • scroll
        • 优点:没有查询上限(单次查询的size不超过10000)
        • 缺点:会有额外内存消耗,并且搜索结果是非实时的
        • 场景:海量数据的获取和迁移。从ES7.1开始不推荐,建议用 after search方案。
  3. 高亮:我们在百度,京东搜索时,关键字会变成红色,比较醒目,这叫高亮显示

    GET /hotel/_search
    {
      "query": {
        "match": {
          "FIELD": "TEXT" // 查询条件,高亮一定要使用全文检索查询
        }
      },
      "highlight": {
        "fields": { // 指定要高亮的字段
          "FIELD": {
            "pre_tags": "<em>",  // 用来标记高亮字段的前置标签
            "post_tags": "</em>" // 用来标记高亮字段的后置标签
          }
        }
      }
    }
    注意:
    • 高亮是对关键字高亮,因此搜索条件必须带有关键字,而不能是范围这样的查询。
    • 默认情况下,高亮的字段,必须与搜索指定的字段一致,否则无法高亮
    • 如果要对非搜索字段高亮,则需要添加一个属性:required_field_match=false

十、RestClient文档查询

  1. 发起查询请求

        @Test
        public void match_All() throws IOException {
            SearchRequest request = new SearchRequest("hotel");
            request.source()
                    .query(QueryBuilders.matchAllQuery());
            SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        }
    • 第一步,创建SearchRequest对象,指定索引库名
    • 第二步,利用request.source()构建 DSL,DSL 中可以包含查询、分页、排序、高亮等
      • query():代表查询条件,利用 QueryBuilders.matchAllQuery() 构建一个 match_all 查询的 DSL
      • request.source(),其中包含了查询、排序、分页、高亮等所有功能
      • QueryBuilders,其中包含 matchAllQuery、match、term、function_score、bool 等各种查询
    • 第三步,利用 client.search() 发送请求,得到响应
  2. match查询

    public void matchQuery() throws IOException {
        SearchRequest request = new SearchRequest("hotel");
        request.source()
                .query(QueryBuilders.matchQuery("all","如家"));
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        SearchHits searchHits = response.getHits();
        System.out.println("hits.getTotalHits().条数 = " + searchHits.getTotalHits().value);
        SearchHit[] hits = searchHits.getHits();
        for (SearchHit hit : hits) {
            String sourceAsString = hit.getSourceAsString();
            HotelDoc hotelDoc = JSON.parseObject(sourceAsString, HotelDoc.class);
            System.out.println(hotelDoc);
        }
    }
  3. 精确查询、布尔查询

    void testBool() throws IOException {
        // 1.准备Request
        SearchRequest request = new SearchRequest("hotel");
        // 2.准备DSL
        request.source()
                .query(
                        QueryBuilders.boolQuery()
                                .must(QueryBuilders.termQuery("city", "上海"))
                                .filter(QueryBuilders.rangeQuery("price").lte(300))
                );
        // 3.发送请求
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        // 4.解析响应
        SearchHits searchHits = response.getHits();
        System.out.println("hits.getTotalHits().条数 = " + searchHits.getTotalHits().value);
        SearchHit[] hits = searchHits.getHits();
        for (SearchHit hit : hits) {
            String sourceAsString = hit.getSourceAsString();
            HotelDoc hotelDoc = JSON.parseObject(sourceAsString, HotelDoc.class);
            System.out.println(hotelDoc);
        }
    }
  4. 排序、分页

    void testPageAndSort() throws IOException {
        // 页码,每页大小
        int page = 1, size = 5;
    
        // 1.准备Request
        SearchRequest request = new SearchRequest("hotel");
        // 2.准备DSL
        // 2.1.query
        request.source().query(QueryBuilders.matchAllQuery());
        // 2.2.排序 sort
        request.source().sort("price", SortOrder.ASC);
        // 2.3.分页 from、size
        request.source().from((page - 1) * size).size(5);
        // 3.发送请求
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        // 4.解析响应
        SearchHits searchHits = response.getHits();
        System.out.println("hits.getTotalHits().条数 = " + searchHits.getTotalHits().value);
        SearchHit[] hits = searchHits.getHits();
        for (SearchHit hit : hits) {
            String sourceAsString = hit.getSourceAsString();
            HotelDoc hotelDoc = JSON.parseObject(sourceAsString, HotelDoc.class);
            System.out.println(hotelDoc);
        }
    }
  5. 高亮

    void testHighlight() throws IOException {
        // 1.准备Request
        SearchRequest request = new SearchRequest("hotel");
        // 2.准备DSL
        // 2.1.query
        request.source().query(QueryBuilders.matchQuery("all", "如家"));
        // 2.2.高亮
        request.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(false));
        // 3.发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    }

十一、DSL数据聚合

  聚合常见的有三类

  • 桶(Bucket)聚合:用来对文档做分组

    • TermAggregation:按照文档字段值分组,例如按照品牌值分组、按照国家分组
    • Date Histogram:按照日期阶梯分组,例如一周为一组,或者一月为一组
      GET /hotel/_search
      {
        "size": 0,  // 设置size为0,结果中不包含文档,只包含聚合结果
        "aggs": { // 定义聚合
          "brandAgg": { //给聚合起个名字
            "terms": { // 聚合的类型,按照品牌值聚合,所以选择term
              "field": "brand", // 参与聚合的字段
              "size": 20 // 希望获取的聚合结果数量
            }
          }
        }
      }
  • 度量(Metric)聚合:用以计算一些值,比如:最大值、最小值、平均值等

    • Avg:求平均值
    • Max:求最大值
    • Min:求最小值
    • Stats:同时求 max、min、avg、sum 等
      GET /hotel/_search
      {
        "size": 0, 
        "aggs": {
          "brandAgg": { 
            "terms": { 
              "field": "brand", 
              "size": 20,
      "order": {
      "score_stats.avg": "asc" // 按照score_stats.avg升序排列
      }

      }, "aggs": { // 是brands聚合的子聚合,也就是分组后对每组分别计算 "score_stats": { // 聚合名称 "stats": { // 聚合类型,这里stats可以计算min、max、avg等 "field": "score" // 聚合字段,这里是score } } } } } }
  • 管道(pipeline)聚合:其它聚合的结果为基础做聚合
    注意:参加聚合的字段必须是keyword、日期、数值、布尔类型

十二、RestAPI数据聚合

  聚合条件与 query 条件同级别,因此需要使用 request.source() 来指定聚合条件

  • public void testAggregation() throws IOException {
        SearchRequest request = new SearchRequest("hotel");
        request.source().aggregation(AggregationBuilders.terms("brandAgg").field("brand").size(20));
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        Terms brandAgg = response.getAggregations().get("brandAgg");
        List<? extends Terms.Bucket> buckets = brandAgg.getBuckets();
        for (Terms.Bucket bucket : buckets) {
            String key = bucket.getKeyAsString();
            System.out.println("key = " + key);
        }
    }

十三、数据同步

  常见的数据同步方案有三种

  • 同步调用
  • 异步通知
  • 监听 binlog
posted @ 2022-03-22 16:46  美少女战士就是我  阅读(84)  评论(0)    收藏  举报