Elasticsearch 这篇还不够吗

news2024/11/14 18:35:49

系列文章目录

文章目录

  • 系列文章目录
  • 一、概述
    • 1. ES 的基本概念
    • 2. ES 和关系型数据库的对比
  • 二、环境准备
    • 1. linux 下单机安装
  • 三、入门操作
    • 1. 创建索引
    • 2. 写入文档
    • 3. 根据id搜索文档
    • 4. 根据一般字段搜索文档
    • 5. 根据文本字段搜索文档
  • 四、ES 客户端实战
    • 1. Spring Data Elasticsearch
    • 2. RestHighLivelClient
      • (1)不带验证的客户端
      • (2)带验证的客户端
    • 3. JavaClient 客户端
  • 五、基础操作
    • 1. 索引操作
      • 1. 创建索引
      • 2. 删除索引
      • 3. 关闭索引
      • 4. 打开索引
      • 5. 索引别名
    • 2. 映射操作
      • 1. 查看映射
      • 2. 扩展映射
        • 1. 基本数据类型
        • 2. 复杂的数据类型
        • 3. 动态映射
        • 4. 多字段
      • 3. 文档操作
        • 1. 单条写入文档
        • 2. 批量写入文档
        • 3. 更新单条文档
        • 4. 批量更新文档
        • 5. 根据条件更新文档
        • 5. 删除单条文档
        • 6. 批量删除文档
        • 7. 根据条件删除文档
  • 六、丰富的搜索功能
    • 1. 指定返回的字段
    • 2. 结果计数
    • 3. 结果分页
    • 4. 查询性能分析
    • 5. 查询所有文档
    • 6. term 查询
    • 7. terms 查询
    • 8. range 查询
    • 9. exists 查询
    • 10. must 查询
    • 11. should 查询
    • 12. must not 查询
    • 13. filter 查询
    • 14. Constant Score 查询
    • 15. match 查询
    • 16. multi_match 查询
    • 17. match_phrase 查询
    • 18. 地理位置查询
    • 19. 建议搜索
    • 20. 按字段值排序
    • 21. 按地理距离排序
  • 七、文本搜索
    • 1. 文本索引的建立过程
    • 2. 文本的搜索过程
    • 3. 使用同义词
      • 1. 建立索引时使用同义词
      • 2.查询时使用同义词
    • 4. 拼音搜索
      • 1. 拼音分析器插件安装
      • 2. 拼音分析器插件的使用
    • 5. 高亮显示搜索
    • 6. 拼音纠错
  • 八、搜索排序
    • 1. 查询时 boost 参数的设置
    • 2. boosting 查询
  • 九、聚合
    • 1.聚合指标
    • 2. 桶聚合
    • 3. 聚合方式
    • 4. 聚合排序
      • 1. 按文档计数排序
      • 2. 按聚合指标排序
      • 3. 按分组 key 排序
    • 4. 聚合分页
      • 1. Top hits 聚合
      • 2. Collapse 聚合

一、概述

1. ES 的基本概念

  • 索引

    • 在使用传统的关系型数据库时,如果对数据有存取和更新操作,需要建立一个数据库,相应地,在ES中则需要建立索引。
  • 文档

    • 在使用传统的关系型数据库时,需要把数据分装成数据库中的一条记录,而在 ES 中对应的则是文档。
  • 字段

    • 一个文档可以包含一个或多个字段、每个字段都有一个类型与其对应。除了常用的数据类型(如字符串型、文本型和数值型)外,ES 还提供了多种数据类型,如数组类型、经纬度类型和IP地址类型等。
  • 映射

    • 建立索引时需要定义文档的数据结构,这种结构叫做映射。在映射中,文档的字段类型一旦设定后就不能更改。因为字段类型在定义后,ES 已经针对定义的类型建立了特定的索引结构,这种结构不能更改。
  • 集群和节点

    • 在分布式系统中,为了完成海量数据的存储、计算并提升系统的高可用性,需要多台计算机集成在一起协作,这种形式被称为集群。
  • 分片

    • 在分布式系统中,为了能存储和计算海量的数据,会先对数据进行切分,然后再将它们存储到多台计算机中。在ES中,一个分片对应的就是一个Lucene索引,每个分片可以设置多个福分片,这样当主分片所在的计算机因为发生故障而离线时,副分片会充当主分片继续服务。索引的分片个数只能设置一次,之后不能更改,在默认情况下,ES 的每个索引设置为5个分片。
  • 副分片

    • 为了提升系统索引数据的高可用性并减轻集群搜索的负载,可以启用分片的副本,该副本叫做副分片,而原有的分片叫作主分片。在默认情况下,ES 不会为索引的分片开启副分片,用户需要手动设置。
  • DSL

    • ES使用DSL(Domain Specific Language,领域特定语言),来定义查询。ES 的DSL 采用JSON进行表达,相应地,ES 也将响应客户端请求的返回数据封装成了 JSON 形式。

2. ES 和关系型数据库的对比

  • ES 属于非关系型数据库。
  • 索引方式:关系型数据库的索引大多是 B-Tree 结构,而ES使用的是倒排索引。
  • 事务支持:事务是关系型数据库的核心组成部分,而ES是不支持事务的。ES 更新文档时,先读取文档在进行修改,然后在为文档重新建立索引。ES使用乐观锁,每次更新增加当前文档的版本号。
  • 数据的实时性:关系型数据库存储和查询数据基本上是实时的,即单条数据写入之后可以立即查询。为了提高数据写入的性能,ES 在内存和磁盘之间增加了一层系统缓存。ES 响应写入数据的请求后,会先将数据存储在内存中,此时该数据还不能被搜索到。内存中的数据每隔一段时间(默认1s)被刷新到系统缓存内,此时数据才能被搜索到。因此,ES 的数据写入不是实时的,而是准实时的。

二、环境准备

1. linux 下单机安装

  1. 压缩包下载

  2. 解压压缩包

    tar -zxvf [压缩包名称] -C [压缩后的包生成存放路径]
    
  3. 由于 es 安全机制,无法使用root启动,需要添加用户

    useradd [新添加的用户名]
    # 切换用户
    su [新添加的用户名]
    
    
  4. 新添加的用户权限不足,无法启动用户,切无法拥有修改文件的权限,需要添加权限。

    chmod -R 755 /opt/elasticsearch-7.6.2
    
  5. 启动客户端

    bin/elasticsearch
    
  6. 启动成功后,如果出现本地却无法访问问题。修改 config/elasticsearch.yml 配置文件。

    在这里插入图片描述

三、入门操作

1. 创建索引

  • hotle:索引名称

  • properties:指定字段名称及其数据类型
    在这里插入图片描述

    在这里插入图片描述

2. 写入文档

  • 即向索引中填充数据
    在这里插入图片描述

3. 根据id搜索文档

在这里插入图片描述

4. 根据一般字段搜索文档

  • 在 ES 中进行搜索时需要用到query字句。
    • _shards:命中的分片信息。
    • total:命中的文档总数。
    • max_score:命中文档中的最高分
    • hits:命中文档集合的信息。
    • _index:文档所在索引。
    • _id:文档 ID。
    • _score:文档分值。
    • _source:文档内容。

在这里插入图片描述

5. 根据文本字段搜索文档

  • 前面的搜索,关系型数据库也能胜任,但是对文本进行模糊匹配并给出分数这一功能是搜索引擎所独有的。
  • 使用 match 搜索对某个字段进行模糊匹配。
    在这里插入图片描述

四、ES 客户端实战

1. Spring Data Elasticsearch

  1. 添加依赖

    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    		</dependency>
    
  2. 配置连接地址。ruls的值可以为多个协调点的服务器地址,中间用逗号隔开。

    spring:
      elasticsearch:
        rest:
          uris: http://192.168.10.130:9200
          username: root
          password: ******
    
  3. 分装实体类

    • @Id:一个Spring Data Elasticsearch 的实体类定义必须定义一个被 @Id 修饰的字段,他是和索引中的_id相对应。对应文档_id 值。
    • @Document(indexName = “hotel”) 对应索引的值
    package com.study.elasticsearch.model.entity;
    
    import lombok.Data;
    import org.springframework.data.annotation.Id;
    import org.springframework.data.elasticsearch.annotations.Document;
    
    @Data
    @Document(indexName = "hotel")
    public class Hotel {
    
        @Id
        private int id;
    
        private String title;
    
        private String city;
    
        private String price;
    
    }
    
  4. 定义接口,需继承 CrudRespository 接口。 Spring Data Elasticsearch 会自动根据方法名识别出方法的具体逻辑。

    package com.study.elasticsearch.service;
    
    import com.study.elasticsearch.model.entity.Hotel;
    import org.springframework.data.repository.CrudRepository;
    
    import java.util.List;
    
    /**
     *
     *
     */
    public interface EsRepositoryService extends CrudRepository<Hotel,String> {
        
        List<Hotel> findByTitleLike(String title);
        
    }
    
  5. 定义服务类

    package com.study.elasticsearch.service;
    
    import com.study.elasticsearch.model.entity.Hotel;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.Resource;
    import java.util.List;
    
    @Service
    public class EsService {
    
        @Resource
        EsRepositoryService repositoryService;
    
        public List<Hotel> getHotelFromTitle(String keyword){
            return repositoryService.findByTitleLike(keyword);
        }
    }
    
  6. Controller 类

    package com.study.elasticsearch.controller;
    
    import com.study.elasticsearch.model.entity.Hotel;
    import com.study.elasticsearch.service.EsService;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.annotation.Resource;
    import java.util.List;
    
    @RestController
    public class TestController {
    
        @Resource
        EsService esService;
    
        @RequestMapping(value = "/test")
        public String getRec(){
            List<Hotel> hotelList = esService.getHotelFromTitle("再来");
            if (hotelList.size() > 0){
                return hotelList.toString();
            }else {
                return "no data";
            }
        }
    }
    
  7. 测试

    在这里插入图片描述

2. RestHighLivelClient

(1)不带验证的客户端

  1. pom依赖:

    		<!--ES 客户端依赖-->
    		<dependency>
    			<groupId>org.elasticsearch.client</groupId>
    			<artifactId>elasticsearch-rest-high-level-client</artifactId>
    			<version>7.6.2</version>
    		</dependency>
    
    		<!--ES 客户端依赖-->
    		<dependency>
    			<groupId>org.elasticsearch</groupId>
    			<artifactId>elasticsearch</artifactId>
    			<version>7.6.2</version>
    		</dependency>
    
  2. yml文件(不带验证客户端 username 和 password 可不用写,用不上,待验证客户端需要填写)

    elasticsearch:
      rest:
        # hosts 可设置多个地址,用逗号隔开
        hosts: 192.168.10.130:9200
        username: root
        password: Jj571376264
    
  3. 实体类

    package com.study.elasticsearch.model.entity;
    
    import lombok.Data;
    
    
    @Data
    public class Hotel {
    
        /**
         * 文档 id
         */
        private String id;
    
        /**
         * 对应索引名称
         */
        private String index;
    
        /**
         * 对应文档的房
         */
        private float score;
    
    
        /**
         * 对应索引中的 title
         */
        private String title;
    
        /**
         * 对应索引中的 city
         */
        private String city;
    
        /**
         * 对应索引中的 price
         */
        private Double price;
    }
    
  4. 生产 RestHighLevelClient 实例

    package com.study.elasticsearch;
    
    import org.apache.http.HttpHost;
    import org.elasticsearch.client.RestClient;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.stereotype.Component;
    
    import java.util.Arrays;
    import java.util.Objects;
    
    @Component
    public class component {
    
        @Value("${elasticsearch.rest.hosts}")
        private String hosts;
    
        @Bean
        public RestHighLevelClient initSimpleClient(){
            //根据配置文件配置 HttpHost 数组
            HttpHost[] httpHosts = Arrays.stream(hosts.split(",")).map(
                    host -> {
                        //分隔 ES 服务器的 IP 和端口
                        String[] hostParts = host.split(":");
                        String hostname = hostParts[0];
                        int port = Integer.parseInt(hostParts[1]);
                        return new HttpHost(hostname, port, HttpHost.DEFAULT_SCHEME_NAME);
                    }).toArray(HttpHost[]::new);
            //构建客户端
            return new RestHighLevelClient(RestClient.builder(httpHosts));
        }
    }
    
  5. 创建 Service 。详细API 可查阅官方文档

    package com.study.elasticsearch.service;
    
    import cn.hutool.core.util.StrUtil;
    import com.study.elasticsearch.model.entity.Hotel;
    import org.elasticsearch.action.search.SearchRequest;
    import org.elasticsearch.action.search.SearchResponse;
    import org.elasticsearch.client.RequestOptions;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.elasticsearch.index.query.QueryBuilder;
    import org.elasticsearch.index.query.QueryBuilders;
    import org.elasticsearch.rest.RestStatus;
    import org.elasticsearch.search.SearchHit;
    import org.elasticsearch.search.SearchHits;
    import org.elasticsearch.search.builder.SearchSourceBuilder;
    import org.springframework.stereotype.Service;
    import org.springframework.util.StringUtils;
    
    import javax.annotation.Resource;
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    
    @Service
    public class EsService {
    
        @Resource
        RestHighLevelClient client;
    
        public List<Hotel> getHotelFromTitle(String keyword){
    
            SearchRequest searchRequest = new SearchRequest("hotel");
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    
            //构建 query
            searchSourceBuilder.query(QueryBuilders.matchQuery("title",keyword));
            searchRequest.source(searchSourceBuilder);
    
            List<Hotel> result = new ArrayList<>();
    
            try {
                SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
    
                RestStatus status = searchResponse.status();
                if (status != RestStatus.OK){
                    return null;
                }
    
                SearchHits searchHits = searchResponse.getHits();
                for (SearchHit searchHit : searchHits) {
                    Hotel hotel = new Hotel();
                    hotel.setId(searchHit.getId());
                    hotel.setIndex(searchHit.getIndex());
                    hotel.setScore(searchHit.getScore());
    
                    Map<String, Object> dataMap = searchHit.getSourceAsMap();
                    hotel.setTitle(StrUtil.toString(dataMap.get("title")));
                    hotel.setCity(StrUtil.toString(dataMap.get("city")));
                    hotel.setPrice(Double.valueOf(StrUtil.toString(dataMap.get("price"))));
                    result.add(hotel);
                }
                return result;
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
    
  6. Controller 类

    package com.study.elasticsearch.controller;
    
    import com.study.elasticsearch.model.entity.Hotel;
    import com.study.elasticsearch.service.EsService;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.annotation.Resource;
    import java.util.List;
    
    @RestController
    public class TestController {
    
        @Resource
        EsService esService;
    
        @RequestMapping(value = "/test")
        public String getRec(){
            List<Hotel> hotelList = esService.getHotelFromTitle("再来");
            if (hotelList.size() > 0){
                return hotelList.toString();
            }else {
                return "no data";
            }
        }
    }
    
  7. 测试结果

    在这里插入图片描述

(2)带验证的客户端

  • 重写客户端

    package com.study.elasticsearch;
    
    import org.apache.http.HttpHost;
    import org.apache.http.auth.AuthScope;
    import org.apache.http.auth.UsernamePasswordCredentials;
    import org.apache.http.impl.client.BasicCredentialsProvider;
    import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
    import org.elasticsearch.client.RestClient;
    import org.elasticsearch.client.RestClientBuilder;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.stereotype.Component;
    
    import java.util.Arrays;
    
    @Component
    public class component {
    
        @Value("${elasticsearch.rest.hosts}")
        private String hosts;
    
        @Value("${elasticsearch.rest.username}")
        private String username;
    
        @Value("${elasticsearch.rest.password}")
        private String password;
        @Bean
        public RestHighLevelClient initSimpleClient(){
            //根据配置文件配置 HttpHost 数组
            HttpHost[] httpHosts = Arrays.stream(hosts.split(",")).map(
                    host -> {
                        //分隔 ES 服务器的 IP 和端口
                        String[] hostParts = host.split(":");
                        String hostname = hostParts[0];
                        int port = Integer.parseInt(hostParts[1]);
                        return new HttpHost(hostname, port, HttpHost.DEFAULT_SCHEME_NAME);
                    }).toArray(HttpHost[]::new);
    
            //生产凭证
            BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
            credentialsProvider.setCredentials(
                    AuthScope.ANY,
                    //明文凭证
                    new UsernamePasswordCredentials(username,password));
    
            //返回带验证的客户端
            return new RestHighLevelClient(RestClient.builder(httpHosts).setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {
                @Override
                public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpAsyncClientBuilder) {
                    httpAsyncClientBuilder.disableAuthCaching();
                    return httpAsyncClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
                }
            }));
        }
    }
    
  • 测试

    在这里插入图片描述

3. JavaClient 客户端

  • 在Elasticsearch7.15版本之后,Elasticsearch官方将它的高级客户端RestHighLevelClient标记为弃用状态。同时推出了全新的Java API客户端Elasticsearch Java API Client,该客户端也将在Elasticsearch8.0及以后版本中成为官方推荐使用的客户端。在Elasticsearch Java API Client 依賴中其实
  1. pom

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    	<modelVersion>4.0.0</modelVersion>
    	<parent>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-parent</artifactId>
    		<version>2.3.2.RELEASE</version>
    		<relativePath/> <!-- lookup parent from repository -->
    	</parent>
    	<groupId>com.study</groupId>
    	<artifactId>elasticsearch</artifactId>
    	<version>0.0.1-SNAPSHOT</version>
    	<name>elasticsearch</name>
    	<description>elasticsearch</description>
    	<properties>
    		<java.version>1.8</java.version>
    		<elasticsearch.version>8.1.0</elasticsearch.version>
    		<jackson.version>2.11.1</jackson.version>
    		<jakartajson.version>2.0.1</jakartajson.version>
    	</properties>
    	<dependencies>
    
    		<dependency>
    			<groupId>co.elastic.clients</groupId>
    			<artifactId>elasticsearch-java</artifactId>
    			<version>8.1.0</version>
    		</dependency>
    		<dependency>
    			<groupId>com.fasterxml.jackson.core</groupId>
    			<artifactId>jackson-databind</artifactId>
    			<version>2.11.1</version>
    		</dependency>
    		<dependency>
    			<groupId>jakarta.json</groupId>
    			<artifactId>jakarta.json-api</artifactId>
    			<version>2.0.1</version>
    		</dependency>
    
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-web</artifactId>
    		</dependency>
    
    		<dependency>
    			<groupId>org.projectlombok</groupId>
    			<artifactId>lombok</artifactId>
    			<optional>true</optional>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-test</artifactId>
    			<scope>test</scope>
    		</dependency>
    
    		<dependency>
    			<groupId>cn.hutool</groupId>
    			<artifactId>hutool-all</artifactId>
    			<version>5.8.10</version>
    		</dependency>
    	</dependencies>
    
    	<build>
    		<plugins>
    			<plugin>
    				<groupId>org.springframework.boot</groupId>
    				<artifactId>spring-boot-maven-plugin</artifactId>
    				<configuration>
    					<excludes>
    						<exclude>
    							<groupId>org.projectlombok</groupId>
    							<artifactId>lombok</artifactId>
    						</exclude>
    					</excludes>
    				</configuration>
    			</plugin>
    		</plugins>
    	</build>
    </project>
    
    • 这里依赖需要注意版本兼容问题。因为我们引入的是

      <parent>
      		<groupId>org.springframework.boot</groupId>
      		<artifactId>spring-boot-starter-parent</artifactId>
      		<version>2.3.2.RELEASE</version>
      		<relativePath/> <!-- lookup parent from repository -->
      	</parent>
      
    • 这里面已经有了我们需要的es 和jakarta 对应的版本,我们需要在properties 里指定版本号。

      <properties>
      			<java.version>1.8</java.version>
      			<elasticsearch.version>8.1.0</elasticsearch.version>
      			<jackson.version>2.11.1</jackson.version>
      			<jakartajson.version>2.0.1</jakartajson.version>
      		</properties>
      
    • 如果出现版本冲突可以通过查看 maven dependencies 来查看。
      在这里插入图片描述

    • 红线则是冲突的,可以双击查看。可以参考这里

      在这里插入图片描述

  2. yml 文件

    ## ES配置:@ConfigurationProperties(prefix = "elasticsearch") //配置的前缀
    elasticsearch:
      # 多个IP逗号隔开
      hosts: 192.168.10.132:9200
    
  3. config配置客户端

    package com.study.elasticsearch.config;
    
    
    import co.elastic.clients.elasticsearch.ElasticsearchAsyncClient;
    import co.elastic.clients.elasticsearch.ElasticsearchClient;
    import co.elastic.clients.json.jackson.JacksonJsonpMapper;
    import co.elastic.clients.transport.rest_client.RestClientTransport;
    import lombok.Setter;
    import org.apache.http.HttpHost;
    import org.elasticsearch.client.RestClient;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.util.StringUtils;
    
    // 配置的前缀
    @ConfigurationProperties(prefix = "elasticsearch")
    @Configuration
    public class EsConfig {
    
        /**
         * 多个IP逗号隔开
         */
        @Setter
        private String hosts;
    
        /**
         * 同步方式
         *
         * @return
         */
        @Bean
        public ElasticsearchClient elasticsearchClient() {
            HttpHost[] httpHosts = toHttpHost();
            // Create the RestClient
            RestClient restClient = RestClient.builder(httpHosts).build();
            // Create the transport with a Jackson mapper
            RestClientTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
            // create the API client
            return new ElasticsearchClient(transport);
        }
    
        /**
         * 异步方式
         *
         * @return
         */
        @Bean
        public ElasticsearchAsyncClient elasticsearchAsyncClient() {
            HttpHost[] httpHosts = toHttpHost();
            RestClient restClient = RestClient.builder(httpHosts).build();
            RestClientTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
            return new ElasticsearchAsyncClient(transport);
        }
    
        /**
         * 解析配置的字符串hosts,转为HttpHost对象数组
         *
         * @return
         */
        private HttpHost[] toHttpHost() {
            if (!StringUtils.hasLength(hosts)) {
                throw new RuntimeException("invalid elasticsearch configuration. elasticsearch.hosts不能为空!");
            }
    
            // 多个IP逗号隔开
            String[] hostArray = hosts.split(",");
            HttpHost[] httpHosts = new HttpHost[hostArray.length];
            HttpHost httpHost;
            for (int i = 0; i < hostArray.length; i++) {
                String[] strings = hostArray[i].split(":");
                httpHost = new HttpHost(strings[0], Integer.parseInt(strings[1]), "http");
                httpHosts[i] = httpHost;
            }
    
            return httpHosts;
        }
    
    }	
    
  4. service

    package com.study.elasticsearch.service;
    
    import com.study.elasticsearch.model.VO.EsBaseResult;
    
    
    /**
     *
     *
     */
    public interface EsRepositoryService {
    
        /**
         * 文档插入     单条
         * @param indexName 索引名称
         */
        EsBaseResult saveSingleIndexDoc(String indexName) ;
    }
    
  5. 实现类

    package com.study.elasticsearch.service.impl;
    
    import co.elastic.clients.elasticsearch.ElasticsearchClient;
    import co.elastic.clients.elasticsearch.core.SearchResponse;
    import co.elastic.clients.elasticsearch.core.search.Hit;
    import com.study.elasticsearch.model.VO.EsBaseResult;
    import com.study.elasticsearch.model.entity.Hotel;
    import com.study.elasticsearch.service.EsRepositoryService;
    import org.springframework.stereotype.Service;
    import javax.annotation.Resource;
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Objects;
    
    @Service
    public class EsRepositoryServiceImpl implements EsRepositoryService {
    
        @Resource
        private ElasticsearchClient elasticsearchClient;
    
    
        @Override
        public EsBaseResult saveSingleIndexDoc(String indexName) {
            List<Hotel> hotels = new ArrayList<>();
            try {
                SearchResponse<Hotel> hotel = elasticsearchClient.search(e -> e.index(indexName), Hotel.class);
                for (Hit<Hotel> hit : hotel.hits().hits()) {
                    Hotel source = hit.source();
                    Objects.requireNonNull(source).setIndex(hit.index());
                    Objects.requireNonNull(source).setId(hit.id());
                    Objects.requireNonNull(source).setScore(hit.score());
                    hotels.add(source);
                }
                return EsBaseResult.builder().data(hotels.toString()).build();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
    
  6. controller

    package com.study.elasticsearch.controller;
    
    import cn.hutool.json.JSONObject;
    import com.study.elasticsearch.model.VO.EsBaseResult;
    import com.study.elasticsearch.service.EsRepositoryService;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.annotation.Resource;
    
    @RestController
    public class TestController {
    
        @Resource
        EsRepositoryService esService;
    
    
    
        @RequestMapping(value = "/save")
        public EsBaseResult getRec(){
            return esService.saveSingleIndexDoc("hotel");
        }
    }
    
  7. 测试
    在这里插入图片描述

五、基础操作

1. 索引操作

1. 创建索引

  • 在创建索引时,可以安装实际需求堆缩影进行主分片和副分片设置。假设 主分片:15,副分片:2。
PUT /${index_name}
{
    "settings": {
        "number_of_shards": 15,
        "number_of_replicas": 2
    },
    "mappings": {
        "properties": {
            ...
        }
    }
}		

2. 删除索引

  • DELETE / [index_name]
    • index_name:索引名称
    • 系统返回操作成功信息
      {
      	"acknowledged":true
      }
      

3. 关闭索引

  • POST / [index_name] / _close
    • 在有些场景下,某个索引暂时不使用,但是后期可能又会使用,这里的使用值数据写入和数据搜索。这个索引在某一段时间内属于冷数据或者归档数据。此时就可以关闭索引,关闭后不能写入和搜索数据。

4. 打开索引

  • POST /hotel/_open

5. 索引别名

  • 顾名思义,是给一个或多个索引另外起一个名字 ,使索引别名和索引之间建立某种逻辑关系。

  • 使用场景(1):

    • 我们建立1、2、3 ,三个月的酒店入住数据,分别对应各三个索引。现在我们需要查询里面的数据,我们就需要分别从这三个索引(分别对应1、2、3,三个月数据)去查询数据,但是我们可以通过创建索引别名,给这三个索引创建同一个索引别名,从这个索引别名去查询数据,即可实现效果(ES 会将请求转发给三个索引);

      POST /_aliases
      {
          "actions":[
              {
                  "add":{
                      "index":"january_log",
                      "alias":"last_three_month"
                  }
              },
              {
                  "add":{
                      "index":"february_log",
                      "alias":"last_three_month"
                  }
              },
              {
                  "add":{
                      "index":"march_log",
                      "alias":"last_three_month"
                  }
              }
          ]
      }
      
    • 但是向该索引别名插入数据时,会出现问题。不能够确认转发请求的对象,如果索引别名只对应一个索引,则没问题,否则报错。我们设置参数 is_write_index :true 来确认请求转发对象。

      POST /_aliases
      {
          "actions":[
              {
                  "add":{
                      "index":"january_log",
                      "alias":"last_three_month",
                      "is_write_index":true
                  }
              }
          ]
      }
      
  • 使用场景(2): 创建索引后,有些参数是不可以修改的,比如主分片的个数。我们可以新建一个索引,设置成我们想要的分片数,然后和旧的索引起一样的索引别名。再删除旧的索引别名,就变相的完成了索引的分片参数的修改。

    POST /_aliases
    {
        "actions":[
            {
                "remove":{
                    "index":"hotel_1",		//删除索引 hotel_1 的别名 hotel
                    "alias":"hotel"
                }
            },
            {
                "add":{
                    "index":"hotel_2",		//增加索引 hotel_2 的别名hotel
                    "alias":"hotel"
                }
            }
        ]
    }
    

2. 映射操作

  • 在使用数据之前,需要构建数据的组织结构。在数据库里叫做表结构,在ES理叫做映射。
  • 作为无模式搜索引擎,ES 在写入数据时猜测数据类型,从而自动创建映射。但有时ES创建的映射中的数据类型和目标类型可能不一致。当需要严格控制数据类型时,还是需要手动进行创建的。

1. 查看映射

  • GET /[index_name]/_mapping

  • 测试:GET/hotel/_mapping,返回结果:

    {
        "hotel":{
            "mappings":{
                "properties":{
                    "city":{                //定义字段类型为keyword
                        "type":"keyword"
                    },
                    "price":{               //定义字段类型为double
                        "type":"double"
                    },
                    "title":{               //定义字段类型为text
                        "type":"text"
                    }
                }
            }
        }
    }
    

2. 扩展映射

  • POST /hotel/_mapping

  • 映射中的字段类型时不可以修改的,但是字段可以扩展。

    POST /hotel/_mapping
    {
        "properties": {
            "tag": {
                "type": "keyword"
            }
        }
    }
    

1. 基本数据类型

  • keyword:keyword 类型时不进行切分的字符串类型。

    • 这里的"不进行切分"指的是:在索引中,对keyword类型的数据不进行切分,直接构建倒排索引。在搜索时,对该类型的查询字符串不进行切分后的部分匹配。
    • keyword 类型数据一般用于对文档的过滤、排序和聚合。
    • 在现实场景中,keyword 经常用语描述姓名、产品类型、用户ID、URL和状态码等。
    • keyword类型数据一般用于比价字符串是否相等,不对数据进行匹配。因此一般查询这种类型的数据时使用term查询。比如有一条数据 user_name:张三,使用term搜索user_name:张三,可以命中数据,但是若使用match搜若user_name:张,则不会命中数据
  • text:是可以进行切分的字符串类型。

    • 这里的 “可切分” 指的是:在索引中,可按照相应的切词算法对文本内容进行切分,然后构建倒排索引:在搜索时,对该类型的查询字符串按照用户的切词算法进行切分,然后对切分后的部分匹配打分。
    • 列入,一个就点搜索项目,我们希望可以根据就点名称即 title 字段进行模糊匹配,因此可以设定 title 字段text 字段。
    • 举个例子:
      PUT /hotel/_doc/001
      {
      	"title":"文雅酒店"
      }
      //使用 term 进行搜索
      GET /hotel/_search
      {
          "query": {
              "term": {
                  "title": {
                      "value": "文雅酒店"
                  }
              }
          }
      }
      /**
       *  发现无法搜索到数据,因为 term 搜索英语搜索值和文档对应的字段是否完全相等,而对于 text 类型的数据,在建立索引时ES已经进行了
       *	切分并建立倒排索引,因此使用 term 进行搜索没有数据。一般情况下,搜索 text 类型数据时应使用 match 搜索。	
       */
      
      //使用 match 搜索,发现可以搜索到新添加的数据
      GET /hotel/_search
      {
          "query": {
              "match": {
                  "title": "文雅"
              }
          }
      }
      
  • 数值类型

    • ES 支持的数值类型有:long、integer、short、byte、double、float、half_float、scaled_fload 和 unsigned_long等。
    • 为节约存储空间并提升搜索和索引的效率,在实际应用中,在满足需求的情况下应尽可能选择范围小的数据类型,如年龄取值最大不超过200,因此可以选择 byte 类型即可。
    • 数值类型的数据也可用于对文档进行过滤、排序和聚合。
    • 使用 term 进行范围搜索价格 350 ~ 400(包含350和400)
      GET /hotel/_search
      {
          "query": {
              "range": {
                  "parice": {
                      "get": 350,
                      "lte": 400
                  }
              }
          }
      }
      
  • 布尔类型:使用 boolean 定义。

    • 用于业务中的二值表示,如商品是否售罄,房屋是否已租,酒店房间是否满房等。
    • 写入或者查询该类型的数据时,其值可以使用 true 或 false,或者使用字符串形式 "true" 和 "false"。
    • 举个例子。
      PUT /hotel
      {
      	 "mappings": {
      		  "properties": {
      		       "full_room": {
      		            "type": "boolean"
      		   }
      		}
      	}
      }		
      // 使用term查询boolean类型的数据
      GET /hotel/_search
      {
          "query": {
              "term": {
                  "full_room": {
                      "type": "true"
                  }
              }
          }
      }
      
  • 日期类型:日期类型的名称为 data。

    • ES 中存储的日期是标准的 UTC 格式。

      PUT /hotel
      {
          "mappings": {
              "properties": {
                  "create_time": {
                      "type": "date"
                  }
              }
          }
      }
      
    • 一般使用如下形式表示日期类型数据。

      • 格式化的日期字符串。
      • 毫秒级的长整型,表示从1970年1月1日0点到现在的毫秒数。
      • 秒级别的整型,表示从1970年1月1日0点到现在的秒数。
    • 日期类型的默认格式为 stric_data_optional||epoch_millis。

      • stric_data_optional:支持 yyyy-MM-dd、yyyyMMdd、yyyyMMddHHmmss、yyyy-MM-ddTHH:mm:ss、yyyy-MM-ddTHH:mm:ss.SSS、yyyy-MM-ddTHH:mm:ss:SSSZ等格式。
      • epoch_millis:从1970年1月1日0点到现在的毫秒数。
    • 布置吃我们常用的 yyyy-MM-dd HH:mm:ss,在我们插入、查询数据使用该格式会报错,我们可以在创建映射时指定日期字段的 format 属性为自定义格式。

      PUT /hotel
      {
          "mappings": {
              "properties": {
                  "create_time": {
                      "type": "date",
                      "format":"yyyy-MM-dd HH:mm:ss"
                  }
              }
          }
      }
      

2. 复杂的数据类型

  • 数组类型

    • ES 数组没有定义方式,使用方式即开箱即用,无需事先声明,在写入时用括号 [] 括起来,由 ES 对该字段完成定义。
    • 如果实现字段已经定义了数据类型,在插入数组数据时,ES也会将数据转化为数组进行存储。比如 设置字段 字段 tay 类型为keyword,插入数据时(也可插入空数组,即值为 [] ):
      POST /hotel/_doc/001
      {
      	"tag": [		//写入字符串数组类型
      		"有车位",
      		"免费WIFI"
      	]
      }
      
    • 数组类型的搜索方式适用于元素类型的搜索方式。也就是说,数组元素类型(即定义的映射字段类型,比如keyword)使用什么搜索,数组字段就适用于什么数组。如上例中,keyword 就是用于 term 搜索。
      GET /hotel/_search
      {
          "query": {
              "term": {
                  "tag": {
                      "value": "有车位"
                  }
              }
          }
      }
      
  • 对象类型

    • 在实际业务中,一个文档需要包含其他内部对象。
    • 比如就点搜索中,用户希望酒店信息中包含评论数据,而评论数据也分好评数据和坏评数据。为了支持这种业务,ES中可以使用兑现类型。和数组类型一样,也不需要事先定义,写入文档时自动识别并转化为对象类型。
    • 添加一条数据:
      PUT /hotel/_doc/001
      {
          "comment_info": { //评论数据
              "properties": {
                  "favourable_comment": 199, //好评数据
                  "negative_comment": 68 //差评数据
              }
          }
      }
      
    • 根据对象类型中的属性进行搜索时,可以直击用 ‘.’ 操作符进行指向。例如,搜索 hotel 索引中好评数大于200的文档。
      GET /hotel/_search
      {
          "query": {
              "range": {
                  "comment_info.properties.favourable_comment": {
                      "gte": 200
                  }
              }
          }
      }
      
    • 当然,对象内部还可以在包括对象。
      PUT /hotel/_doc/001
      {
          "comment_info": {
              "properties": {
                  "favourable_comment": 199,
                  "nagative_comment": 68,
                  "top3_favourable_comment": { //新增字段
                      "top1": {
                          "content": "干净整洁的一家酒店", //增加的第一条评论数据
                          "score": 87
                      },
                      "top2": { //增加的第二条评论数据
                          "content": "服务周到,停止方便",
                          "score": 89
                      },
                      "top3": { //增加的第三条评论数据
                          "content": "闹钟取静,环境优美",
                          "score": 90
                      }
                  }
              }
          }
      }
      
  • 地理类型

    • 该类型需要在 mapping 中指定目标字段的数据类型为geo_point类型。
      PUT /hotel
      {
          "mappings": {
              "properties": {
                  "location": {
                      "type": "geo_point"
                  }
              }
          }
      }
      
    • 添加一条酒店文档
      POST /hotel/_doc/001
      {
          "loaction": {
              "lat": 40.012134,
              "lon": 116.497553
          }
      }
      

3. 动态映射

  • 当字段没有定义时,ES 可以根据写入的数据自动定义该字段的类型,这种机制叫做动态映射。前面介绍中也用到过。

  • 数组类型和对象类型,这两种类型都不需要用户提前定义,ES 将根据写入的数据自动创建 mapping 中对应的字段并指定类型。

  • 对于基本类型,如果字段没有定义类型,ES 在将数据存储到索引时会进行自动映射。映射表如下:

    JSON 类型索引类型
    null不新增字段
    true或falseboolean
    integerlong
    objectobject(对象)
    array根据数组中的第一个非空值进行判断
    stringdate、double、long、text,根据数据形式进行转换
  • 一般情况下,使用基本数据类型时,最好先定义好类型。因为动态映射可能会和用户预期有偏差。

4. 多字段

  • 针对同一个字段,有时需要不同的数据类型,通常表现在为了不同的目的以不同的索引类型来实现。

  • 例如,在订单搜索中,即希望能够按照用户姓名进行搜索,也希望按照姓氏进行排序,可以在 mapping 定义中将姓名字段先后定义为 text 类型和 keyword 类型。其中 keyword 类型的字段为子字段,这样在 ES 在建立索引时会将姓名字段建立两份索引,即 text 类型的索引和keyword 类型的索引。索引定义如下:

    PUT /hotel_order
    {
        "mappings": {
            "properties": {
                "order_id": {
                    "type": "keyword"
                },
                "user_name": {
                    "type": "text",
                    "fields": { //定义 user_name 多字段
                        "user_name_keyword": { //定义 user_name 字段的子字段 user_name_keyword,并定义其类型为 keyword
                            "type": "keyword"
                        }
                    }
                }
            }
        }
    }
    
  • 在普通搜索中使用 user_name 字段

    GET /hotel_order/_search
    {
        "query": {
            "match": {
                "user_name": "Jordan"
            }
        },
        "sort": {
            "user_name.user_name_keyword": "asc"
        }
    }
    

3. 文档操作

  • 使用 ES 构建搜索引擎时需要经常堆文档进行操作,除了简单的单文档操作,还需要进行批量操作。接下来除了会展示 ES 操作的命令外,还会展示这些命令在 javaClient 中的是如何使用的。

1. 单条写入文档

  • POST / [index_name] / _doc / [id]。id为ES中的稳定id,这种请求方式是用户自定义id值,不使用 ES 生产的id。也可不指定id,由ES自动生成。

    POST /hotel/_doc
    {
        "title": "好再来酒店",
        "city": "青岛",
        "price": 578.23
    }
    
  • 使用 Java API client

    • service
      package com.study.elasticsearch.service;
      
      import co.elastic.clients.elasticsearch.core.IndexResponse;
      
      import java.io.IOException;
      
      public interface EsDocService {
      
          /**
           * 新增一个文档
           *
           * @param indexName 索引名称
           * @param indexId 索引id
           * @param doc   新增文档
           * @return
           */
          <T> IndexResponse saveDoc(String indexName, String indexId, T doc) throws IOException;
      }
      
    • 实现类
      package com.study.elasticsearch.service.impl;
      
      import co.elastic.clients.elasticsearch.ElasticsearchAsyncClient;
      import co.elastic.clients.elasticsearch.ElasticsearchClient;
      import co.elastic.clients.elasticsearch.core.IndexResponse;
      import com.study.elasticsearch.service.EsDocService;
      import org.springframework.stereotype.Service;
      
      import javax.annotation.Resource;
      import java.io.IOException;
      
      @Service
      public class EsDocServiceImpl implements EsDocService {
      
          @Resource
          private ElasticsearchClient client;
      
          @Resource
          private ElasticsearchAsyncClient asyncClient;
      
          @Override
          public <T> IndexResponse saveDoc(String indexName, String indexId, T doc) throws IOException {
              IndexResponse indexResponse = client.index(idx -> idx.index(indexName).id(indexId).document(doc));
              return indexResponse;
          }
      }
      

2. 批量写入文档

  • ES 中批量写入文档请求的类型是 POST,形式如下。

    POST /_bulk					//批量请求
    {"index":{"_index":"${index_name}"}}		//指定批量写入的索引
    {...}										//该索引下的文档内容
    {"index":{"_index":"${index_name}"}}		//指定批量写入的索引
    {...}										//该索引下的文档内容
    
  • 举个例子:

    POST /[index_name]/_bulk
    {"index":{"_index":"hotel"}}
    {"title":"文雅酒店","city":"北京""price":556.00}
    {"index":{"_index":"hotel"}}
    {"title":"嘉怡假日就点","city":"北京""price":337.00}
    
  • 上述的 DSL 写入索引中的文档id是es自动生成的,如果需要指定id,需要在index后面拼接。如:{“index”:{“_index”:“hotel”,“_id”:“1001”}}

  • 批量的提交内容可能很多,我们可能需要把这些内容放入文档中, 然后提交文档来批量写入。使用命令:

    • curl -s -XPOST ‘127.0.0.1:9200/_bulk?preety’ --data-binary “@bulk_doc.json”
    • bulk_doc.json 对应的文件名称。
      在这里插入图片描述
  • 对应的 JavaClient 代码操作。

    • service

      package com.study.elasticsearch.service;
      
      import co.elastic.clients.elasticsearch.core.BulkResponse;
      import co.elastic.clients.elasticsearch.core.IndexResponse;
      
      import java.io.IOException;
      import java.util.List;
      
      public interface EsDocService {
      
          /**
           * 新增一个文档
           *
           * @param indexName 索引名称
           * @param indexId 索引id
           * @param doc   新增文档
           * @return
           */
          <T> IndexResponse saveDoc(String indexName, String indexId, T doc) throws IOException;
      
      
          /**
           * 批量添加文档 (该方式适用于es自动生成文档的id方式,因为泛型文档无法获取里面属性,比如id)
           *
           * @param idxName   索引名称
           * @param docs  文档集合
           * @return
           * @param <T>   文档实体类
           * @throws Exception
           */
          <T> BulkResponse bulkSave(String idxName, List<T> docs) throws Exception;
      }
      
    • 实现类

      package com.study.elasticsearch.service.impl;
      
      import co.elastic.clients.elasticsearch.ElasticsearchAsyncClient;
      import co.elastic.clients.elasticsearch.ElasticsearchClient;
      import co.elastic.clients.elasticsearch.core.BulkRequest;
      import co.elastic.clients.elasticsearch.core.BulkResponse;
      import co.elastic.clients.elasticsearch.core.IndexResponse;
      import com.study.elasticsearch.service.EsDocService;
      import org.springframework.stereotype.Service;
      
      import javax.annotation.Resource;
      import java.io.IOException;
      import java.util.List;
      
      @Service
      public class EsDocServiceImpl implements EsDocService {
      
          @Resource
          private ElasticsearchClient client;
      
          @Resource
          private ElasticsearchAsyncClient asyncClient;
      
          @Override
          public <T> IndexResponse saveDoc(String indexName, String indexId, T doc) throws IOException {
              IndexResponse indexResponse = client.index(idx -> idx.index(indexName).id(indexId).document(doc));
              return indexResponse;
          }
      
          @Override
          public <T> BulkResponse bulkSave(String idxName, List<T> docs) throws Exception {
              BulkRequest.Builder br = new BulkRequest.Builder();
              docs.forEach(doc -> br
                      .operations(op -> op.
                              index(idx -> idx.index(idxName).document(doc))));
              return client.bulk(br.build());
          }
      }
      
    • 测试:

      在这里插入图片描述

  • 考虑到会根据id更新的情况,接口优化如下:

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

3. 更新单条文档

  • 在 ES 中更新文档的请求类型是 POST ,形式如下:

    POST /[index_name]/_update/[_id]
    
  • 举个例子:

    POST /hotel/_update/1100
    {
    	"title":"test05",
    	"city":"cs005",
    	"price":"659.45"
    }
    
  • 根据文档 id 搜索命令:GET /[index_name]/_doc/[id]

    GET /hotel/_doc/1100
    
  • 更新操作对应的 JavaClient 代码:

    • service

          /**
           * 更新一个文档
           *
           * @param idxName 索引名称
           * @param data    更新文档
           * @param <T>     文档类型
           */
          <T extends BaseEsEntity> void updateDoc(String idxName, T data) throws IOException;
      
    • serviceImpl

          @Override
          public <T extends BaseEsEntity> void updateDoc(String idxName, T data) throws IOException {
              client.update(e -> e.index(idxName).id(data.getId()).doc(data), data.getClass());
          }
      
  • 也可用 上文提到的新增 save 方法,但是要 文档id 不为空 且 已存在。

  • 测试:

    • 更新前:在这里插入图片描述

    在这里插入图片描述

    • 更新后:
      在这里插入图片描述

4. 批量更新文档

  • 指令和批量写相似:区别在于这里的 文档 _id 为必填项。

    POST /_bulk					//批量请求
    {"index":{"_index":"${index_name}","_id":"${id}"}}		//指定批量写入的索引
    {...}										//该索引下的文档内容
    {"index":{"_index":"${index_name}","_id":"${id}"}}}		//指定批量写入的索引
    {...}										//该索引下的文档内容
    
  • 对应客户端,代码和批量新增一致,只是更新的话 id 不为空且已存在。

    在这里插入图片描述

5. 根据条件更新文档

  • 使用 _upda_by_query 功能。
POST /[_index_name]/_update_by_query
{
	"query":{		//更新文档的查询条件
	...
	},
	"script":{		//条件更新的更新脚本
		...
	}
}
  • 举个例子:
POST /[_index_name]/_update_by_query
{
	"query":{										//更新文档的查询条件,城市为北京的文档
		"term":{
			"city":{
				"value":"北京"
			}
		}
	},
	"script":{										//条件更新的更新脚本,将城市改为"上海"
		"source":"ctx._source['city']='上海'",
		"lang":"painless"
	}
}
  • 对应的 JavaClient 代码:
//todo

5. 删除单条文档

  • 请求类型 DELETE

    DELETE /[_index_name]/_doc/[id]
    
  • 举个例子:

    DELETE /hotel/_doc/001
    

    在这里插入图片描述
    在这里插入图片描述

6. 批量删除文档

  • 与批量写入和更新文档不同的是,批量删除文档不需要提供 JSON 数据。

    POST /_bulk
    //批量删除文档,指定文档_id
    {"delete":{"_inde":"${index_name"},"_id":"${_id}"}}
    //批量删除文档,指定文档_id
    {"delete":{"_inde":"${index_name"},"_id":"${_id}"}}
    
  • 对应的 JavaClient 代码:
    在这里插入图片描述
    在这里插入图片描述

7. 根据条件删除文档

  • ES 提供了 _delete_by_query 功能。

    POST /${index_name}/_delete_by_query
    {
    	"query":{
    		...
    	}
    }
    
  • 举个例子:

    POST /${index_name}/_delete_by_query
    	{
    		"query":{
    			"term":{
    				"city":{
    					"value":"北京"
    				}
    			}
    		}
    	}
    
  • 对应的 JavaClient 代码:

    //todo
    

六、丰富的搜索功能

  • 为优化搜索性能,需要指定一部分字段内容。为了更好地呈现呈现结果,需要用到结果计数和分页功能;当遇到性能瓶颈时,需要剖析搜索各个环节的耗时;面对不符合预期的搜索结果时,需要分析各个文档的评分细节。
  • 后面的客户端操作会使用 elasticsearch high level client 客户端进行操作

1. 指定返回的字段

  • 考虑到性能问题,需要对搜索结果进行"瘦身" ——指定返回的字段。

  • 在 ES 中,通过 _source 字句可以设定返回结果的字段。_source 指向一个 JSON 数组,数组中的元素是希望返回的字段名称。

  • 下面的 DSL 指定搜索结果只返回 title 和 city 字段。

    GET /hotel/_search
    {
    	"_source":["title","city"],		//设定只返回 title 和 city 字段
    		"query":{
    			"term":{
    				"city":{
    					"value":"北京"
    				}
    			}
    		}
    }
    
  • 客户端代码:

    在这里插入图片描述

2. 结果计数

  • 针对搜索结果进行计数。

  • DSL 语句如下

    GET /hotel/_count
    {
        "query": {
            "term": {
                "city": {
                    "value": "北京"
                }
            }
        }
    }
    
  • 客户端代码:

    在这里插入图片描述

3. 结果分页

  • ES 默认返回前10个搜索匹配的文档,可以通过设置 from 和 size 来定义搜索的位置和每页显示的文档数量。

    • from:查询结果的起始下标。
    • size:从下标开始返回的文档个数。
  • DSL 语句:

    GET /hotel/_search
    {
        "from": 0,
        "size": 20,
        "query": {
            "term": {
                "city": {
                    "value": "北京"
                }
            }
        }
    }
    
  • ES 最大可返回 10000 但是也可以修改。

    PUT /hotel/_settings
    {
    	"index":{
    		"max_result_window":20000
    	}
    }
    
  • 客户端代码:

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

4. 查询性能分析

  • 在使用 ES 搜索的过程中,有的搜索请求的响应可能比较慢,大部分是DSL的执行逻辑有问题。通过 ES 提供的 profile 功能,该功能能详细的列出搜索时每一个步骤的耗时,可以帮助我们对 DSL 的性能进行剖析。
  • DSL 语句
GET /hotel/_search
{                    
	"profile":"true", //打开性能剖析开关
	"query":{
		"match":{
			"title":"金都"
		}
	}
}
  • 建议使用 Kibana 提供的可视化工具,更加的直观。

5. 查询所有文档

  • DSL 语句:

    GET /hotel/_search
    {
    	"_source":[		//只返回title 和city字段
    		"title",
    		"city"
    	],
    	"query":{
    		"match_all":{		//查询所有文档
    			"boost":2		//设置所有分值为2.0
    		}
    	}
    }
    
  • Java 客户单:
    在这里插入图片描述

6. term 查询

  • term 查询是结构化精准查询的主要查询方式,用于查询待查字段和查询值是否完全匹配

  • DSL 语句如下:

    GET /hotel/_search
    {
    	"query"{
    		"term":{
    			"city":{
    				"value":"北京"
    			}
    		}
    	}
    }
    
  • 需要注意的是日期类型字段的查询。参考上文。

  • Java 客户端中没有 日志类型的参数查询,该如何解决呢。可以使用日期类型字符串来解决,如下:
    在这里插入图片描述

7. terms 查询

  • terms 是 term 的拓展形式。用于查询一个或多个值与待查字段是否完全匹配。

  • DSL 语句如下:

    GET /hotel/_search
    	{
    		"query"{
    			"term":{
    				"city":{
    					"北京",
    					"天津"
    				}
    			}
    		}
    	}
    
  • Java 客户端代码:
    在这里插入图片描述

8. range 查询

  • 用于范围查询。一般针对数值和日期类型做范围查询。

    • gt:大于
    • lt:小于
    • gte:大于或等于
    • lte:小于或等于
  • DSL 语句如下:

    GET /hotel/_search
    {
    	"query":{
    		"range"{
    			"price":{
    				"gte":300,
    				"lte:500
    			}
    		}
    	}
    }
    
  • Java 客户端如下:

    在这里插入图片描述

9. exists 查询

  • 在某些场景下,我们希望找到某个字段不为空的文档,可以使用 exists 查询。

  • 字段不为空的情况有:

    • 值存在且不为空。
    • 值不是空数组。
    • 值是数组,但不是null。
  • DSL 语句如下。

    GET /hotel/_search
    {
    	"query":{
    		"exists":{
    			"field": "price"
    		}
    	}
    }
    
  • Java 客户端代码

    在这里插入图片描述

10. must 查询

  • 相当于逻辑查询中的 “与” 查询。must 搜索包含一个数组,可以把其他的 term 级别的小哈讯以及布尔查询放入其中。

  • 使用 must 查询城市为北京 且 价格在350~400之间的酒店。

    GET /hotel/_search
    {
        "query": {
            "bool": {
                "must": [ //拼接多个查询,用 and 连接
                    { //第一个子查询:
                        "term": {
                            "city": {
                                "value": "北京"
                            }
                        }
                    },
                    { //第一个子查询:
                        "range": {
                            "price": {
                                "gte": 350,
                                "lte": 400
                            }
                        }
                    }
                ]
            }
        }
    }
    
  • Java 客户端:

    在这里插入图片描述

11. should 查询

  • should 包含一个数组,可以把其他的 term 级别的查询及布尔查询放入其中。

    GET /hotel/_search
    {
        "query": {
            "bool": {
                "should": [ //拼接多个查询,用 or 连接
                    { //第一个子查询:
                        "term": {
                            "city": {
                                "value": "北京"
                            }
                        }
                    },
                    { //第一个子查询:
                        "term": {
                             "city": {
                                "value": "天津"
                            }
                        }
                    }
                ]
            }
        }
    }
    
  • Java 客户端

    在这里插入图片描述

12. must not 查询

  • 表示 非。

  • 使用 must not 查询城市即不为北京也不为天津的酒店。

    GET /hotel/_search
    {
        "query": {
            "bool": {
                "must_not": [ //拼接多个查询,用 or 连接
                    { //第一个子查询:
                        "term": {
                            "city": {
                                "value": "北京"
                            }
                        }
                    },
                    { //第一个子查询:
                        "term": {
                             "city": {
                                "value": "天津"
                            }
                        }
                    }
                ]
            }
        }
    }
    
  • Java 客户端

    在这里插入图片描述

13. filter 查询

  • 即过滤查询。其他布尔查询关注的是查询条件和文档的匹配程度,并按照匹配程度进行打分。而 filter 查询只关注的是查询条件和文档是否匹配,不进行相关的打分计算,但是会对部分匹配结果进行缓存。

  • DSL 语句如下:城市为北京,且未满房的酒店。

  • 如果查询不需要打分,filter 查询更加高效。

    GET /hotel/_search
    {
        "query": {
            "bool": {
                "filter": [ //拼接多个查询,用 or 连接
                    { //第一个子查询:
                        "term": {
                            "city": "北京"
                        }
                    },
                    { //第一个子查询:满房状态为否
                        "term": {
                             "full_room": false
                        }
                    }
                ]
            }
        }
    }
    
  • Java 客户端

    在这里插入图片描述

14. Constant Score 查询

  • 如果不想让检索词频率TF(Term Frequency)对搜索结果排序有影响,只想过滤某个文本字段是否包含某个词。可以使 Constant Score 包含起来。

  • 比如查询字段 amenities 字段包含关键字 “停车场” 的酒店。

    GET /hotel/_search
    {
        "_source":["amenities"],
        "query": {
            "constant_score":{              //满足条件即打分1
                "match":{
                    "amenities":"停车场"    //查询设施中包含”停车场“的文档
                }
            }  
        }
    }
    
  • Java 客户端

    在这里插入图片描述

15. match 查询

  • 不同于结构化查询,全文搜索首先对查询词进行分析,然后根据查询词的分词结果构建查询。这里全文指的是数据类型 text 类型。
  • 结构化搜索关注的是数据是否匹配,结构化搜索一般用于精准匹配。
  • 全文搜索关注的是匹配的程度,而全文搜索用于部分匹配。
  • 默认情况下,match 查询使用的是标准的分词器。该分词器比较适用于英文,如果是中文则按照字进行切分,所以默认的不太合适,需要使用对应的中文分词器。
  • 需要注意的是,match 匹配中参数 operator ,默认是或(or),即进行分词查询时,用or连接。可以设置与(and):
	GET /hotel/_search
	{
    "_source": [
        "amenities"
    ],
    "query": {
        "match": {
            "title": "金都",
            "operator": "and"
        }
    }
}
  • 也可是指匹配程度进行搜索。
	GET /hotel/_search
	{
    "_source": [
        "amenities"
    ],
    "query": {
        "match": {
            "title": "金都",
            "operator": "or",
            "minimum_should_match":"80%" //设置最小匹配度为80%
        }
    }
}
  • Java 客户端
    在这里插入图片描述

16. multi_match 查询

  • 在多个字段里查询关键字。

  • 比如在 title 和 amenities 两个字段里同时搜索 “假日” 关键词。即 title = 假日 or amenities = 假日。

    GET /hotel/_search
    {
     "_source":["titile","amenities"],
     "query":{
     	"multi_match":{
     		"query":"假日",
     		"fields":[
     			"title",
     			"amenities"
     		]
     	}
     }
    }
    
  • Java 客户端

    在这里插入图片描述

17. match_phrase 查询

  • 用于搜索确切的短语或邻近的词语。

  • 假设在标题里搜索 “文雅酒店”,希望酒店标题中的“文雅”与“酒店”紧邻并且“文雅”在“酒店”前面,则使用 match_phrase 查询。

    GET /hotel/_search
    {
    	"query"{
    		"match_phrase":{
    			"title": "文雅酒店"
    		}
    	}
    }
    
  • 如果有文档title 为文雅精品酒店,则无法匹配,因为默认间隔阈值为1,文雅精品酒店中文雅和酒店差2。可以设置最大阈值

    GET /hotel/_search
    	{
    		"query"{
    			"match_phrase":{
    				"title": {
    					"query":"文雅酒店",
    					"slop":2
    				}
    			}
    		}
    	}
    
  • Java 客户端

    在这里插入图片描述

18. 地理位置查询

  • 地理点(geo_point)字段类型查询方式有三种:
    • geo_distance 查询:需要用户指定一个坐标点,在指定的距离该店的范围后,ES 即可查询到响应的文档。
    • geo_bounding_box 查询:提供的是矩形内的搜索。需要用户给出左上角和右下角的顶点地理坐标。
    • geo_polygon 查询:支持多边形内的文档搜索。
  • 假设 北京天安门的经纬度为[116.4039,39.915142],以下使用 geo_distance 查询找到天安门 5KM 范围内的酒店。
GET /hotel/_search
{
    "_source": [
        "title",
        "city",
        "location"
    ],
    "query": {
        "geo_distance": {
            "distance": "5KM",				//设置距离范围为5KM
            "location": {					//设置中心点经纬度
                "lat": "39.915142",			//设置纬度
                "lon": "116.4039"			//设置经度
            }
        }
    }
}
  • Java 客户端:

    在这里插入图片描述

    • geo_bounding_box :给其左上角经纬度:[116.457044,39.922821],右下角顶点的经纬度为:[116.479466,39.907104]。
    {
        "_source": [
            "title",
            "city",
            "location"
        ],
        "query": {
            "geo_bounding_box": {
                "location": {
                    "top_left": {
                        "lat": "39.922821",
                        "lon": "116.457044"
                    },
                    "bottom_right": {
                        "lat": "39.907104",
                        "lon": "116.479466"
                    }
                }
            }
        }
    }
    
  • geo_polygon :比如求三角形内的文档,给出三个顶点经纬度:[116.417088,39.959829],[116.432035,39.960271],[116.421399,39.965802]

    {
        "_source": [
            "title",
            "city",
            "location"
        ],
        "query": {
            "geo_polygon": {
                "location": {
                    "points": [
                        {
                            "lat": "39.959829",
                            "lon": "116.417088"
                        },
                        {
                            "lat": "39.960272",
                            "lon": "116.432035"
                        },
                        {
                            "lat": "39.965802",
                            "lon": "116.421399"
                        }
                    ]
                }
            }
        }
    }
    
  • Java 客户单:

    在这里插入图片描述

19. 建议搜索

  • 用户每输入一个字符,前端就需要向后端发送一次查询请求对匹配项进项查询,要求后端响应速度比较高。通过协助用户进行搜索,可以避免用户输入错误的关键字,引导用户使用更合适的关键词,提升用户的搜索体验和搜索效率。

  • ES 的 Completion Suggester 是比较合适的。为了使用 Completion Suggester ,其对应的字段类型需要定义为 completion 类型。

  • 假设用户输入 “如家”关键词,需要 ES 给出前缀为该词的酒店查询词,DSL 语句如下:

    GET /hotel/_search
    {
        "suggest": {
            "hotel_zh_sug": { //定义搜索建议名称
                "prefix": "如家", //设置搜索建议的前缀
                "completion": { //设置搜索建议对应的字段名称
                    "field": "query_word"
                }
            }
        }
    }
    
  • 返回结果也有所不同,不是分装在 hits 中,而是分装在 suggest 中。

  • Java 客户端

    在这里插入图片描述

20. 按字段值排序

  • 默认情况下,ES 堆搜索结果的相关性进行降序排序的,有时需要对某个字段进行升序或者降序排序。

  • ES 提供了 sort 子句可以堆数据进行排序。默认对字段进行升序排序的(即不设置了该字段排序但没设置升序还是降序)。

  • 使用 sort 默认情况下是不进行打分的。

  • 先按价格进行降序排序,在按口碑进行降序排序。

    GET /hotel/_search
    {
        "source": [
            "title",
            "price"
        ],
        "query": {
            "match": {
                "title": "金都"
            }
        },
        "sort": [
            { //按照价格降序排序
                "price": {
                    "order": "desc"
                },
                "praise": { //按照口碑进行降序排序
                    "order": "desc"
                }
            }
        ]
    }
    
  • Java 客户端

    在这里插入图片描述

21. 按地理距离排序

  • 按照文档坐标与指定坐标的距离对结果进行排序。

    {
        "source": [
            "title",
            "price",
            "location"
        ],
        "query": {
            "geo_distance": {
                "distance": "5km", //设置地理位置为5km
                "location": { //设置中心点坐标
                    "lat": "39.915143",
                    "lon": "116.4039"
                }
            }
        },
        "sort": [ //设置排序逻辑
            {
                "_geo_distance": {
                    "lacation": { //设置排序的中心点坐标
                        "lat": "39.915143",
                        "lon": "116.4039"
                    },
                    "order": "asc", //按照距离由近到远
                    "unit": "km", //所使用的距离的计量单位
                    "distance_type": "plane" //排序所使用的距离计算算法
                }
            }
        ]
    }
    
  • Java

    在这里插入图片描述

七、文本搜索

  • 作为一款搜索引擎框架,文本搜索是核心功能。ES 在文本索引的建立和搜索依赖两大组件:
    • Lucene :负责进行倒排索引的物理构建。
    • 分析器:在建立倒排索引前和搜索前堆文本进行分词和语法处理。

1. 文本索引的建立过程

  • 为了完成文本的快速搜索,ES 使用了一种称为 “倒排索引” 的数据结构。倒排索引中的所有词语存储在词典中,每个词语又指向包含它的文档信息列表。

    • 假设需要对下面两个酒店的信息进行倒排索引的创建。
    • 文档 ID 为 001,酒店名称为 “金都嘉怡假日酒店”。
    • 文档 ID 为002,酒店名称为“金都欣欣酒店”
  • 首先,ES 将文档交给分析器进行处理,处理的过程中包括字符过滤、分词和分词过滤,最终的处理结果是文档内容被表示为一系列关键词信息的集合。关键词信息指关键词本身以及它在文档中的位置信息和词性信息,如下图所示:
    在这里插入图片描述

  • 其次,ES 根据分析结果建立文档-词语矩阵,用以表示词语和文档的包含关系。

    在这里插入图片描述

  • ES 会遍历文档-词语矩阵中的每一个词语,然后将包含该词语的文档信息与该词语建立一种映射关系。映射关系中的词语集合叫作 Term Dictionary,即“词典”。映射中的文档集合信息不仅包含文档ID,还包含词语在文档中的位置和词频信息,包含这些文档信息的结构叫作 Postion List。需要一种高效的数据结构堆映射关系中的词语集合进行索引,这种结构叫作 Term Index,上述三种结构结合在一起就成了 ES 的倒排索引。

  • 倒排索引与三者之间的逻辑关系如图所示:

    在这里插入图片描述

  • 本例中的倒排序索引如图所示:

    在这里插入图片描述

2. 文本的搜索过程

  • 在 ES 中,一般使用 match 查询对文本字段进行搜索。match 查询过程一般分为如下几步:
    1. ES 将查询的字符串传入对应的分析器中,分析器的主要作用是对查询文本进行分词,并把分词后的每个词语变换为对应的底层 lucene term 查询。
    2. ES 用 term 查询在倒排索引中查找每个 term,然后获取一组包含该term的文档集合。
    3. ES 根据文本相关度对每个文档进行打分计算,打分完毕后,ES 吧文档按照相关性进行倒序排序。
    4. ES 根据得分高低返回匹配的文档。
  • 如图所示为酒店索引中搜索“金都嘉怡”的查询流程。
    在这里插入图片描述
  • ES 分析器先将查询词切分为“金都”和“嘉怡”,然后分别到倒排索引里查询两个词对应的文档列表并获得了文档001和002,然后根据相关性算法计算文档得分并进行排序,最后将文档集合返回给客户端。

3. 使用同义词

  • 用户还可以通过 ES 中的分析器来使用同义词,有两种使用方式:
  1. 在建立索引时指定同义词并构建同义词的倒排索引。
  2. 在搜索时指定字段的 search_analyzer 查询分析器使用同义词。

1. 建立索引时使用同义词

  • 在 ES 内置的分词过滤器中,有一种分词过滤器叫作 synonyms,它是一种支持用户自定义同义词的分词过滤器。以下是使用 IK 分析器和 synonyms 分词过滤器一起定义索引的DSL。

    PUT /hotel
    {
        "settings": {
            "analysis": {
                "filter": { //定义分词过滤器
                    "ik_synonyms_filter": {
                        "type": "synonym",
                        "synonyms": [ //在分词过滤器中定义近义词
                            "北京,首都",
                            "天津,天津卫",
                            "假日,度假"
                        ]
                    }
                },
                "analyzer": { //自定义分析器
                    "ik_analyzer_synonyms": {
                        "tokenizer": "ik_max_word", //指定分词器
                        "filter": [ //指定分词过滤器
                            "lowercase",
                            "ik_synonyms_filter"
                        ]
                    }
                }
            }
        },
        "mapping": {
            "properties": {
                "title": {
                    "type": "text",
                    "analyzer": "ik_analyer_synonyms" //指定索引时使用自定义 的分析器
                }
            }
        }
    }
    
  • 测试搜索:

    GET /hotel/_search
    {
    	"query":{
    		"match":{
    			"title":"首都度假"
    		}
    	}
    }
    
  • 返回结果

    北京金都嘉怡酒店		//字段中的北京与查询词中首都匹配
    文雅假日酒店			//字段中的度假与查询词中假日匹配
    

2.查询时使用同义词

  • 在 ES 内置的分词过滤器中还有个分词过滤器叫作 synonym_graph,它是一种支持查询时用户自定义同义词的分词过滤器。以下是使用 IK 分析器和 synonym_graph 分词过滤器一起定义索引的 DSL:

    PUT /hotel
    {
        "settings": {
            "analysis": {
                "filter": { //定义分词过滤器
                    "IK_synonyms_graph_filter": {
                        "type": "synonym_graph",
                        "synonyms": [ //在分词过滤器中定义近义词
                            "北京,首都",
                            "天津,天津卫",
                            "假日,度假"
                        ]
                    }
                },
                "analyzer": { //自定义分析器
                    "ik_analyzer_synonyms_graph": {
                        "tokenizer": "ik_max_word", //指定分词器
                        "filter": [ //指定分词过滤器
                            "lowercase",
                            "ik_synonyms_graph_filter"
                        ]
                    }
                }
            }
        },
        "mapping": {
            "properties": {
                "title": {
                    "type": "text",
                    "analyzer": "ik_max_word",
                    "search_analyzer":"ik_analyzer_synonyms_graph" //指定查询时使用自定义的分析器
                }
            }
        }
    }                    
    
  • 搜索

    GET /hotel/_sarch
    {
    	"query"{
    		"match":{
    			"title":"首都度假"
    		}
    	}
    }
    
  • 返回结果

    文雅假日酒店
    北京金都嘉怡酒店
    
  • 如果有更新同义词的需求,则只能使用查询时使用同义词的的这中方式。首先需要先关闭当前索引。

    POST /hotel/_close
    
  • 添加一组近义词“精华,豪华”

    PUT /hotel/_settings
    {
        "settings": {
            "analysis": {
                "filter": { //定义分词过滤器
                    "IK_synonyms_graph_filter": {
                        "type": "synonym_graph",
                        "synonyms": [ //在分词过滤器中定义近义词
                            "北京,首都",
                            "天津,天津卫",
                            "假日,度假",
                            "精选,豪华"
                        ]
                    }
                },
                "analyzer": { //自定义分析器
                    "ik_analyzer_synonyms_graph": {
                        "tokenizer": "ik_max_word", //指定分词器
                        "filter": [ //指定分词过滤器
                            "lowercase",
                            "ik_synonyms_graph_filter"
                        ]
                    }
                }
            }
        }
    }
    
  • ES 支持用户将同义词放在文件中,文件的位置必须是在 ${ES_HOME}/config 目录及其子目录下,注意该文件必须存在于 ES 集群中的每一个节点上。在 ${ES_HOME}/config 目录下建立一个目录mydict,然后在该目录下创建名称为 synonyms.dict 的文件。然后在创建酒店索引时,在settings中制定同义词文件及其路径,DSL如下:

    {
        "settings": {
            "analysis": {
                "filter": { //定义分词过滤器
                    "IK_synonyms_graph_filter": {
                        "type": "synonym_graph",
                        "synonyms_path": "mydict/synonyms.dict" //指定同义词文件及其路径
                    }
                },
                "analyzer": { //自定义分析器
                    "ik_analyzer_synonyms_graph": {
                        "tokenizer": "ik_max_word", //指定分词器
                        "filter": [ //指定分词过滤器
                            "lowercase",
                            "ik_synonyms_graph_filter"
                        ]
                    }
                }
            }
        },
        "mapping": {
            "properties": {
                "title": {
                    "type": "text",
                    "analyzer": "ik_max_word",
                    "search_analyzer": "ik_analyzer_synonyms_graph" //指定查询时使用自定义的分析器
                }
            }
        }
    }
    

4. 拼音搜索

  • 在 ES 中可以使用拼音分析器插件进行拼音搜索,插件的项目地址为github地址,该插件对较新的 ES 版本并不支持,需要用户自行进行安装编译。

1. 拼音分析器插件安装

  1. 拉取项目

    git clone https://github.com/medcl/elasticsearch-analysis-pinyin.git
    
  2. 修改该项目pom文件

    在这里插入图片描述

  3. 使用 mvn 编译

    mvn install
    
  4. 会在 ${PROJECT_PATH}/target/release/目录下生产目标文件 elasticsearch-analysis-pinyin-7.10.2.zip。

  5. 在{ES_HOME}/plugins/目录下创建一个名称为 pinyin-analysis 的子目录,然后将上面的文件复制到该目录下。然后进行解压

2. 拼音分析器插件的使用

  • 使用 pinyin 分析器对待测试文本进行分析,DSL如下
POST _analyze
{
	"analyzer":"pinyin",
	"text":"王府井"
}
  • 测试发现 王府井 被切分成拼音 wang、fu、jing及首字母wfj。

  • 也可将拼音分析器应用到索引的字段中。以下示例中将自定义的 ik_pinyin_analyzer 分析器设置为酒店索引中 title 字段的默认分析器。DSL 如下:

    PUT /hotel
    {
        "settings": {
            "analysis": {
                "analyzer": { //自定义分析器
                    "ik_pinyin_analyzer": {
                        "tokenizer": "ik_max_word", //设置分词器为 ik_max_word
                        "filter": [
                            "pinyin_filter"//设置分词器过滤器为pinyin_filter
                        ] 
                    }
                },
                "filter": { //定义分词过滤器
                    "pinyin_filter": {
                        "type": "pinyin", // 分装pinyin分词过滤器
                        "keep_first_letter": true, //设置保留拼音的首字母
                        "keep_full_pinyin": false, //设置保留拼音的全拼
                        "keep_none_chinese": true //设置不保留中文
                    }
                }
            }
        },
        "mapping": {
            "properties": {
                "title": {
                    "type": "text",
                    "analyzer": "ik_pinyin_analyzer" //设置使用自定义分析器
                }
            }
        }
    }
    
  • 搜索关键词 wy,目的是想搜索 “文雅”相关的酒店, DSL 如下

    GET /hotel/_search
    {
    	"query":{
    		"match":{
    			"title":"wy"
    		}
    	}
    }
    
  • 搜索结果有

    文雅精选酒店
    文雅假日酒店
    

5. 高亮显示搜索

  • 使用高亮搜索:

    {
        "query": {
            "match": {
                "title": "金都酒店"
            }
        },
        "hightlight": {		//设置高连搜索的字段
            "fields": {
                "title": {}
            }
        }
    }
    
  • 搜索结果

    ...
    "highlight":{
    	"title":{
    		//使用默认的 HTML 标签 <em></em>标签匹配的词语
    	}
    }
    
  • 默认使用是< em>< /em>标签,可更改标签为 < high>< /high>

    GET /hotel/_search
    {
        "query": {
            "match": {
                "title": "金都酒店"
            }
        },
        "hightlight": {
            "fields": {
                "title": {                  //设置默认使用标签<high></high> 标记匹配词语
                    "pre_tags": "<high",
                    "post_tags": "</high>"
                }
            }
        }
    }
    
  • 选择高亮显示搜索策略

    • plain:精准度比较高的策略,因此它必须将文档全部加载到内存中,并重新进行查询分析,因此在处理大量文档或者大文档是效率比较慢。

    • unified:Lucene Unified Highlighter 来实现,默认情况下,ES 高亮显示的是该策略。

    • fvh:基于向量的高亮搜索策略。更适合在文档中包含大字段的情况(一般超过1MB下使用)。

      GET /hotel/_search
      {
          "query": {
              "match": {
                  "title": "金都酒店"
              }
          },
          "hightlight": {
              "fields": {
                  "title": {
                      "type": "plain" //设置使用plain匹配策略
                  }
              }
          }
      }
      
  • Java 客户单对应代码:

    在这里插入图片描述

6. 拼音纠错

  • 可以使用 ES 进行拼写纠错,首先需要搜集一段时间内用户搜索日志中有搜索结果的查询词,然后单独建立一个纠正词索引。当用户进行搜索时,如果在商品索引中没有匹配到结果,则在纠正词索引中进行匹配,如果有匹配结果则给出匹配词,并给出该匹配词对应的商品结果,如果没有匹配结果则告知用户没有搜索到商品。

  • 在 ES 中进行纠错匹配是使用 fuzzy_match 搜索,该搜索使用编辑距离和倒排索引相结合的形式完成纠错,倒排索引前面已经介绍过了,什么叫编辑距离呢?词语A经过多次编辑后和词语B相等,编辑的词语就叫做编辑距离,可以这样定义一次编辑,替换一个字符,或删除一个字符,或插入一个字符,或交换两个字符的位置。

  • 假设有词语A为“景王”,词语B为“王府井”,词语A进行如下编辑才能等于词语B。

    • 将“景王”两个字符交换位置,变为“王景”。
    • 在“王景”中间添加“府”,变成“王府景”。
    • 将“王府景”中的“景”替换为“井”。
      在这里插入图片描述
  • 经过上述编辑,A和B相等,那么词语A和词语B编辑距离为3。一般情况下,绝大数距离不超过2。

    PUT /error_correct
    {
        "mappings": {
            "properties": {
                "hot_word": { //设置hot_word的类型为text,并指定分析器为ik_max_word
                    "type": "text",
                    "analyzer": "ik_max_word"
                }
            }
        }
    }
    
  • 搜索“王府景”时,指定编辑距离为1的搜索纠错的DSL。

    GET /error_correct/_search
    {
        "query": {
            "match": {
                "hot_word": {
                    "query": "王府景",
                    "operator": "and",
                    "fuzziness": 1 //指定编辑距离为1
                }
            }
        }
    }
    
  • 搜索结果下

    王府景
    王府井
    成府路
    
  • 因为成府路被切分成 成、府、路,而王府景被切分成王府、景,王府和府编辑距离为1.所以匹配到了。

八、搜索排序

1. 查询时 boost 参数的设置

  • 在 ES 中可以通过查询的boost 值对某个查询设定其权重。默认情况下 boost 值为1。但是,设置为2,不到表匹配的文档评分是原来的两倍,而是代表匹配该查询的文档得分相对于其他文档得分被提升了。

  • boost 值的设置只限定在 term 查询和类 match 查询中,其他类型的查询不能使用 boots 设置。当值在 0~1 时表示对权重起负向作用,当该值大于1时表示对权重起正向作用。

  • 查询标题中包含 “金都” 或者 “文雅” 的酒店文档的 DSL。

    GET /hotel/_search
    {
        "query": {
            "bool": {
                "should": [
                    {
                        "match": {
                            "title": {
                                "query": "金都"
                            }
                        }
                    },
                    {
                        "match": {
                            "title": {
                                "query": "文雅"
                            }
                        }
                    }
                ]
            }
        }
    }
    
  • 默认各个子查询的 boost 值为1,也就是说两个 match 查询是平等的。如果我们想匹配 “金都” 的这些文档排序分值高点,可以设定其 boost 值高一点。

    GET /hotel/_search
    {
        "query": {
            "bool": {
                "should": [
                    {
                        "match": {
                            "title": {
                                "query": "金都",
                                "boost": 2
                            }
                        }
                    },
                    {
                        "match": {
                            "title": {
                                "query": "文雅"
                            }
                        }
                    }
                ]
            }
        }
    }
    
  • Java 客户单对应代码:

    在这里插入图片描述

2. boosting 查询

  • 虽然使用 boost 值可以对查询的权重进行调整,但是仅限于 term 查询的和类 match 查询,有时需要调整更多类型的查询,如搜索酒店时,需要将放假低于 200 的酒店权重降低,此时可能需要用到 range 查询,但是 range 查询不能使用 boost 参数,这时可以使用 ES 的 boosting 查询进行分装。

  • ES 的 boosting 查询分为两部分:一部分是 positive 查询,代表正向查询,另一部分是 negative 查询,代表负向查询。negative_boost 参数设置负向查询的权重系数,该值范围为 0~1,最终文档得分:正向匹配值 + 负向匹配值 * negative_boost。

  • 对房价低于200元的酒店进行降权处理 DSL:

    GET /hotel/_search
    {
        "query": {
            "boosting": {
                "positive": {
                    "match": {
                        "title": {
                            "query": "金都"
                        }
                    }
                },
                "negative": { //设置负面查询
                    "range": {
                        "price": {
                            "ite": 200
                        }
                    }
                },
                "negative_boost": 0.2 //设置降低的权重值
            }
        }
    }
    
  • 在以上查询中,使用布尔查询将 “房价低于200”和 “满房状态“ 的酒店分装到了一个布尔查询中然后放入 negative 查询中:

    GET /hotel/_search
    {
        "query": {
            "boosting": {
                "positive": {
                    "match": {
                        "title": {
                            "query": "金都"
                        }
                    }
                },
                "negative": { //扩展 negative 查询,增加更多条件
                    "bool": {
                        "should": [
                            {
                                "range": {
                                    "price": {
                                        "lte": 200
                                    }
                                }
                            },
                            {
                                "term": {
                                    "full_room": {
                                        "value": "true"
                                    }
                                }
                            }
                        ]
                    }
                },
                "negative_boost": 0.2 //设置降低的权重值
            }
        }
    }
    
  • Java 客户单对应代码:

    在这里插入图片描述

九、聚合

  • 当用户使用搜索引擎完成搜索之后,在展示结果中需要进一步的筛选,而筛选的维度需要根据当前的搜索结果进行汇总,这就用到了聚合技术。
  • ES 支持丰富的集合操作,不仅可以使用聚合功能对文档进行技术,还可以计算文档平均值、最大值和最小值等。ES 还提供了桶聚合的功能,以便于对多维度数据进行聚合。

1.聚合指标

  • 时用 avg 字句进行平均值的聚合时。如果我们不需要返回具体的文档信息,可以将返回的文档个数设置为0。这样既可以让结果看起来更整洁,有可以提高查询速度。DSL 如下:

    GET /hotel/_search
    {
        "size": 0,
        "aggs": {
            "my_agg": { // 聚合名称
                "avg": {
                    "field": "price" //计算文档的平均价格
                }
            }
        }
    }
    
  • 返回结果如下:索引中5个文档全部命中。my_agg 是聚合的名称,value值对应具体聚合结果,即酒店的平均价格。

    {
        ...
        "hits": {
            "total": {
                "value": 5,
                "relation": "eq"
            },
            "max_score": null,
            "hits": []
        },
        "aggregations": {
            "my_agg": {
                "value": 514
            }
        }
    }
    
  • 与平均值类似、最大值、最小值以及加和值分别使用max、min和sum字句进行聚合,这里不在赘述。

  • 平均值的Java客户端代码:

    在这里插入图片描述

  • 为了避免多次请求,ES 还提供了 stats 聚合。stats 聚合可以将对应字段的最大值、最小值、平均值及加和值一起计算并返回计算结果。DSL 如下:

    GET /hotel/_search
    {
        "size": 0,
        "aggs": {
            "my_agg": {
                "stats": {
                    "field": "price"
                }
            }
        }
    }
    
  • 查询结果如下:

    {
    	...
        "hits": {
            "total": {
                "value": 5,
                "relation": "eq"
            },
            "max_score": null,
            "bits": []
        },
        "aggregations": {
            "my_agg": {
                "count": 4, //文档数量
                "min": 200.0, //聚合的价格最小值
                "max": 800.0, //聚合的价格最大值
                "avg": 514.0, //聚合的价格平均值
                "sum": 2056.0 //聚合的加和值
            }
        }
    }
    
  • 对应的 Java客户端代码:

    在这里插入图片描述

  • value_count 聚合,该聚合用于统计字段非空值的个数(比如有些文档某字段缺失)。下面的DSL 使用value_count 聚合统计了price字段中非空值的个数。

    / GET /hotel/_search
    {
        "size": 0,
        "aggs": {
            "my_agg": {
                "value_count": { //统计price字段中非空值的个数
                    "field": "price"
                }
            }
        }
    }
    
  • 返回结果:

    {
        "hits": {
            "total": {
                "value": 5,
                "relation": "eq"
            },
            "max_score": null,
            "hits": []
        },
        "aggregations": {
            "my_agg": { //price 字段中非空值的个数
                "value": 4
            }
        }
    }
    
  • Java 客户端代码:

    在这里插入图片描述

  • 值得注意的是,如果判断的字段是数组类型,则 value_count 统计的是符合条件的所有文档中该字段数组中元素个数的总和,而不是数组的个数总和。

  • 如果在进行聚合操作时,需要对空值字段的数据进行默认值填充,可是使用如下查询:

    GET /hotel/_search
    {
        "size": 0,
        "aggs": {
            "my_agg": {
                "sum": {
                    "field": "price",
                    "missing": 100 //计算加和值时将price字段中的空值用100代替
                }
            }
        }
    }
    
  • Java 客户端代码:

    在这里插入图片描述

2. 桶聚合

  • 有时需要根据某些维度进行聚合,比如更具酒店是否满房、按照城市、标签等信息进行聚合。按一个维度对文档进行聚合就是单维度桶聚合。按照多个维度进行聚合就是多维度桶嵌套聚合。

  • 在桶聚合时,聚合的桶也需要匹配,匹配的方式有 terms,filter 和 ranges 等。

  • 按照城市进行聚合的DSL

    GET /hotel/_search
    {
        "size": 0,
        "aggs": {
            "my_agg": {
                "terms": {
                    "field": "city"
                }
            }
        }
    }
    
  • Java 客户端代码:

    在这里插入图片描述

  • range 的桶聚合:DSL

    GET /hotel/_search
    {
        "size": 0,
        "aggs": {
            "my_agg": {
                "range": {
                    "field": "price",
                    "ranges": [ //多个范围桶
                        {
                            "to": 200 //不指定 from ,默认 from 为0
                        },
                        {
                            "from": 200,
                            "to": 500
                        },
                        {
                            "from": 500 //不指定to,默认to为该字段最大值
                        }
                    ]
                }
            }
        }
    }
    
  • Java 客户端代码:

    在这里插入图片描述

  • 有时还需要对单维度桶指定聚合指标,聚合指标单独使用子 aggs 进行封装,该 aggs 子句的使用方式和上一节介绍的聚合指标相同。按照 城市维度进行聚合,统计各个城市的平均酒店价格:

    GET /hotel/_search
    {
        "size": 0,
        "aggs": {
            "my_agg": { //单维度聚合名称
                "terms": { //定义单维度桶
                    "field": "city"
                },
                "aggs": { //用于封装单维度桶下的聚合指标
                    "my_sum": { //聚合指标名称
                        "sum": { //对price字段进行加和
                            "field": "price",
                            "missing": 200
                        }
                    }
                }
            }
        }
    }
    
  • Java 客户端代码:

    在这里插入图片描述

  • 有时候我们需要多个维度的嵌套聚合,ES 支持嵌套聚合,可是使用 aggs 字句进行子桶的继续嵌套,指标放在最里面的子桶内,DSL 如下:

    GET /hotel/_search
    {
        "size": 0,
        "aggs": {
            "group_city": { //多维度桶名称
                "terms": {
                    "field": "city"
                },
                "aggs": { //单维度桶
                    "group_fulll_room": {
                        "terms": {
                            "field": "full_room"
                        },
                        "aggs": { //聚合指标
                            "my_sum": {
                                "avg": {
                                    "field": "price",
                                    "missing": 200
                                }
                            }
                        }
                    }
                }
            }
        }
    
  • Java 客户端代码:

    		//多维度桶嵌套聚合
            //按城市聚合的名称
            String aggNameCity = "my_terms_city";
            //定义 terms 聚合,指定字段为城市
            TermsAggregationBuilder termsAggCity = AggregationBuilders.terms(aggNameCity).field("city");
            //按满房状态的聚合的名称
            String aggNameFullRoom = "my_terms_full_room";
            //定义 terms 聚合,指定字段为满房状态
            TermsAggregationBuilder termsArrFullRoom = AggregationBuilders.terms(aggNameFullRoom).field("full_room");
            //sum 聚合的名称
            String sumAggName = "my_sum";
            //定义sum 聚合,指定字段为价格
            SumAggregationBuilder sumAgg = AggregationBuilders.sum(sumAggName).field("price");
            
            //定义聚合的父子关系
            termsArrFullRoom.subAggregation(sumAgg);
            termsAggCity.subAggregation(termsArrFullRoom);
            //添加聚合
            searchSourceBuilder.aggregation(termsArrFullRoom);
            searchRequest.source(searchSourceBuilder);
            try {
                SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);
                Aggregations aggregations = search.getAggregations();
                Terms terms = aggregations.get(aggNameCity);
                //遍历第一层 bucket
                for (Terms.Bucket bucket : terms.getBuckets()) {
                    //获取第一层 bucket 名称
                    String termsKeyCity = bucket.getKey().toString();
                    Terms termsFullRom = bucket.getAggregations().get(aggNameCity);
                    //遍历第二层bucket
                    for (Terms.Bucket termsFullRomBucket : termsFullRom.getBuckets()) {
                        //获取第二层 bucket 名称
                        String termsKeyFullRoom = termsFullRomBucket.getKeyAsString();
                        //获取聚合指标
                        Sum sum = termsFullRomBucket.getAggregations().get(sumAggName);
                        //获取聚合指标名称
                        String key = sum.getName();
                        //获取聚合指标值
                        double sumVal = sum.getValue();
                    }
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
    
  • 地理距离聚合。用户可以使用 geo_distance 聚合进行地理距离聚合,通过 field 参数来设置距离计算的字段,可以在 origin字句中设定距离的原点,通过 unit 参数来设置距离的单位,可以选择 mi 和 km,分别表示米和千米。ranges 字句用来对距离进行阶段性的分组,该字句和使用方式和前面介绍的range 聚合类似。DSL使用 geo_distance 聚合进行地理距离聚合的方法。

    GET /hotel/_search
    {
        "size": 0,
        "aggs": {
            "my_agg": {
                "geo_distance": {
                    "field": "location", //指定聚合的中心点经纬度
                    "origin": {
                        "lat": 39.915143,
                        "lon": 116.4039
                    },
                    "unit": "km", //指定聚合时的距离计量单位
                    "ranges": [ //指定每一个聚合桶的距离范围
                        {
                            "to": 3
                        },
                        {
                            "from": 3,
                            "to": 10
                        },
                        {
                            "from": 10
                        }
                    ]
                }
            }
        }
    }
    
  • 在上述的 DSL 中,给定一个地理位置,此处使用 ranges 聚合对距离该位置的酒店划分了3个分组的桶;第一个桶为3km范围内,第二个桶为3~10km;第3个桶为大于等于10km。

  • 也可以指定聚合指标进行地理距离聚合,下面的DSL将按照bucket 分桶聚合酒店的最低价格:

    GET /hotel/_search
    {
        "size": 0,
        "aggs": {
            "my_agg": {
                "geo_distance": {
                    "field": "location", //指定聚合的中心点经纬度
                    "origin": {
                        "lat": 39.915143,
                        "lon": 116.4039
                    },
                    "unit": "km", //指定聚合时的距离计量单位
                    "ranges": [ //指定每一个聚合桶的距离范围
                        {
                            "to": 3
                        },
                        {
                            "from": 3,
                            "to": 10
                        },
                        {
                            "from": 10
                        }
                    ]
                },
                "aggs": {
                    "my_min": { //指定聚合指标
                        "min": { //聚合指标名称
                            "field": "price", //计算每个桶内price字段的最小值
                            "missing": 100
                        }
                    }
                }
            }
        }
    }
    
  • Java 客户端代码:

    在这里插入图片描述

3. 聚合方式

  • 直接聚合:指的是聚合时的DSL没有query子句,是直接对索引内的所有文档进行聚合。前面介绍的都属于直接聚合。

  • 与直接聚合相对应,这种查询方式需要增加query子句,query子句和普通的query查询没有区别,参加聚合的文档必须匹配query查询。DSL 如下:

    {
        "size": 0,
        "query": { //指定查询query逻辑
            "term": {
                "city": {
                    "value": "北京"
                }
            }
        },
        "aggs": { //指定聚合逻辑
            "my_agg": {
                "avg": {
                    "field": "price"
                }
            }
        }
    }
    
  • Java 客户端代码如下:

    在这里插入图片描述

  • 前过滤器:有时需要对聚合条件进一步地过滤,但是又不能影响当前的查询条件。例如用户进行酒店搜索时的搜索条件是天津的酒店,但是聚合时需要将非满房的酒店平均价格进行聚合并展示给用户。此时不能变更用户的查询条件,需要在聚合子句中添加过滤条件。下面的DSL展示了在聚合时使用过滤条件的用法:

    GET /hotel/_search
    {
        "size": 0,
        "query": { //指定查询query逻辑
            "term": {
                "city": {
                    "value": "北京"
                }
            }
        },
        "aggs": {
            "my_agg": {
                "filter": { //指定过滤器逻辑
                    "term": {
                        "full_room": false
                    }
                },
                "aggs": { //指定聚合逻辑
                    "my_avg": {
                        "avg": {
                            "field": "price"
                        }
                    }
                }
            }
        }
    }
    
  • 返回结果:

{
	...
    "hits": {
        "total": {
            "value": 2,
            "relation": "rq"
        },
        "max_score": null,
        "hits": []
    },
    "aggregations": {
        "my_agg": {
            "doc_count": 1, //只有文档004没有被过滤
            "my_avg": {
                "value": 500.0
            }
        }
    }
}
  • 通过上述结果可以知道,满足查询条件的文档个数为2,命中的文档为004和005,但是在聚合时要求匹配非满足的酒店,只有文档004满足聚合条件,因此酒店的平均值为文档004的price字段值。

  • Java客户端代码:
    在这里插入图片描述

  • 后过滤器:根据条件进行数据查询,但是聚合的结果集不受影响。例如在酒店搜索场景中,用户的查询词为“假日”此时应该展现标题中带有“假日”的酒店。但是在该页面中,如果还希望给用户呈现出全国各个城市的酒店的平均价格,这时可以使用 ES 提供的后过滤器功能。该过滤器是现在查询和聚合之后进行过滤的,因此它的过滤条件对聚合没有影响。DSL 如下:

    GET /hotel/_search
    {
        "size": 0,
        "query": {
            "match": {
                "title": "假日"
            }
        },
        "post_filter": {
            "term": {
                "city": "北京"
            }
        },
        "aggs": {
            "my_agg": {
                "avg": {
                    "field": "price",
                    "missing": 200
                }
            }
        }
    }
    
  • 在上面的查询中,使用 match 匹配title包含“假日”的酒店,并且查询出这些酒店的平均价格,最后使用 post_filter 设置后过滤器的条件,将酒店的诚实锁定为 "北京”。

  • 返回结果:

    ...
    {
        "hits": {
            "total": {
                "value": 3,
                "relation": "eq"
            },
            "max_score": null,
            "hits": []
        },
        "aggregations": {
            "my_agg": { //聚合时酒店的诚实锁定为“北京”
                "value": 364.0
            }
        }
    }
    
  • 更具查询结果可知,match 查询命中了4个文档,对这4个文档的price字段取平均值为364,最后通过 post_filter 将其中的文档004过滤掉,因此hits子句中的total数量为3.

  • Java客户端代码为:

    在这里插入图片描述

4. 聚合排序

  • ES 提供的 sort 字句进行自定义排序,有多种排序方式供用户选择;可以按照聚合后的文档计数的大小进行排序;可以按照聚合后的某个指标进行排序;还可以按照每个组的名称进行排序。下面将介绍3中排序方式。

1. 按文档计数排序

  • 按照每个组聚合后的文档数量进行排序的场景。此时可以使用_count 来引用每组聚合的文档计数排序排序。

  • 按照城市的酒店平均价格进行聚合,并按照聚合后的文档计数进行升序排序的请求。

    GET /hotel/_search
    {
        "size": 0,
        "aggs": {
            "group_city": {
                "terms": {
                    "field": "city",
                    "order": { //按照文档计数进行升序排列
                        "_count": "asc"
                    }
                },
                "aggs": {
                    "my_avg": {
                        "avg": { //使用价格平均值作为聚合指标
                            "field": "price",
                            "missing": 200
                        }
                    }
                }
            }
        }
    }
    
  • Java 客户端代码:

    在这里插入图片描述

2. 按聚合指标排序

  • 按照每个组聚合后的指标进行排序。比如按照城市的酒店平均价格进行聚合,并按照聚合后的平均价格进行升序排序的请求。

    GET /hotel/_search
    {
        "size": 0,
        "aggs": {
            "group_city": {
                "terms": {
                    "field": "city",
                    "order": { //按照文档计数进行升序排列
                        "my_avg": "asc"
                    }
                },
                "aggs": {
                    "my_avg": {
                        "avg": { //使用价格平均值作为聚合指标
                            "field": "price",
                            "missing": 200
                        }
                    }
                }
            }
        }
    }
    
  • Java 客户端代码:

    在这里插入图片描述

3. 按分组 key 排序

  • 按照每个分组的组名称排序的场景。此时可以使用_key来引用分组名称。比如:按照城市的酒店平均价格进行聚合,并按照聚合后的分组名称进行升序排序的请求:

    GET /hotel/_search
    {
        "size": 0,
        "aggs": {
            "group_city": {
                "terms": {
                    "field": "city",
                    "order": { //按照文档计数进行升序排列
                        "_key": "asc"
                    }
                },
                "aggs": {
                    "my_avg": {
                        "avg": { //使用价格平均值作为聚合指标
                            "field": "price",
                            "missing": 200
                        }
                    }
                }
            }
        }
    }
    
  • Java 客户端代码:
    在这里插入图片描述

4. 聚合分页

  • ES 提供的 Top hits 聚合和 Collapse可以完成分页。

1. Top hits 聚合

  • Top hits 聚合指的是聚合时在每个分组内部按照某个规则选出前 N 个文档进行展示。例如,搜索“金都”时,如果希望按照城市分组,每组 按照匹配分数降序展示3条文档数据,DSL 如下:

    {
        "size": 0,
        "query": {
            "match": {
                "title": "金都"
            }
        },
        "aggs": {
            "group_city": {
                "terms": { //按照城市进行桶聚合
                    "field": "city"
                },
                "aggs": {
                    "my_avg": {
                        "top_hits": { //指定返回每个桶的前3个文档
                            "size": 3
                        }
                    }
                }
            }
        }
    }
    
  • Java 客户端代码:

    在这里插入图片描述

2. Collapse 聚合

  • 当在索引中有大量数据命中时,Top hits 聚合存在效率问题,并且需要用户自行排序。针对上述问题。ES 推出了 Collapse 聚合,即用户可以在 collapse 子句中指定分组字段,匹配 query 的结果按照该字段进行分组,并且每个分组中按照得分高低展示组内的文档。当用户在 query 子句外指定 from 和 size 时,将作用在 Collapse 聚合之后,即此时的分页是作用在分组之后的,以下DSL 展示了 Collapse 聚合的用法。

    GET /hotel/_search
    {
        "from": 0, //指定分页的起始位置
        "size": 5, //指定每页返回的数量
        "query": { //指定查询的query逻辑
            "match": {
                "title": "金都"
            }
        },
        "collapse": { //指定按照城市进行Collapse聚合
            "field": "city"
        }
    }
    
  • Java客户端代码:

    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/177103.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

学习shell与shell编程 vi与vim

Linux配置文件都是以ASCII的纯文本形式存在。 为什么学习vi 1)UnixLike系统都会内置vi文本编辑器&#xff0c;其他的文本编辑器则不一定存在 2)许多软件的编辑接口都会主动调用vi 3)vi具有程序编辑的能力&#xff0c;可以主动以字体颜色辨别语法的正确性 4)程序简单&#…

webgl纹理贴图机制

文章目录前言纹理图片大小规范纹理坐标系统贴图流程JavaScript部分齐次坐标—uv坐标数据准备加载外部纹理图像纹理配置加载着色器部分顶点着色器片元着色器完整示例使用多张纹理着色器接受两个纹理单元封装纹理配置赋值函数完整示例总结前言 在计算机图形学中&#xff0c;为了…

HTML+CSS+JS制作炫酷【烟花特效】

文章目录制作炫酷烟花特效一、普通烟花(分散形)HTML代码CSS代码JS代码二、圆形烟花HTML代码CSS代码JS代码三、爱心形烟花HTML代码CSS代码JS代码四、源码获取在线下载制作炫酷烟花特效 &#x1f4a1;本篇内容使用htmlcssjs制作鼠标点击出现烟花效果&#xff0c;分别介绍了分散型…

python-测试代码

1. 测试函数get_name.pydef combination(first, last):将姓名组合在一起name first lastreturn name.title()hello_world.pyfrom get_name import combinationprint("Enter q to quit!") while True:first input(Please input your first name: )if first q:b…

理光Aficio MP C2500扫描到文件夹设置方法

首先在需要接收扫描文件的电脑上设置共享文件夹。 注&#xff1a; &#xff08;1&#xff09;文件夹的名字最好简单一点&#xff0c;比如&#xff1a;scan、123等等&#xff1b; &#xff08;2&#xff09;文件夹的共享权限最好能设置为最大&#xff08;WindowsXP、Windows200…

Future、CompletableFuture概述

1.同步和异步 &#xff08;1&#xff09;同步&#xff1a;需要等待结果返回&#xff0c;才能继续运行 &#xff08;2&#xff09;异步&#xff1a;不需要等待结果返回&#xff0c;就能继续运行 &#xff08;3&#xff09;异步设计&#xff1a;多线程可以让方法执行变为异步(比…

第四章必备前端基础知识-第二节3:CSS盒模型和浮动

文章目录一&#xff1a;盒模型&#xff08;1&#xff09;border&#xff08;2&#xff09;padding&#xff08;3&#xff09;margin二&#xff1a;flex布局一&#xff1a;盒模型 盒模型&#xff1a;在HTML中&#xff0c;每个标签&#xff08;或元素&#xff09;相当于是一个盒…

Mybatis和Jpa

这里写目录标题1.Mybatis1.1 JDBC的缺点1.2 Mybatis的整体架构1.3 入门案例1.3.1 问题:无法连接到数据库服务器1.4 动态代理实现Mapper1.5 mybatis-config.xml配置1.5.1 properties属性读取外部资源1.5.2 settings设置1.5.3 typeAliases1.5.4 typeHandlers&#xff08;类型处理…

【Substance Designer】基础操作和节点学习记录

写在前面 这个记录稍微有点杂&#xff0c;大概是庄懂的技术美术入门课(美术向)-直播录屏-第20课和一些基础操作的记录合集吧&#xff01; 补充 学习发现&#xff0c;基础的节点是需要学习和记录的&#xff0c;但是真正用起来还是要多用多练&#xff01;所以这种简单的记录节点…

YOLOv5/v7 引入 RepVGG 重参数化模块

本篇博文代码出自YOLOv5-lite &#xff0c;YOLOv5-lite的作者在CSDN的账号是 pogg_ &#xff0c;大家可以关注一下&#xff0c;这也是一位在开源项目上做了很多工作的博主。 RepVGG的原理和融合推导过程可以看我的这篇博文&#xff1a;RepVGG&#xff1a;让VGG风格的ConvNets再…

机制设计原理与应用(三)Screening

文章目录3 Screening3.1 为单个不可分割的项目定价3.1.1 对θ\thetaθ的假设3.1.2 问题描述3.1.3 特性3.2 为无限可分的项目定价3.2.1 对θ\thetaθ的假设3.2.3 特性3.2.4 收益最大化3.2.5 最优解决方案3 Screening Screening theory&#xff1a;机制设计理论可以被看作是其多…

Cadence PCB仿真使用Allegro PCB SI生成振铃ringing仿真报告及报告导读图文教程

🏡《Cadence 开发合集目录》   🏡《Cadence PCB 仿真宝典目录》 目录 1,概述2,生成报告3,报告导读4,总结1,概述 本文简单介绍使用Allegro PCB SI生成网络的振铃性能评估的报告的方法,及振铃ringing报告要点导读。 2,生成报告 第1步,选择需要生成报告的网络,然后…

第二章 ArcGIS数据和地理数据库

文章目录第一节 ArcGIS和4D数据基本知识1 4D数据介绍1.1 DLG1.2 DEM1.3 DOM1.4 DRG1.5 4D表现2 ArcGIS的数据和4D数据对应3 栅格数据3.1 查看帮助3.2 空间分辨率3.3 分辨率与比例尺换算3.4 栅格数据介绍——cellsize3.5 栅格数据波段3.6 栅格格式4 栅格数据改变分辨率5 转换栅格…

【 uniapp - 黑马优购 | 登录与支付(2)】如何实现三秒后跳转和微信支付

个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名大二在校生&#xff0c;讨厌编程&#x1f38b; &#x1f43b;‍❄️个人主页&#x1f947;&#xff1a;小新爱学习. &#x1f43c;个人WeChat&#xff1a;见文末 &#x1f54a;️系列专栏&#xff1a;&#x1f5bc;…

Ubuntu20.04+MAVROS+PX4+Gazebo安装教程

Ubuntu20.04MAVROSPX4Gazebo安装PX4步骤安装MAVROS安装QGCPX4仿真安装PX4步骤 从github上clone源码 git clone https://github.com/PX4/PX4-Autopilot.git --recursive进入PX4-Autopilot文件夹&#xff0c;继续下载未下载完的组件 cd PX4-Autopilot/ git submodule update -…

flowable使用 act_hi_xxx

HistoryService 流程历史信息 act_hi_procinst : 历史流程信息&#xff0c;&#xff0c;如果流程执行完了&#xff0c;end_time_ 和 duration不为null // 没有执行完的List<HistoricProcessInstance> list historyService.createHistoricProcessInstanceQuery().unfi…

uniapp封装并全局挂载request请求

前言 日常开发中,前端项目中需要调用服务端api完成页面渲染,uniapp提供的请求api:uni.request相对繁琐;另外服务端提供的不同api仅子路径不同,api域名以及根路径都是相同的,一旦接口api变更,需要更改地方就会很多.鉴于以上可以将uni.request进行封装,简化开发. 目前uniapp项…

MySQL(四):B+树索引、聚簇索引、二级索引、联合索引

目录一、B树索引1.1 在没有索引时进行查找记录1.2 索引方案1.3 InnoDB中的索引方案二、聚簇索引三、二级索引四、联合索引五、InnoDB中B树索引的注意事项5.1 根页面的位置不会改变5.2 内节点中目录项记录的唯一性5.3 一个页面至少容纳两条记录一、B树索引 数据库中的用来存储数…

MySQL进阶篇之索引1

02、索引 2.1、索引概述 1、介绍 索引&#xff08;index&#xff09;是帮助MySQL高效获取数据的数据结构&#xff08;有序&#xff09;。在数据之外&#xff0c;数据库系统还维护着满足特定查找算法的数据结构&#xff0c;这些数据结构以某种方式引用&#xff08;指向&#…

Cepstral Analysis 倒谱分析

源过滤器分离 倒谱分析是另一种将声道滤波器响应与激励分开的方法&#xff08;如线性预测&#xff09; 它基于以下观察&#xff1a;语音信号的频谱是激励频谱和声道频率响应的乘积 可以使用log将乘法转换为加法&#xff0c;因此&#xff0c;“对数频谱”可以看作是对数激励频…