天天看點

【分布式搜尋引擎】elasticsearch基礎

ElasticSearch基礎學習

分布式搜尋引擎01 -- elasticsearch基礎

0.學習目标

1.初識elasticsearch

1.1.了解ES

1.1.1.elasticsearch的作用

elasticsearch是一款非常強大的開源搜尋引擎,具備非常多強大功能,可以幫助我們從海量資料中快速找到需要的内容

例如:

【分布式搜尋引擎】elasticsearch基礎
  • 在GitHub搜尋代碼
    【分布式搜尋引擎】elasticsearch基礎
  • 在電商網站搜尋商品
    【分布式搜尋引擎】elasticsearch基礎
  • 在百度搜尋答案
    【分布式搜尋引擎】elasticsearch基礎
  • 在打車軟體搜尋附近的車
    【分布式搜尋引擎】elasticsearch基礎

1.1.2.ELK技術棧

elasticsearch結合kibana、Logstash、Beats,也就是elastic stack(ELK)。被廣泛應用在日志資料分析、實時監控等領域:

【分布式搜尋引擎】elasticsearch基礎

而elasticsearch是elastic stack的核心,負責存儲、搜尋、分析資料。

【分布式搜尋引擎】elasticsearch基礎

1.1.3.elasticsearch和lucene

elasticsearch底層是基于lucene來實作的。

Lucene是一個Java語言的搜尋引擎類庫,是Apache公司的頂級項目,由DougCutting于1999年研發。官網位址:https://lucene.apache.org/ 。

【分布式搜尋引擎】elasticsearch基礎

elasticsearch的發展曆史:

  • 2004年Shay Banon基于Lucene開發了Compass
  • 2010年Shay Banon 重寫了Compass,取名為Elasticsearch。
【分布式搜尋引擎】elasticsearch基礎

1.1.4.為什麼不是其他搜尋技術?

目前比較知名的搜尋引擎技術排名:

【分布式搜尋引擎】elasticsearch基礎

雖然在早期,Apache Solr是最主要的搜尋引擎技術,但随着發展elasticsearch已經漸漸超越了Solr,獨占鳌頭:

【分布式搜尋引擎】elasticsearch基礎

1.1.5.總結

什麼是elasticsearch?

  • 一個開源的分布式搜尋引擎,可以用來實作搜尋、日志統計、分析、系統監控等功能

什麼是elastic stack(ELK)?

  • 是以elasticsearch為核心的技術棧,包括beats、Logstash、kibana、elasticsearch

什麼是Lucene?

  • 是Apache的開源搜尋引擎類庫,提供了搜尋引擎的核心API

1.2.反向索引

反向索引的概念是基于MySQL這樣的正向索引而言的。

1.2.1.正向索引

那麼什麼是正向索引呢?例如給下表(tb_goods)中的id建立索引:

【分布式搜尋引擎】elasticsearch基礎

如果是根據id查詢,那麼直接走索引,查詢速度非常快。

但如果是基于title做模糊查詢,隻能是逐行掃描資料,流程如下:

1)使用者搜尋資料,條件是title符合

"%手機%"

2)逐行擷取資料,比如id為1的資料

3)判斷資料中的title是否符合使用者搜尋條件

4)如果符合則放入結果集,不符合則丢棄。回到步驟1

逐行掃描,也就是全表掃描,随着資料量增加,其查詢效率也會越來越低。當資料量達到數百萬時,就是一場災難。

1.2.2.反向索引

反向索引中有兩個非常重要的概念:

  • 文檔(

    Document

    ):用來搜尋的資料,其中的每一條資料就是一個文檔。例如一個網頁、一個商品資訊
  • 詞條(

    Term

    ):對文檔資料或使用者搜尋資料,利用某種算法分詞,得到的具備含義的詞語就是詞條。例如:我是中國人,就可以分為:我、是、中國人、中國、國人這樣的幾個詞條

建立反向索引是對正向索引的一種特殊處理,流程如下:

  • 将每一個文檔的資料利用算法分詞,得到一個個詞條
  • 建立表,每行資料包括詞條、詞條所在文檔id、位置等資訊
  • 因為詞條唯一性,可以給詞條建立索引,例如hash表結構索引

如圖:

【分布式搜尋引擎】elasticsearch基礎

反向索引的搜尋流程如下(以搜尋"華為手機"為例):

1)使用者輸入條件

"華為手機"

進行搜尋。

2)對使用者輸入内容分詞,得到詞條:

華為

手機

3)拿着詞條在反向索引中查找,可以得到包含詞條的文檔id:1、2、3。

4)拿着文檔id到正向索引中查找具體文檔。

【分布式搜尋引擎】elasticsearch基礎

雖然要先查詢反向索引,再查詢反向索引,但是無論是詞條、還是文檔id都建立了索引,查詢速度非常快!無需全表掃描。

1.2.3.正向和倒排

那麼為什麼一個叫做正向索引,一個叫做反向索引呢?

  • 正向索引是最傳統的,根據id索引的方式。但根據詞條查詢時,必須先逐條擷取每個文檔,然後判斷文檔中是否包含所需要的詞條,是根據文檔找詞條的過程。
  • 而反向索引則相反,是先找到使用者要搜尋的詞條,根據詞條得到保護詞條的文檔的id,然後根據id擷取文檔。是根據詞條找文檔的過程。

是不是恰好反過來了?

那麼兩者方式的優缺點是什麼呢?

正向索引:

  • 優點:
    • 可以給多個字段建立索引
    • 根據索引字段搜尋、排序速度非常快
  • 缺點:
    • 根據非索引字段,或者索引字段中的部分詞條查找時,隻能全表掃描。

反向索引:

    • 根據詞條搜尋、模糊搜尋時,速度非常快
    • 隻能給詞條建立索引,而不是字段
    • 無法根據字段做排序

1.3.es的一些概念

elasticsearch中有很多獨有的概念,與mysql中略有差别,但也有相似之處。

1.3.1.文檔和字段

elasticsearch是面向文檔(Document)存儲的,可以是資料庫中的一條商品資料,一個訂單資訊。文檔資料會被序列化為json格式後存儲在elasticsearch中:

【分布式搜尋引擎】elasticsearch基礎

而Json文檔中往往包含很多的字段(Field),類似于資料庫中的列。

1.3.2.索引和映射

索引(Index),就是相同類型的文檔的集合。

  • 所有使用者文檔,就可以組織在一起,稱為使用者的索引;
  • 所有商品的文檔,可以組織在一起,稱為商品的索引;
  • 所有訂單的文檔,可以組織在一起,稱為訂單的索引;
【分布式搜尋引擎】elasticsearch基礎

是以,我們可以把索引當做是資料庫中的表。

資料庫的表會有限制資訊,用來定義表的結構、字段的名稱、類型等資訊。是以,索引庫中就有映射(mapping),是索引中文檔的字段限制資訊,類似表的結構限制。

1.3.3.mysql與elasticsearch

我們統一的把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實作
  • 兩者再基于某種方式,實作資料的同步,保證一緻性
【分布式搜尋引擎】elasticsearch基礎

1.4.安裝es、kibana

1.4.1.安裝

參考課前資料:

【分布式搜尋引擎】elasticsearch基礎

1.4.2.分詞器

【分布式搜尋引擎】elasticsearch基礎

1.4.3.總結

分詞器的作用是什麼?

  • 建立反向索引時對文檔分詞
  • 使用者搜尋時,對輸入的内容分詞

IK分詞器有幾種模式?

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

IK分詞器如何拓展詞條?如何停用詞條?

  • 利用config目錄的IkAnalyzer.cfg.xml檔案添加拓展詞典和停用詞典
  • 在詞典中添加拓展詞條或者停用詞條

2.索引庫操作

索引庫就類似資料庫表,mapping映射就類似表的結構。

我們要向es中存儲資料,必須先建立“庫”和“表”。

2.1.mapping映射屬性

mapping是對索引庫中文檔的限制,常見的mapping屬性包括:

  • type:字段資料類型,常見的簡單類型有:
    • 字元串:text(可分詞的文本)、keyword(精确值,例如:品牌、國家、ip位址)
    • 數值:long、integer、short、byte、double、float、
    • 布爾:boolean
    • 日期:date
    • 對象:object
  • index:是否建立索引,預設為true
  • analyzer:使用哪種分詞器
  • properties:該字段的子字段

例如下面的json文檔:

{
    "age": 21,
    "weight": 52.1,
    "isMarried": false,
    "info": "黑馬程式員Java講師",
    "email": "[email protected]",
    "score": [99.1, 99.5, 98.9],
    "name": {
        "firstName": "雲",
        "lastName": "趙"
    }
}
           

對應的每個字段映射(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.2.索引庫的CRUD

這裡我們統一使用Kibana編寫DSL的方式來示範。

2.2.1.建立索引庫和映射

基本文法:

  • 請求方式:PUT
  • 請求路徑:/索引庫名,可以自定義
  • 請求參數:mapping映射

格式:

PUT /索引庫名稱
{
  "mappings": {
    "properties": {
      "字段名":{
        "type": "text",
        "analyzer": "ik_smart"
      },
      "字段名2":{
        "type": "keyword",
        "index": "false"
      },
      "字段名3":{
        "properties": {
          "子字段": {
            "type": "keyword"
          }
        }
      },
      // ...略
    }
  }
}
           

示例:

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

2.2.2.查詢索引庫

  • 請求方式:GET
  • 請求路徑:/索引庫名
  • 請求參數:無
GET /索引庫名
           
【分布式搜尋引擎】elasticsearch基礎

2.2.3.修改索引庫

反向索引結構雖然不複雜,但是一旦資料結構改變(比如改變了分詞器),就需要重新建立反向索引,這簡直是災難。是以索引庫一旦建立,無法修改mapping。

雖然無法修改mapping中已有的字段,但是卻允許添加新的字段到mapping中,因為不會對反向索引産生影響。

文法說明:

PUT /索引庫名/_mapping
{
  "properties": {
    "新字段名":{
      "type": "integer"
    }
  }
}
           
【分布式搜尋引擎】elasticsearch基礎

2.2.4.删除索引庫

文法:

  • 請求方式:DELETE
DELETE /索引庫名
           

在kibana中測試:

【分布式搜尋引擎】elasticsearch基礎

2.2.5.總結

索引庫操作有哪些?

  • 建立索引庫:PUT /索引庫名
  • 查詢索引庫:GET /索引庫名
  • 删除索引庫:DELETE /索引庫名
  • 添加字段:PUT /索引庫名/_mapping

3.文檔操作

3.1.新增文檔

POST /索引庫名/_doc/文檔id
{
    "字段1": "值1",
    "字段2": "值2",
    "字段3": {
        "子屬性1": "值3",
        "子屬性2": "值4"
    },
    // ...
}
           
POST /heima/_doc/1
{
    "info": "黑馬程式員Java講師",
    "email": "[email protected]",
    "name": {
        "firstName": "雲",
        "lastName": "趙"
    }
}
           

響應:

【分布式搜尋引擎】elasticsearch基礎

3.2.查詢文檔

根據rest風格,新增是post,查詢應該是get,不過查詢一般都需要條件,這裡我們把文檔id帶上。

GET /{索引庫名稱}/_doc/{id}
           

通過kibana檢視資料:

GET /heima/_doc/1
           

檢視結果:

【分布式搜尋引擎】elasticsearch基礎

3.3.删除文檔

删除使用DELETE請求,同樣,需要根據id進行删除:

DELETE /{索引庫名}/_doc/id值
           
# 根據id删除資料
DELETE /heima/_doc/1
           

結果:

【分布式搜尋引擎】elasticsearch基礎

3.4.修改文檔

修改有兩種方式:

  • 全量修改:直接覆寫原來的文檔
  • 增量修改:修改文檔中的部分字段

3.4.1.全量修改

全量修改是覆寫原來的文檔,其本質是:

  • 根據指定的id删除文檔
  • 新增一個相同id的文檔

注意:如果根據id删除時,id不存在,第二步的新增也會執行,也就從修改變成了新增操作了。

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

           
PUT /heima/_doc/1
{
    "info": "黑馬程式員進階Java講師",
    "email": "[email protected]",
    "name": {
        "firstName": "雲",
        "lastName": "趙"
    }
}
           

3.4.2.增量修改

增量修改是隻修改指定id比對的文檔中的部分字段。

POST /{索引庫名}/_update/文檔id
{
    "doc": {
         "字段名": "新的值",
    }
}
           
POST /heima/_update/1
{
  "doc": {
    "email": "[email protected]"
  }
}
           

3.5.總結

文檔操作有哪些?

  • 建立文檔:POST /{索引庫名}/_doc/文檔id { json文檔 }
  • 查詢文檔:GET /{索引庫名}/_doc/文檔id
  • 删除文檔:DELETE /{索引庫名}/_doc/文檔id
  • 修改文檔:
    • 全量修改:PUT /{索引庫名}/_doc/文檔id { json文檔 }
    • 增量修改:POST /{索引庫名}/_update/文檔id { "doc": {字段}}

4.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
【分布式搜尋引擎】elasticsearch基礎

我們學習的是Java HighLevel Rest Client用戶端API

4.0.導入Demo工程

4.0.1.導入資料

首先導入課前資料提供的資料庫資料:

【分布式搜尋引擎】elasticsearch基礎

資料結構如下:

CREATE TABLE `tb_hotel` (
  `id` bigint(20) NOT NULL COMMENT '酒店id',
  `name` varchar(255) NOT NULL COMMENT '酒店名稱;例:7天酒店',
  `address` varchar(255) NOT NULL COMMENT '酒店位址;例:航頭路',
  `price` int(10) NOT NULL COMMENT '酒店價格;例:329',
  `score` int(2) NOT NULL COMMENT '酒店評分;例:45,就是4.5分',
  `brand` varchar(32) NOT NULL COMMENT '酒店品牌;例:如家',
  `city` varchar(32) NOT NULL COMMENT '所在城市;例:上海',
  `star_name` varchar(16) DEFAULT NULL COMMENT '酒店星級,從低到高分别是:1星到5星,1鑽到5鑽',
  `business` varchar(255) DEFAULT NULL COMMENT '商圈;例:虹橋',
  `latitude` varchar(32) NOT NULL COMMENT '緯度;例:31.2497',
  `longitude` varchar(32) NOT NULL COMMENT '經度;例:120.3925',
  `pic` varchar(255) DEFAULT NULL COMMENT '酒店圖檔;例:/img/1.jpg',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
           

4.0.2.導入項目

然後導入課前資料提供的項目:

【分布式搜尋引擎】elasticsearch基礎

項目結構如圖:

【分布式搜尋引擎】elasticsearch基礎

4.0.3.mapping映射分析

建立索引庫,最關鍵的是mapping映射,而mapping映射要考慮的資訊包括:

  • 字段名
  • 字段資料類型
  • 是否參與搜尋
  • 是否需要分詞
  • 如果分詞,分詞器是什麼?

其中:

  • 字段名、字段資料類型,可以參考資料表結構的名稱和類型
  • 是否參與搜尋要分析業務來判斷,例如圖檔位址,就無需參與搜尋
  • 是否分詞呢要看内容,内容如果是一個整體就無需分詞,反之則要分詞
  • 分詞器,我們可以統一使用ik_max_word

來看下酒店資料的索引庫結構:

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"
      }
    }
  }
}
           

幾個特殊字段說明:

  • location:地理坐标,裡面包含精度、緯度
  • all:一個組合字段,其目的是将多字段的值 利用copy_to合并,提供給使用者搜尋

地理坐标說明:

【分布式搜尋引擎】elasticsearch基礎

copy_to說明:

【分布式搜尋引擎】elasticsearch基礎

4.0.4.初始化RestClient

在elasticsearch提供的API中,與elasticsearch一切互動都封裝在一個名為RestHighLevelClient的類中,必須先完成這個對象的初始化,建立與elasticsearch的連接配接。

分為三步:

1)引入es的RestHighLevelClient依賴:

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

4.1.建立索引庫

4.1.1.代碼解讀

建立索引庫的API如下:

【分布式搜尋引擎】elasticsearch基礎

代碼分為三步:

  • 1)建立Request對象。因為是建立索引庫的操作,是以Request是CreateIndexRequest。
  • 2)添加請求參數,其實就是DSL的JSON參數部分。因為json字元串很長,這裡是定義了靜态字元串常量MAPPING_TEMPLATE,讓代碼看起來更加優雅。
  • 3)發送請求,client.indices()方法的傳回值是IndicesClient類型,封裝了所有與索引庫操作有關的方法。

4.1.2.完整示例

在hotel-demo的cn.itcast.hotel.constants包下,建立一個類,定義mapping映射的JSON字元串常量:

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" +
            "}";
}
           

在hotel-demo中的HotelIndexTest測試類中,編寫單元測試,實作建立索引:

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

4.2.删除索引庫

删除索引庫的DSL語句非常簡單:

DELETE /hotel
           

與建立索引庫相比:

  • 請求方式從PUT變為DELTE
  • 請求路徑不變
  • 無請求參數

是以代碼的差異,注意展現在Request對象上。依然是三步走:

  • 1)建立Request對象。這次是DeleteIndexRequest對象
  • 2)準備參數。這裡是無參
  • 3)發送請求。改用delete方法

在hotel-demo中的HotelIndexTest測試類中,編寫單元測試,實作删除索引:

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

4.3.判斷索引庫是否存在

判斷索引庫是否存在,本質就是查詢,對應的DSL是:

GET /hotel
           

是以與删除的Java代碼流程是類似的。依然是三步走:

  • 1)建立Request對象。這次是GetIndexRequest對象
  • 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 ? "索引庫已經存在!" : "索引庫不存在!");
}
           

4.4.總結

JavaRestClient操作elasticsearch的流程基本類似。核心是client.indices()方法來擷取索引庫的操作對象。

索引庫操作的基本步驟:

  • 初始化RestHighLevelClient
  • 建立XxxIndexRequest。XXX是Create、Get、Delete
  • 準備DSL( Create時需要,其它是無參)
  • 發送請求。調用RestHighLevelClient#indices().xxx()方法,xxx是create、exists、delete

5.RestClient操作文檔

為了與索引庫操作分離,我們再次參加一個測試類,做兩件事情:

  • 我們的酒店資料在資料庫,需要利用IHotelService去查詢,是以注入這個接口
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();
    }
}

           

5.1.新增文檔

我們要将資料庫的酒店資料查詢出來,寫入elasticsearch中。

5.1.1.索引庫實體類

資料庫查詢後的結果是一個Hotel類型的對象。結構如下:

@Data
@TableName("tb_hotel")
public class Hotel {
    @TableId(type = IdType.INPUT)
    private Long id;
    private String name;
    private String address;
    private Integer price;
    private Integer score;
    private String brand;
    private String city;
    private String starName;
    private String business;
    private String longitude;
    private String latitude;
    private String pic;
}
           

與我們的索引庫結構存在差異:

  • longitude和latitude需要合并為location

是以,我們需要定義一個新的類型,與索引庫結構吻合:

package cn.itcast.hotel.pojo;

import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
public class HotelDoc {
    private Long id;
    private String name;
    private String address;
    private Integer price;
    private Integer score;
    private String brand;
    private String city;
    private String starName;
    private String business;
    private String location;
    private String pic;

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

           

5.1.2.文法說明

新增文檔的DSL語句如下:

POST /{索引庫名}/_doc/1
{
    "name": "Jack",
    "age": 21
}
           

對應的java代碼如圖:

【分布式搜尋引擎】elasticsearch基礎

可以看到與建立索引庫類似,同樣是三步走:

  • 1)建立Request對象
  • 2)準備請求參數,也就是DSL中的JSON文檔
  • 3)發送請求

變化的地方在于,這裡直接使用client.xxx()的API,不再需要client.indices()了。

5.1.3.完整代碼

我們導入酒店資料,基本流程一緻,但是需要考慮幾點變化:

  • 酒店資料來自于資料庫,我們需要先查詢出來,得到hotel對象
  • hotel對象需要轉為HotelDoc對象
  • HotelDoc需要序列化為json格式

是以,代碼整體步驟如下:

  • 1)根據id查詢酒店資料Hotel
  • 2)将Hotel封裝為HotelDoc
  • 3)将HotelDoc序列化為JSON
  • 4)建立IndexRequest,指定索引庫名和id
  • 5)準備請求參數,也就是JSON文檔
  • 6)發送請求

在hotel-demo的HotelDocumentTest測試類中,編寫單元測試:

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

5.2.查詢文檔

5.2.1.文法說明

查詢的DSL語句如下:

GET /hotel/_doc/{id}
           

非常簡單,是以代碼大概分兩步:

  • 準備Request對象
  • 發送請求

不過查詢的目的是得到結果,解析為HotelDoc,是以難點是結果的解析。完整代碼如下:

【分布式搜尋引擎】elasticsearch基礎

可以看到,結果是一個JSON,其中文檔放在一個

_source

屬性中,是以解析就是拿到

_source

,反序列化為Java對象即可。

與之前類似,也是三步走:

  • 1)準備Request對象。這次是查詢,是以是GetRequest
  • 2)發送請求,得到結果。因為是查詢,這裡調用client.get()方法
  • 3)解析結果,就是對JSON做反序列化

5.2.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);
}
           

5.3.删除文檔

删除的DSL為是這樣的:

DELETE /hotel/_doc/{id}
           

與查詢相比,僅僅是請求方式從DELETE變成GET,可以想象Java代碼應該依然是三步走:

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

5.4.修改文檔

5.4.1.文法說明

修改我們講過兩種方式:

  • 全量修改:本質是先根據id删除,再新增
  • 增量修改:修改文檔中的指定字段值

在RestClient的API中,全量修改與新增的API完全一緻,判斷依據是ID:

  • 如果新增時,ID已經存在,則修改
  • 如果新增時,ID不存在,則新增

這裡不再贅述,我們主要關注增量修改。

代碼示例如圖:

【分布式搜尋引擎】elasticsearch基礎
  • 1)準備Request對象。這次是修改,是以是UpdateRequest
  • 2)準備參數。也就是JSON文檔,裡面包含要修改的字段
  • 3)更新文檔。這裡調用client.update()方法

5.4.2.完整代碼

@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.5.批量導入文檔

案例需求:利用BulkRequest批量将資料庫資料導入到索引庫中。

步驟如下:

  • 利用mybatis-plus查詢酒店資料
  • 将查詢到的酒店資料(Hotel)轉換為文檔類型資料(HotelDoc)
  • 利用JavaRestClient中的BulkRequest批處理,實作批量新增文檔

5.5.1.文法說明

批量處理BulkRequest,其本質就是将多個普通的CRUD請求組合在一起發送。

其中提供了一個add方法,用來添加其他請求:

【分布式搜尋引擎】elasticsearch基礎

可以看到,能添加的請求包括:

  • IndexRequest,也就是新增
  • UpdateRequest,也就是修改
  • DeleteRequest,也就是删除

是以Bulk中添加了多個IndexRequest,就是批量新增功能了。示例:

【分布式搜尋引擎】elasticsearch基礎

其實還是三步走:

  • 1)建立Request對象。這裡是BulkRequest
  • 2)準備參數。批處理的參數,就是其它Request對象,這裡就是多個IndexRequest
  • 3)發起請求。這裡是批處理,調用的方法為client.bulk()方法

5.5.2.完整代碼

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

5.6.小結

  • 建立XxxRequest。XXX是Index、Get、Update、Delete、Bulk
  • 準備參數(Index、Update、Bulk時需要)
  • 發送請求。調用RestHighLevelClient#.xxx()方法,xxx是index、get、update、delete、bulk
  • 解析結果(Get時需要)

繼續閱讀