Springboot搭配ElasticSearch常用api以及面试题

Springboot搭配ElasticSearch常用api


ElasticSearch是什么

Elasticsearch (ES)是一个基于Lucene构建的开源、分布式、RESTful 接口全文搜索引擎。 Elasticsearch 还是一个分布式文档数据库,其中每个字段均是被索引的数据且可被搜索,它 能够扩展至数以百计的服务器存储以及处理PB级的数据。它可以在很短的时间内在存储、搜 索和分析大量的数据。它通常作为具有复杂搜索场景情况下的核心发动机。 es是由java语言编写的


ElasticSearch能干什么

  • 他有强大的查询、分析、聚合能力使得它与数据库有良好的配合性
  • 从数据获取,存储计算到可视化,ES 开发了一整套解决方案,Logstash 、Beat 负责数据抓取,ES 负责存储计算,kibana 对数据进行展示分析。另外还有收费的 X-Pack 可以实现安全、告警、监控和 ML 等更丰富的功能。ES 在搜索、日志分析、指标分析和安全分析等领域应用广泛。从前端到后端到数据分析,从云服务到最流行的机器学习,ES 都提供了一整套解决方案

ElasticSearch有什么好处

  • 横向可扩展性:只需要增加台服务器,做一点儿配置,启动一下Elasticsearch就可以并入集群。

  • 分片机制提供更好的分布性:同一个索引分成多个分片(sharding), 这点类似于HDFS的块机制;分而治之的方式可提升处理效率。

  • 高可用:提供复制( replica) 机制,一个分片可以设置多个复制,使得某台服务器在宕机的情况下,集群仍旧可以照常运行,并会把服务器宕机丢失的数据信息复制恢复到其他可用节点上。

  • 使用简单:共需一条命令就可以下载文件,然后很快就能搭建一一个站内搜索引擎。


ElasticSearch为什么数据库不如它

  • 倒排索引 倒排表以字或词为关键字进行索引,表中关键字所对应的记录表项记录了出现这个字或词的所有文档,一个表项就是一个字表段,它记录该文档的ID和字符在该文档中出现的位置情况。

    • 由于每个字或词对应的文档数量在动态变化,所以倒排表的建立和维护都较为复杂,但是在查询的时候由于可以一次得到查询关键字所对应的所有文档,所以效率高于正排表。在全文检索中,检索的快速响应是一个最为关键的性能,而索引建立由于在后台进行,尽管效率相对低一些,但不会影响整个搜索引擎的效率。

SpringBoot操作ElasticSearch

导入依赖 由于默认的es依赖支持6.x的所以使用新的依赖

<properties>
    <elasticsearch.version>7.4.2</elasticsearch.version>
</properties>

<!--引入依赖: elasticsearch-rest-high-level-client -->
<dependencies>
    <dependency>
        <groupId>org.elasticsearch.client</groupId>
        <artifactId>elasticsearch-rest-high-level-client</artifactId>
        <version>7.4.2</version>
    </dependency>
</dependencies>

定义yml文件

es:
  host: # 服务器ip 本地的 127.0.0.1
  port: 9200
  protocol: http

定义配置类读取yml文件信息

package cn.chy.com.config;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

/**
 * @Description: TODO
 * @author: 颜淮川
 * @date: 2021年12月7日15:14:11
 * 第二遍
 */
@Configuration
@Slf4j
// 读取为es前缀的信息
@ConfigurationProperties("es")
// 这种读取方式必须设置setter方法
@Setter
public class ElasticSearchConfig {

    private String host;

    private int port;

    private String protocol;

    public static final RequestOptions REQUEST_OPTIONS;

    static {
    	// 读取RequestOptions 并赋值
        RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
        REQUEST_OPTIONS = builder.build();
    }
	
	// 在spring中添加一个 restHighLevelClient 信息
    @Bean
    public RestHighLevelClient restHighLevelClient() {
    	// 调用yml文件中配置信息
        RestClientBuilder builder = RestClient.builder(new HttpHost(host, port, protocol));

        RestHighLevelClient restHighLevelClient = new RestHighLevelClient(builder);

        return restHighLevelClient;
    }
}

根据以下查询对应的实体类

package cn.chy.com.es;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @Description: TODO
 * @author: 颜景琦
 * @date: 2021年12月07日 10:24
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Company {

    private String name;

    private String job;

    private String logo;

    private Double payment;
}

public boolean save(String index, String id, String jsonResult) {
	// 新增请求
    IndexRequest request = new IndexRequest(index);
    // 新增id
    request.id(id);
    // 新增内容
    request.source(jsonResult, XContentType.JSON);
    try {	
    	// 新增响应的结果  
        IndexResponse response =
        		// 这边调用的是 他被spring托管的信息 使用@Resource注入一下
                restHighLevelClient.index(request, ElasticSearchConfig.REQUEST_OPTIONS);
        System.out.println(response);
        return true;
    } catch (IOException e) {
        e.printStackTrace();
    }
    return false;
}



### 测试方法
@Test
public void testSave() {
    Company company = Company.builder().logo("yyy").name("颜淮川")
            .job("2000").payment(2909.3).build();
    String result = JSON.toJSONString(company);
    esUtil.save("company-index", "10", result);
}

public boolean delete(String index, String id) {
    DeleteRequest request = new DeleteRequest(index);
    request.id(id);
    try {
        DeleteResponse response =
                restHighLevelClient.delete(request, ElasticSearchConfig.REQUEST_OPTIONS);
        System.out.println(response);
        return true;
    } catch (IOException e) {
        e.printStackTrace();
    }
    return false;
}


### 测试方法
@Test
public void testDelete() {

   esUtil.delete("company-index", "10");
}

public boolean update(String index, String id, String jsonResult) {
    UpdateRequest request = new UpdateRequest(index, id);
    request.doc(jsonResult, XContentType.JSON);
    try {
        UpdateResponse response =
                restHighLevelClient.update(request, ElasticSearchConfig.REQUEST_OPTIONS);
        System.out.println(response);
        return true;
    } catch (IOException e) {
        e.printStackTrace();
    }
    return false;
}

### 测试方法


@Test
public void testUpdate() {
    Company company = Company.builder().logo("yyy").name("颜道修")
            .job("2000").payment(2909.3).build();
    String result = JSON.toJSONString(company);
    esUtil.update("company-index", "10", result);
}

查 id

public T getById(String index, String id, Class<T> target) {
    GetRequest request = new GetRequest(index, id);
    try {
        GetResponse response =
                restHighLevelClient.get(request, ElasticSearchConfig.REQUEST_OPTIONS);

        String result = response.getSourceAsString();

        return JSON.parseObject(result, target);
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}


###
@Test
public void testId() {

//        SearchSourceBuilder search = new SearchSourceBuilder();
//        search.query(QueryBuilders.termQuery("_id", 10));
    Company utilById = esUtil.getById("company-index", "10", Company.class);
    System.out.println(utilById);
}

查所有

public List<T> list(String index,
                    SearchSourceBuilder searchSourceBuilder
        , Class<T> target) {
    SearchRequest request = new SearchRequest(index);

    request.source(searchSourceBuilder);

    List<T> tList = new ArrayList<>();
    try {
        SearchResponse response =
                restHighLevelClient.search(request, ElasticSearchConfig.REQUEST_OPTIONS);
        SearchHits hits = response.getHits();
        SearchHit[] totalHits = hits.getHits();
        if (ObjectUtils.isEmpty(totalHits)) {
            return null;
        }
        for (SearchHit totalHit : totalHits) {
            tList.add(JSON.parseObject(totalHit.getSourceAsString(), target));
        }
        return tList;
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}


### 测试方法
@Test
public void testList() {
    SearchSourceBuilder search = new SearchSourceBuilder();
    search.query(QueryBuilders.termQuery("name", "张三"));
    List<Company> list = esUtil.list("company-index", search, Company.class);
    System.out.println(list);
}

查询page信息

public Map<String, Object> pageInfo(String index,
                                    SearchSourceBuilder searchSourceBuilder,
                                    Class<T> target,
                                    int from,
                                    int size) {
    Map<String, Object> map = new HashMap<>();
    SearchRequest request = new SearchRequest(index);
    searchSourceBuilder.from(from);
    searchSourceBuilder.size(size);
    request.source(searchSourceBuilder);
    List<T> tList = new ArrayList<>();
    int page = 0;
    try {
        SearchResponse response =
                restHighLevelClient.search(request, ElasticSearchConfig.REQUEST_OPTIONS);
        SearchHits hits = response.getHits();
        page = (int) Math.ceil(hits.getTotalHits().value / size);

        SearchHit[] totalHits = hits.getHits();
        if (ObjectUtils.isEmpty(totalHits)) {
            return null;
        }
        for (SearchHit totalHit : totalHits) {
            tList.add(JSON.parseObject(totalHit.getSourceAsString(), target));
        }
    } catch (IOException e) {
        e.printStackTrace();
    }

    map.put("page", page);
    map.put("result", tList);
    return map;
}


### 测试方法

@Test
public void testPage() {
    SearchSourceBuilder search = new SearchSourceBuilder();
    search.query(QueryBuilders.termQuery("name", "张三"));
    Map<String, Object> map = esUtil.pageInfo("company-index", search, Company.class, 0, 3);
    System.out.println(map);
}

面试天堑

  • 我要开始了

1.为什么要使用Elasticsearch?

  • ==在我们商城中的数据,将来会非常多,所以采用以往的模糊查询,模糊查询前置配置,会放弃索引,导致商品查询是全表扫面,在百万级别的数据库中,效率非常低下,而我们使用ES做一个全文索引,我们将经常查询的商品的某些字段,比如说商品名,描述、价格还有id这些字段我们放入我们索引库里,可以提高查询速度。==

2.详细描述一下Elasticsearch索引文档的过程。

  •  ==协调节点默认使用文档ID参与计算(也支持通过routing),以便为路由提供合适的分片。   shard = hash(document_id) % (num_of_primary_shards)   当分片所在的节点接收到来自协调节点的请求后,会将请求写入到Memory Buffer,然后定时(默认是每隔1秒)写入到Filesystem Cache,这个从Momery Buffer到Filesystem   Cache的过程就叫做refresh;   当然在某些情况下,存在Momery Buffer和Filesystem Cache的数据可能会丢失,ES是通过translog的机制来保证数据的可靠性的。其实现机制是接收到请求后,同时也会写入到translog中,当Filesystem cache中的数据写入到磁盘中时,才会清除掉,这个过程叫做flush;   在flush过程中,内存中的缓冲将被清除,内容被写入一个新段,段的fsync将创建一个新的提交点,并将内容刷新到磁盘,旧的translog将被删除并开始一个新的translog。   flush触发的时机是定时触发(默认30分钟)或者translog变得太大(默认为512M)时;==

3.详细描述一下Elasticsearch更新和删除文档的过程

  • ==删除和更新也都是写操作,但是Elasticsearch中的文档是不可变的,因此不能被删除或者改动以展示其变更;   磁盘上的每个段都有一个相应的.del文件。当删除请求发送后,文档并没有真的被删除,而是在.del文件中被标记为删除。该文档依然能匹配查询,但是会在结果中被过滤掉。当段合并时,在.del文件中被标记为删除的文档将不会被写入新段。   在新的文档被创建时,Elasticsearch会为该文档指定一个版本号,当执行更新时,旧版本的文档在.del文件中被标记为删除,新版本的文档被索引到一个新段。旧版本的文档依然能匹配查询,但是会在结果中被过滤掉。==
  1. 在并发情况下,Elasticsearch如果保证读写一致?
  • ==可以通过版本号使用乐观并发控制,以确保新版本不会被旧版本覆盖,由应用层来处理具体的冲突;   另外对于写操作,一致性级别支持quorum/one/all,默认为quorum,即只有当大多数分片可用时才允许写操作。但即使大多数可用,也可能存在因为网络等原因导致写入副本失败,这样该副本被认为故障,分片将会在一个不同的节点上重建。   对于读操作,可以设置replication为sync(默认),这使得操作在主分片和副本分片都完成后才会返回;如果设置replication为async时,也可以通过设置搜索请求参数_preference为primary来查询主分片,确保文档是最新版本。==
  1. ElasticSearch中的分片是什么?
  • ==在大多数环境中,每个节点都在单独的盒子或虚拟机上运行。   索引 - 在Elasticsearch中,索引是文档的集合。   分片 -因为Elasticsearch是一个分布式搜索引擎,所以索引通常被分割成分布在多个节点上的被称为分片的元素==
  1. ElasticSearch中的集群、节点、索引、文档、类型是什么?
  • 群集是一个或多个节点(服务器)的集合,它们共同保存您的整个数据,并提供跨所有节点的联合索引和搜索功能。群集由唯一名称标识,默认情况下为“elasticsearch”。此名称很重要,因为如果节点设置为按名称加入群集,则该节点只能是群集的一部分。

  • 节点是属于集群一部分的单个服务器。它存储数据并参与群集索引和搜索功能。

  • 索引就像关系数据库中的“数据库”。它有一个定义多种类型的映射。索引是逻辑名称空间,映射到一个或多个主分片,并且可以有零个或多个副本分片。 MySQL =>数据库 ElasticSearch =>索引

  • 文档类似于关系数据库中的一行。不同之处在于索引中的每个文档可以具有不同的结构(字段),但是对于通用字段应该具有相同的数据类型。 MySQL => Databases => Tables => Columns / Rows ElasticSearch => Indices => Types =>具有属性的文档

  • 类型是索引的逻辑类别/分区,其语义完全取决于用户。

全部评论

相关推荐

球球别再泡了:坏,我单9要了14
点赞 评论 收藏
分享
牛客969571862号:昨天捞我今天面这个,岗位一模一样,感觉就是面着玩
点赞 评论 收藏
分享
1 收藏 评论
分享
牛客网
牛客企业服务