ElasticSearch

Elasticsearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。

本文基于 Elasticsearch7.8.0。

目录

  • ElasticSearch的安装使用
  • 相关配套设施的安装使用
  • 基本概念简介
  • SpringBoot整合Elasticsearch的两种方案
    1. 自定义封装RestHighLevelClient
    
    1. 基于JPA规范开发

1. ElasticSearch的安装使用

  1. 基于window 解压运行

  2. 基于docker(参见 常用docker环境运维

2. 相关配套设施的安装使用

  1. elasticsearch-head : 类似Navicat的elasticseach的数据可视化查询工具(Chrome商店安装

  2. Kibana:ElasticSearch官方配套可视化组件,可组成ELK日志处理可视化体系

    安装:下载解压

    修改配置文件 config/kibana.yml :

    1
    2
    3
    4
    5
    6
    # 端口
    server.port: 5601
    # elasticsearch 地址
    elasticsearch.hosts: ["http://localhost:9200"]
    # 语言设置
    i18n.locale: "zh-CN"
  1. ./bin/kibana.bat 运行

3. 基本概念简介

  1. 索引 index(名词)

一个 索引 类似于传统关系数据库中的一个 数据库 ,是一个存储关系型文档的地方,是ES对逻辑数据的逻辑存储,索引的结构是为快速有效的全文检索做准备。 索引 (index) 的复数词为 indices 或 indexes 。

  1. 索引 index(动词)

索引一个文档 就是存储一个文档到一个 索引 (名词)中以便它可以被检索和查询到。这非常类似于 SQL 语句中的 INSERT 关键词,除了文档已存在时新chaj文档会替换旧文档情况之外。

  1. 倒排索引

倒排索引源于实际应用中需要根据属性的值来查找记录。这种索引表中的每一项都包括一个属性值和具有该属性值的各记录的地址。由于不是由记录来确定属性值,而是由属性值来确定记录的位置,因而称为倒排索引(inverted index)。带有倒排索引的文件我们称为倒排索引文件,简称倒排文件(inverted file)。

  1. 文档 document

存储在ES上的主要实体叫文档

  1. 文档类型 type(废弃)

在ES中,一个索引对象可以存储很多不同用途的对象。

  1. 映射

存储有关字段的信息,每一个文档类型都有自己的映射。

  1. 面向文档

在应用程序中对象很少只是一个简单的键和值的列表。通常,它们拥有更复杂的数据结构,可能包括日期、地理信息、其他对象或者数组等。

也许有一天你想把这些对象存储在数据库中。使用关系型数据库的行和列存储,这相当于是把一个表现力丰富的对象挤压到一个非常大的电子表格中:你必须将这个对象扁平化来适应表结构—通常一个字段>对应一列—而且又不得不在每次查询时重新构造对象。

Elasticsearch 是 面向文档 的,意味着它存储整个对象或 文档。Elasticsearch 不仅存储文档,而且 _索引 每个文档的内容使之可以被检索。在 Elasticsearch 中,你 对文档进行索引、检索、排序和过滤—而不是对行列数据。这是一种完全不同的思考数据的方式,也是 Elasticsearch 能支持复杂全文检索的原因。

API参考 elasticsearch的常用restful api

主要是index和document的api

用的最多的是document的复合查询

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
插入index : emp
type: it
id: 1
的元素

curl -XPOST 'localhost:9200/emp/it/1?pretty' -H 'Content-Type: application/json' -d'
{
"first_name" : "John",
"last_name" : "Smith",
"age" : 25,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}
'

修改元素
curl -XPUT 'localhost:9200/emp/it/1?pretty' -H 'Content-Type: application/json' -d'
{
"first_name" : "John",
"last_name" : "Smith",
"age" : 25,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}

4. SpringBoot整合Elasticsearch的两种方案

  1. 基于官方的 RestHighLevelClient
    1. 配置RestHighLevelClient 此处使用Restful 高级 API
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    package io.kid1999.esystem.config;

    import org.elasticsearch.client.RestHighLevelClient;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.elasticsearch.client.ClientConfiguration;
    import org.springframework.data.elasticsearch.client.RestClients;

    /**
    * @author kid1999
    * @create 2021-01-27 16:16
    * @description ES 客服端配置
    **/
    @Configuration
    public class ESRestTemplateConfig {

    @Value("${es.host}")
    private String ES_HOST;

    @Bean
    public RestHighLevelClient ESClient() {
    final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
    .connectedTo(ES_HOST)
    .build();
    return RestClients.create(clientConfiguration).rest();
    }
    }
    1. 封装工具类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    package io.kid1999.esystem.utils;

    import lombok.extern.slf4j.Slf4j;
    import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
    import org.elasticsearch.action.bulk.BulkRequest;
    import org.elasticsearch.action.bulk.BulkResponse;
    import org.elasticsearch.action.delete.DeleteRequest;
    import org.elasticsearch.action.delete.DeleteResponse;
    import org.elasticsearch.action.get.GetRequest;
    import org.elasticsearch.action.get.GetResponse;
    import org.elasticsearch.action.index.IndexRequest;
    import org.elasticsearch.action.index.IndexResponse;
    import org.elasticsearch.action.support.master.AcknowledgedResponse;
    import org.elasticsearch.action.update.UpdateRequest;
    import org.elasticsearch.action.update.UpdateResponse;
    import org.elasticsearch.client.RequestOptions;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.elasticsearch.client.indices.CreateIndexRequest;
    import org.elasticsearch.client.indices.CreateIndexResponse;
    import org.elasticsearch.client.indices.GetIndexRequest;
    import org.elasticsearch.common.unit.TimeValue;
    import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
    import org.springframework.stereotype.Component;

    import javax.annotation.Resource;
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.HashMap;

    /**
    * @author kid1999
    * @create 2021-01-27 18:37
    * @description elasticsearch 工具类
    **/
    @Slf4j
    @Component
    public class ESUtil {
    @Resource
    private RestHighLevelClient client;

    private static final String INDEX = "test";


    //创建索引
    public CreateIndexResponse createIndex(String index) throws IOException {
    CreateIndexRequest createIndexRequest = new CreateIndexRequest(index);
    CreateIndexResponse response = client.indices().create(createIndexRequest, RequestOptions.DEFAULT);
    return response;
    }

    /**
    * 测试索引是否存在
    */
    public boolean existIndex(String index) throws IOException {
    GetIndexRequest request = new GetIndexRequest(index);
    boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
    return exists;
    }

    /**
    * 删除索引
    */
    public AcknowledgedResponse deleteIndex(String index) throws IOException {
    DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(index);
    AcknowledgedResponse delete = client.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT);
    return delete;
    }

    /**
    * 添加文档
    */
    public IndexResponse createDocument(String index,Object object) throws IOException {
    IndexRequest request = new IndexRequest(index);
    request.id("1");
    request.timeout(TimeValue.timeValueSeconds(1));
    request.timeout("1s");
    request.source(object);
    IndexResponse response = client.index(request, RequestOptions.DEFAULT);
    return response;
    }

    /**
    * 判断是否存在文档
    */
    public void isExist() throws IOException {
    GetRequest getRequest = new GetRequest(INDEX, "1");
    //不获取返回的source的上下文
    getRequest.fetchSourceContext(new FetchSourceContext(false));
    getRequest.storedFields("_none_");
    boolean exists = client.exists(getRequest, RequestOptions.DEFAULT);
    System.out.println(exists);
    }

    //获取文档信息
    public void getDocument() throws IOException {
    GetRequest getRequest = new GetRequest(INDEX, "1");
    GetResponse response = client.get(getRequest, RequestOptions.DEFAULT);
    //打印文档信息
    System.out.println(response.getSourceAsString());
    System.out.println(response);
    }

    //更新文档信息
    public void updateDocument() throws IOException {
    UpdateRequest request = new UpdateRequest(INDEX, "1");
    request.timeout("1s");
    HashMap<String,String> map = new HashMap<>();
    map.put("name","kid");
    map.put("password","asdasd");
    request.doc(map);
    UpdateResponse update = client.update(request, RequestOptions.DEFAULT);
    System.out.println(update);
    System.out.println(update.status());
    }
    //删除文档
    public void deleteDocument() throws IOException {
    DeleteRequest request = new DeleteRequest(INDEX, "1");
    request.timeout("10s");
    DeleteResponse update = client.delete(request, RequestOptions.DEFAULT);
    System.out.println(update.status());
    }
    //批量插入数据
    public void BulkRequest() throws IOException {
    BulkRequest bulkRequest = new BulkRequest();
    bulkRequest.timeout("10s");
    ArrayList<HashMap<String,String>> maps = new ArrayList<>();
    HashMap<String,String> map = new HashMap<>();
    map.put("name","kid");
    map.put("pass","dasda");
    for (int i = 0; i < maps.size(); i++) {
    bulkRequest.add(
    new IndexRequest(INDEX)
    .id("" + i + 1)
    .source(maps.get(i))
    );
    }
    BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT);
    System.out.println(bulk);

    }


    }
  1. 基于JPA规范开发
    1. application.yml 配置es
    1
    2
    3
    4
    5
    6
    # jpa elasticsearch 配置
    # 不同于 spring.data.elasticsearch.cluster-nodes的老式配置,已被废弃,此为restful高级api的配置
    spring:
    elasticsearch:
    rest:
    uris: kid1999.top:9200
    1. 创建es的entry实体对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    package io.kid1999.esystem.es;

    import lombok.Data;
    import org.springframework.data.annotation.Id;
    import org.springframework.data.elasticsearch.annotations.Document;
    import org.springframework.data.elasticsearch.annotations.Field;
    import org.springframework.data.elasticsearch.annotations.FieldType;

    /**
    * @author kid1999
    * @create 2021-01-28 11:12
    * @description TODO
    **/
    @Data
    @Document(indexName = "my-index")
    public class ElasticsearchDto {
    @Id
    private Long id;
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String title;
    @Field(type = FieldType.Keyword)
    private String category;
    @Field(type = FieldType.Double)
    private BigDecimal price;
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String content;
    }
    • @Document加在类上

    | 类型 | 属性名 | 默认值 | 说明 |
    | :——: | :——————-: | :——: | ————————————————— |
    | String | indexName | 无 | 索引库的名称,建议以项目的名称命名 |
    | String | type | “” | 类型,建议以实体的名称命名 |
    | short | shards | 5 | 默认分区数 |
    | short | replica | 1 | 每个分区默认的备份数 |
    | String | refreshInterval | “1s” | 刷新间隔 |
    | String | indexStoreType | “fs” | 索引文件存储类型 |

    • @Field属性上加的,相当于@Column,可以不写,默认全部添加到ES中。主键上是@Id

    | 类型 | 属性名 | 默认值 | 说明 |
    | :————: | :——————: | :————————-: | ——————————————— |
    | FieldType | type | FieldType.Auto | 自动检测属性的类型 |
    | FieldIndex | index | FieldIndex.analyzed | 默认情况下分词 |
    | boolean | store | false | 默认情况下不存储原文 |
    | String | searchAnalyzer | “” | 指定字段搜索时使用的分词器 |
    | String | indexAnalyzer | “” | 指定字段建立索引时指定的分词器 |
    | String[] | ignoreFields | {} | 如果某个字段需要被忽略 |

    1. 创建es的JPA规范的Repository
    1
    2
    3
    4
    5
    6
    7
    package com.uwith.springbootelasticsearch;

    import org.springframework.data.elasticsearch.repository.ElasticsearchCrudRepository;

    public interface ElasticsearchRepository extends ElasticsearchCrudRepository<ElasticsearchDto, Long> {

    }
    1. 其他用法雷同 JPA操作

参考:博客官方文档