elasticsearch是什么?

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

elasticsearch可以解决什么问题?

elasticsearch是一款非常强大的开源搜索引擎,具备非常多强大功能,可以帮助我们从海量数据中快速找到需要的内容
ELK技术栈
elasticsearch结合kibana、Logstash、Beats,也就是elastic stack(ELK)。被广泛应用在日志数据分析、实时监控等领域
而elasticsearch是elastic stack的核心,负责存储、搜索、分析数据。
elasticsearch底层是基于lucene来实现的。
image.png
image.png

总结

什么是elasticsearch?

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

    什么是elastic stack(ELK)?

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

    什么是Lucene?

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

    elasticsearch怎么用?

    1.正向索引和倒排索引:

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

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

正向索引

  • 优点:
    • 可以给多个字段创建索引
    • 根据索引字段搜索、排序速度非常快
  • 缺点:
    • 根据非索引字段,或者索引字段中的部分词条查找时,只能全表扫描。

倒排索引

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

      2.mysql与elasticsearch

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

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

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

  • Mysql:擅长事务类型操作,可以确保数据的安全和一致性
  • Elasticsearch:擅长海量数据的搜索、分析、计算

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

  • 对安全性要求较高的写操作,使用mysql实现
  • 对查询性能要求较高的搜索需求,使用elasticsearch实现
  • 两者再基于某种方式,实现数据的同步,保证一致性

    总结

    分词器的作用是什么?
    image.png

  • 创建倒排索引时对文档分词

  • 用户搜索时,对输入的内容分词

IK分词器有几种模式?

  • ik_smart:智能切分,粗粒度
  • ik_max_word:最细切分,细粒度

IK分词器如何拓展词条?如何停用词条?

  • 利用config目录的IkAnalyzer.cfg.xml文件添加拓展词典和停用词典
  • 在词典中添加拓展词条或者停用词条

    索引库操作

    1.mapping映射属性

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

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

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

例如下面的json文档:

  1. {
  2. "age": 21,
  3. "weight": 52.1,
  4. "isMarried": false,
  5. "info": "黑马程序员Java讲师",
  6. "email": "zy@itcast.cn",
  7. "score": [99.1, 99.5, 98.9],
  8. "name": {
  9. "firstName": "云",
  10. "lastName": "赵"
  11. }
  12. }

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

  • age:类型为 integer;参与搜索,因此需要index为true;无需分词器
  • weight:类型为float;参与搜索,因此需要index为true;无需分词器
  • isMarried:类型为boolean;参与搜索,因此需要index为true;无需分词器
  • info:类型为字符串,需要分词,因此是text;参与搜索,因此需要index为true;分词器可以用ik_smart
  • email:类型为字符串,但是不需要分词,因此是keyword;不参与搜索,因此需要index为false;无需分词器
  • score:虽然是数组,但是我们只看元素的类型,类型为float;参与搜索,因此需要index为true;无需分词器
  • name:类型为object,需要定义多个子属性

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

      2.索引库的CRUD

      2.1创建索引库和映射

      PUT /heima
      {
      "mappings": {
      "properties": {
       "info":{
         "type": "text",
         "analyzer": "ik_smart"
       },
       "email":{
         "type": "keyword",
         "index": "false"
       },
       "name":{
         "properties": {
           "firstName": {
             "type": "keyword"
           }
         }
       }
      }
      }
      }
      

      2.2.查询索引库

      GET /索引库名
      

      2.3.修改索引库

      PUT /索引库名/_mapping
      {
      "properties": {
      "新字段名":{
       "type": "integer"
      }
      }
      }
      

      2.4.删除索引库

      DELETE /索引库名
      

      总结

      索引库操作有哪些?
  • 创建索引库:PUT /索引库名

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

    文档操作

    1.新增文档

    POST /heima/_doc/1
    {
      "info": "黑马程序员Java讲师",
      "email": "zy@itcast.cn",
      "name": {
          "firstName": "云",
          "lastName": "赵"
      }
    }
    

    2.查询文档

    GET /heima/_doc/1
    

    3.删除文档

    # 根据id删除数据
    DELETE /heima/_doc/1
    

    4.修改文档

    修改有两种方式:

  • 全量修改:直接覆盖原来的文档

  • 增量修改:修改文档中的部分字段

    4.1.全量修改

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

  • 根据指定的id删除文档

  • 新增一个相同id的文档

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

PUT /heima/_doc/1
{
    "info": "黑马程序员高级Java讲师",
    "email": "zy@itcast.cn",
    "name": {
        "firstName": "云",
        "lastName": "赵"
    }
}

4.2.增量修改

POST /heima/_update/1
{
  "doc": {
    "email": "ZhaoYun@itcast.cn"
  }
}

总结

文档操作有哪些?

  • 创建文档: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
      其中的Java Rest Client又包括两种:
  • Java Low Level Rest Client

  • Java High Level Rest Client

我们学习的是Java HighLevel Rest Client客户端API

几个特殊字段说明:

  • location:地理坐标,里面包含精度、纬度
  • all:一个组合字段,其目的是将多字段的值 利用copy_to合并,提供给用户搜索 ```json PUT /hotel

{ “mappings”: { “properties”: { “id”: { “type”: “keyword” }, “name”:{ “type”: “text”, “analyzer”: “ik_max_word”, “copy_to”: “all” }, “address”:{ “type”: “keyword”, “index”: false }, “price”:{ “type”: “integer” }, “score”:{ “type”: “integer” }, “brand”:{ “type”: “keyword”, “copy_to”: “all” }, “city”:{ “type”: “keyword”, “copy_to”: “all” }, “starName”:{ “type”: “keyword” }, “business”:{ “type”: “keyword” }, “location”:{ “type”: “geo_point” }, “pic”:{ “type”: “keyword”, “index”: false }, “all”:{ “type”: “text”, “analyzer”: “ik_max_word” } } } }

![image.png](https://cdn.nlark.com/yuque/0/2022/png/26568475/1655545981990-820e9868-d397-4cab-b53f-ebd867182267.png#clientId=ub84a610e-0a2e-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=405&id=u12a87738&margin=%5Bobject%20Object%5D&name=image.png&originHeight=810&originWidth=1413&originalType=binary&ratio=1&rotation=0&showTitle=false&size=173599&status=done&style=none&taskId=uc2952112-e16f-4c61-9d12-92710ad219f&title=&width=706.5)<br />all里面包含name,brand,city三个字段的所有分词的结果;<br />当使用all搜索时,就相当于在name,brand,city三个字段中搜索,只要有一个满足,就可以查出来。
<a name="IB69B"></a>
## 使用RestClient步骤如下
<a name="VRQqo"></a>
## 使用RestClient操作索引库:
<a name="AXOjO"></a>
### 1.初始化RestClient:
在elasticsearch提供的API中,与elasticsearch一切交互都封装在一个名为RestHighLevelClient的类中,必须先完成这个对象的初始化,建立与elasticsearch的连接。<br />分为三步:<br />1)引入es的RestHighLevelClient依赖:
```json
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
</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:
初始化的代码如下:

RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
        HttpHost.create("http://192.168.150.101:9200")
));

这里为了单元测试方便,我们创建一个测试类HotelIndexTest,然后将初始化的代码编写在@BeforeEach方法中:

package cn.itcast.hotel;

import org.apache.http.HttpHost;
import org.elasticsearch.client.RestHighLevelClient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.IOException;

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();
    }
}

2.创建索引库

@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);
}
-- 创建索引库代码
package cn.itcast.hotel;

import cn.itcast.hotel.config.HotelConstants;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.common.xcontent.XContentType;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.IOException;

public class EsIndexDemo {
    @Test
    public void createIndex() throws IOException {

        //创建索引库
        CreateIndexRequest request = new CreateIndexRequest("hotel");

        request.source(HotelConstants.MAPPING_TEMPLATE, XContentType.JSON);

        CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
        System.out.println(response.isAcknowledged());
    }

    RestHighLevelClient client;

    @BeforeEach
    public void init() {
        //准备好es的客户端对象
        client = new RestHighLevelClient(RestClient.builder(
                HttpHost.create("http://192.168.200.130:9200")
        ));
    }

    @AfterEach
    public void close() {
        try {
            client.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3.删除索引库

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

4.判断索引库是否存在

判断索引库是否存在,本质就是查询,对应的DSL是:

GET /hotel
@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 ? "索引库已经存在!" : "索引库不存在!");
}

总结

JavaRestClient操作elasticsearch的流程基本类似。核心是client.indices()方法来获取索引库的操作对象。
索引库操作的基本步骤:

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

RestClient操作文档

1.初始化RestHighLevelClient

package cn.itcast.hotel;

import cn.itcast.hotel.pojo.Hotel;
import cn.itcast.hotel.service.IHotelService;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.IOException;
import java.util.List;

@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();
    }
}

1.新增文档

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

  • 酒店数据来自于数据库,我们需要先查询出来,得到hotel对象
  • hotel对象需要转为HotelDoc对象
  • HotelDoc需要序列化为json格式
  • 把HotelDoc对象中的数据转换为JSON格式后存入ES索引库

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

  • 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.发送请求  //添加文档:clent.index
      client.index(request, RequestOptions.DEFAULT);
    }
    

    2.查询文档

    @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);
    }
    

    3.删除文档

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

    4.修改文档

    @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);
    }
    

    5.批量导入文档

    @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.发送请求
     BulkResponse bulk = client.bulk(request, RequestOptions.DEFAULT);
          System.out.println(bulk.status());//ok代表成功
    }
    

    小结

    文档操作的基本步骤:

  • 初始化RestHighLevelClient

  • 创建XxxRequest。XXX是Index、Get、Update、Delete、Bulk
  • 准备参数(Index、Update、Bulk时需要)
  • 发送请求。调用RestHighLevelClient#.xxx()方法,xxx是index、get、update、delete、bulk
  • 解析结果(Get时需要)