什么是elasticsearch?

  • 一个开源的分布式搜索引擎,可以用来实现搜索、日志统计、分析、系统监控等功能,ES的特点:速度快,海量数据近实时搜索,扩展性高,强大的查询和分析,操作简单.

    什么是elastic stack(ELK)?

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

    什么是Lucene?

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

    ES核心概念

    索引库(index indices)
    文档(document)
    映射(Mapping)
    集群Cluster 和 节点Node
    分片(Shards)和副本(replicas)

    倒排索引

    一句话介绍倒排索引
    根据要检索的内容,创建词汇表,查询时 先查询词汇表,根据关键词得到对应的内容.

  • 优点:

    • 根据词条搜索、模糊搜索时,速度非常快
  • 缺点:
    • 只能给词条创建索引,而不是字段
    • 无法根据字段做排序

      倒排索引的工作原理

      1.将文档逐个编号,存储到文档列表中,给每个编号创建索引
      2.如果要根据字段查询,会将该字段内容进行分词,产生词汇表
      3.词汇表中的每个词汇,会关联文档ID
      4.查询时,将查询的条件内容拆分成词汇,去词汇表中匹配
      5.根据匹配到词汇,得到关联的ID
      6.去文档列表中 根据ID获取文档

mysql 和es 对比:

image.png

  • Mysql:擅长事务类型操作,可以确保数据的安全和一致性
  • Elasticsearch:擅长海量数据的搜索、分析、计算
  • 对安全性要求较高的写操作,使用mysql实现
  • 对查询性能要求较高的搜索需求,使用elasticsearch实现
  • 两者再基于某种方式,实现数据的同步,保证一致性

    mapping映射属性

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

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

    • 字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)
    • 数值:long、integer、short、byte、double、float、
    • 布尔:boolean
    • 日期:date
    • 对象:object
  • index:是否创建索引,默认为true
  • analyzer:使用哪种分词器
  • properties:该字段的子字段

    创建索引库和映射

  • 请求方式:PUT

  • 请求路径:/索引库名,可以自定义
  • 请求参数:mapping映射

    示例:

    1. PUT /heima
    2. {
    3. "mappings": {
    4. "properties": {
    5. "info":{
    6. "type": "text",
    7. "analyzer": "ik_smart"
    8. },
    9. "email":{
    10. "type": "keyword",
    11. "index": "false"
    12. },
    13. "name":{
    14. "properties": {
    15. "firstName": {
    16. "type": "keyword"
    17. }
    18. }
    19. }
    20. }
    21. }
    22. }

    索引库操作有哪些?

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

  • 查询索引库:GET /索引库名
  • 删除索引库:DELETE /索引库名
  • 添加字段:PUT /索引库名/_mapping

文档操作有哪些?

  • 创建文档:POST /{索引库名}/_doc/文档id { json文档 }
  • 查询文档:GET /{索引库名}/_doc/文档id
  • 删除文档:DELETE /{索引库名}/_doc/文档id
  • 修改文档:
    • 全量修改:PUT /{索引库名}/_doc/文档id { json文档 }
    • 增量修改:POST /{索引库名}/_update/文档id { “doc”: {字段}}

RestAPI

ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是组装DSL语句,通过http请求发送给ES。官方文档地址:https://www.elastic.co/guide/en/elasticsearch/client/index.html

初始化RestClient

在elasticsearch提供的API中,与elasticsearch一切交互都封装在一个名为RestHighLevelClient的类中,必须先完成这个对象的初始化,建立与elasticsearch的连接。
分为三步:
1)引入es的RestHighLevelClient依赖:

  1. <dependency>
  2. <groupId>org.elasticsearch.client</groupId>
  3. <artifactId>elasticsearch-rest-high-level-client</artifactId>
  4. </dependency>

2)因为SpringBoot默认的ES版本是7.6.2,所以我们需要覆盖默认的ES版本:

<properties>
    <java.version>1.8</java.version>
    <elasticsearch.version>7.12.1</elasticsearch.version>
</properties>

3)初始化RestHighLevelClient:
创建一个测试类HotelIndexTest,然后将初始化的代码编写在@BeforeEach方法中:

public class HotelIndexTest {
    private RestHighLevelClient client;

    @BeforeEach
    void setUp() {
        this.client = new RestHighLevelClient(RestClient.builder(
                HttpHost.create("http://192.168.150.101:9200")
        ));
    }

    @AfterEach
    void tearDown() throws IOException {
        this.client.close();
    }
}

创建索引库

  • 1)创建Request对象。因为是创建索引库的操作,因此Request是CreateIndexRequest。
  • 2)添加请求参数,其实就是DSL的JSON参数部分。因为json字符串很长,这里是定义了静态字符串常量MAPPING_TEMPLATE,让代码看起来更加优雅。
  • 3)发送请求,client.indices()方法的返回值是IndicesClient类型,封装了所有与索引库操作有关的方法。 ```java package cn.itcast.hotel.constants;

public class HotelConstants { public static final String MAPPING_TEMPLATE = “{\n” + “ \”mappings\”: {\n” + “ \”properties\”: {\n” + “ \”id\”: {\n” + “ \”type\”: \”keyword\”\n” + “ },\n” + “ \”name\”:{\n” + “ \”type\”: \”text\”,\n” + “ \”analyzer\”: \”ik_max_word\”,\n” + “ \”copy_to\”: \”all\”\n” + “ },\n” + “ \”address\”:{\n” + “ \”type\”: \”keyword\”,\n” + “ \”index\”: false\n” + “ },\n” + “ \”price\”:{\n” + “ \”type\”: \”integer\”\n” + “ },\n” + “ \”score\”:{\n” + “ \”type\”: \”integer\”\n” + “ },\n” + “ \”brand\”:{\n” + “ \”type\”: \”keyword\”,\n” + “ \”copy_to\”: \”all\”\n” + “ },\n” + “ \”city\”:{\n” + “ \”type\”: \”keyword\”,\n” + “ \”copy_to\”: \”all\”\n” + “ },\n” + “ \”starName\”:{\n” + “ \”type\”: \”keyword\”\n” + “ },\n” + “ \”business\”:{\n” + “ \”type\”: \”keyword\”\n” + “ },\n” + “ \”location\”:{\n” + “ \”type\”: \”geo_point\”\n” + “ },\n” + “ \”pic\”:{\n” + “ \”type\”: \”keyword\”,\n” + “ \”index\”: false\n” + “ },\n” + “ \”all\”:{\n” + “ \”type\”: \”text\”,\n” + “ \”analyzer\”: \”ik_max_word\”\n” + “ }\n” + “ }\n” + “ }\n” + “}”; }

```java
@Test
void createHotelIndex() throws IOException {
    // 1.创建Request对象
    CreateIndexRequest request = new CreateIndexRequest("hotel");
    // 2.准备请求的参数:DSL语句
    request.source(MAPPING_TEMPLATE, XContentType.JSON);
    // 3.发送请求
    client.indices().create(request, RequestOptions.DEFAULT);
}

删除索引库

删除索引库的DSL语句非常简单:DELETE /hotel
与创建索引库相比:

  • 请求方式从PUT变为DELTE
  • 请求路径不变
  • 无请求参数

所以代码的差异,注意体现在Request对象上。依然是三步走:

  • 1)创建Request对象。这次是DeleteIndexRequest对象
  • 2)准备参数。这里是无参
  • 3)发送请求。改用delete方法

    @Test
    void testDeleteHotelIndex() throws IOException {
      // 1.创建Request对象
      DeleteIndexRequest request = new DeleteIndexRequest("hotel");
      // 2.发送请求
      client.indices().delete(request, RequestOptions.DEFAULT);
    }
    

    判断索引库是否存在

    对应的DSL是:GET /hotel
    因此与删除的Java代码流程是类似的。依然是三步走:

  • 1)创建Request对象。这次是GetIndexRequest对象

  • 2)准备参数。这里是无参
  • 3)发送请求。改用exists方法

    @Test
    void testExistsHotelIndex() throws IOException {
      // 1.创建Request对象
      GetIndexRequest request = new GetIndexRequest("hotel");
      // 2.发送请求
      boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
      // 3.输出
      System.err.println(exists ? "索引库已经存在!" : "索引库不存在!");
    }
    

    引库操作的基本步骤:

  • 初始化RestHighLevelClient

  • 创建XxxIndexRequest。XXX是Create、Get、Delete
  • 准备DSL( Create时需要,其它是无参)
  • 发送请求。调用RestHighLevelClient#indices().xxx()方法,xxx是create、exists、delete

    RestClient操作文档

    为了与索引库操作分离,我们再次参加一个测试类,做两件事情:

  • 初始化RestHighLevelClient

  • 我们的酒店数据在数据库,需要利用IHotelService去查询,所以注入这个接口

    @SpringBootTest
    public class HotelDocumentTest {
      @Autowired
      private IHotelService hotelService;
    
      private RestHighLevelClient client;
    
      @BeforeEach
      void setUp() {
          this.client = new RestHighLevelClient(RestClient.builder(
                  HttpHost.create("http://192.168.150.101:9200")
          ));
      }
    
      @AfterEach
      void tearDown() throws IOException {
          this.client.close();
      }
    }
    

    与我们的索引库结构存在差异:

  • longitude和latitude需要合并为location

因此,我们需要定义一个新的类型,与索引库结构吻合:

  public HotelDoc(Hotel hotel) {
        this.id = hotel.getId();
        this.name = hotel.getName();
        this.address = hotel.getAddress();
        this.price = hotel.getPrice();
        this.score = hotel.getScore();
        this.brand = hotel.getBrand();
        this.city = hotel.getCity();
        this.starName = hotel.getStarName();
        this.business = hotel.getBusiness();
        this.location = hotel.getLatitude() + ", " + hotel.getLongitude();
        this.pic = hotel.getPic();
    }

新增文档

我们导入酒店数据,基本流程一致,但是需要考虑几点变化:

  • 酒店数据来自于数据库,我们需要先查询出来,得到hotel对象
  • hotel对象需要转为HotelDoc对象
  • HotelDoc需要序列化为json格式

因此,代码整体步骤如下:

  • 1)根据id查询酒店数据Hotel
  • 2)将Hotel封装为HotelDoc
  • 3)将HotelDoc序列化为JSON
  • 4)创建IndexRequest,指定索引库名和id
  • 5)准备请求参数,也就是JSON文档
  • 6)发送请求

    @Test
    void testAddDocument() throws IOException {
      // 1.根据id查询酒店数据
      Hotel hotel = hotelService.getById(61083L);
      // 2.转换为文档类型
      HotelDoc hotelDoc = new HotelDoc(hotel);
      // 3.将HotelDoc转json
      String json = JSON.toJSONString(hotelDoc);
    
      // 1.准备Request对象
      IndexRequest request = new IndexRequest("hotel").id(hotelDoc.getId().toString());
      // 2.准备Json文档
      request.source(json, XContentType.JSON);
      // 3.发送请求
      client.index(request, RequestOptions.DEFAULT);
    }
    

    查询文档

  • 1)准备Request对象。这次是查询,所以是GetRequest

  • 2)发送请求,得到结果。因为是查询,这里调用client.get()方法
  • 3)解析结果,就是对JSON做反序列化

    @Test
    void testGetDocumentById() throws IOException {
      // 1.准备Request
      GetRequest request = new GetRequest("hotel", "61082");
      // 2.发送请求,得到响应
      GetResponse response = client.get(request, RequestOptions.DEFAULT);
      // 3.解析响应结果
      String json = response.getSourceAsString();
    
      HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
      System.out.println(hotelDoc);
    }
    

    删除文档

  • 1)准备Request对象,因为是删除,这次是DeleteRequest对象。要指定索引库名和id

  • 2)准备参数,无参
  • 3)发送请求。因为是删除,所以是client.delete()方法

    @Test
    void testDeleteDocument() throws IOException {
      // 1.准备Request
      DeleteRequest request = new DeleteRequest("hotel", "61083");
      // 2.发送请求
      client.delete(request, RequestOptions.DEFAULT);
    }
    

    修改文档

  • 1)准备Request对象。这次是修改,所以是UpdateRequest

  • 2)准备参数。也就是JSON文档,里面包含要修改的字段
  • 3)更新文档。这里调用client.update()方法

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

    批量导入文档

    批量处理BulkRequest,其本质就是将多个普通的CRUD请求组合在一起发送
    其实还是三步走:

  • 1)创建Request对象。这里是BulkRequest

  • 2)准备参数。批处理的参数,就是其它Request对象,这里就是多个IndexRequest
  • 3)发起请求。这里是批处理,调用的方法为client.bulk()方法

    @Test
    void testBulkRequest() throws IOException {
      // 批量查询酒店数据
      List<Hotel> hotels = hotelService.list();
    
      // 1.创建Request
      BulkRequest request = new BulkRequest();
      // 2.准备参数,添加多个新增的Request
      for (Hotel hotel : hotels) {
          // 2.1.转换为文档类型HotelDoc
          HotelDoc hotelDoc = new HotelDoc(hotel);
          // 2.2.创建新增文档的Request对象
          request.add(new IndexRequest("hotel")
                      .id(hotelDoc.getId().toString())
                      .source(JSON.toJSONString(hotelDoc), XContentType.JSON));
      }
      // 3.发送请求
      client.bulk(request, RequestOptions.DEFAULT);
    }
    

    文档操作的基本步骤:
    ●初始化RestHighLevelClient
    ●创建XxxRequest。XXX是Index、Get、Update、Delete、Bulk
    ●准备参数(Index、Update、Bulk时需要)
    ●发送请求。调用RestHighLevelClient#.xxx()方法,xxx是index、get、update、delete、bulk
    ●解析结果(Get时需要)

若有收获,就点个赞吧