ElasticSearch

一、简介与安装

1、简介

ElasticSearch全文搜索引擎,高可用,实时存储数据,检索数据,本身扩展性好,通过RESTful API来隐藏Lucene的复杂性,让全文搜索变得简单

ElasticSearch与Solr性能比较

  1. 单纯对已有的数据进行检索,Solr更快

  2. 当建立索引时,Solr会产生阻塞,查询性能比较差,ES具有明显的优势

  3. 随着数据量增加,Solr的搜索效率会变低,而ES没有明显变化

两者区别总结

  1. Solr利用Zookeeper进行分布式管理,而ES自带分布式协调管理功能

  2. Solr支持更多格式的数据,比如JSON,XML,CSV,而ElasticSearch,仅支持json文件格式

  3. Solr查询快,单更新索引时慢,即插入快删除慢,用于电商等查询多的应用,ES建立索引快,实时查询快,用于facebook新浪等搜索

  4. Solr比较成熟,有更大更成熟的用户,开发者和社区贡献

https://zhuanlan.zhihu.com/p/567196335

2、安装

安装ES,目录中最好不要有空格等,不然后面安装ik分词器插件会有问题

下载地址:https://www.elastic.co/cn/downloads/past-releases#elasticsearch

解压即用,通过bin目录下的elasticsearch.bat启动,简单的连接工具,可以使用谷歌Multi ElasticSearch Heads插件

安装kibana

下载地址:https://www.elastic.co/cn/downloads/past-releases#kibana

解压即用,通过bin目录下的kibana.bat启动,注意需要node环境,可以通过conf目录下的kibana.yml修改默认配置,如将页面改成汉化版本

i18n.locale: "zh-CN"

二、基本操作

1. 基本概念

elasticsearch是面向文档的,类似数据库,两者概念类比,值得注意的是,es是以json格式存储数据的。

关系数据库

elasticsearch

数据库(database)

索引(indices)

表(tables)

types(慢慢会被启用)

行(rows)

documents

字段(columns)

fields

物理设计:

elasticsearch把索引划分成多个分片,以及分片对应的副本,这和kafka中主题,分区和副本概念类似,包括分区及其副本在集群中节点中如何存储的都很类似。

文档:

一个文档包含字段和值,形式类似map,当然字段也有它自身的类型

2. 倒排索引

elasticsearch使用倒排索引的结构,底层使用Lucene的倒排索引,这种结构适用于全文快速索引。首先理解一些什么是正排索引,这种结构适用于需要顺序访问文档内容的场景。考虑一个简化的博客集合,其中包含三篇博客:

  • 博客1: 《LangChain学习笔记——Model I/O》

  • 博客2: 《Docker存储驱动初探》

  • 博客3: 《几种常见的消息队列介绍》

在正排索引中,每篇文章都按照其在文档集合中的顺序存储,每篇博客包含了其所包含的所有词汇。以下是一个简化的正排索引示例:

博客ID

博客标题

1

LangChain学习笔记——Model I/O

2

Docker存储驱动初探

3

几种常见的消息队列介绍

这个正排索引示例中,我们可以通过博客ID快速找到每篇文章的完整内容。例如,如果我们想查看文档2的内容,只需根据文档ID为2检索正排索引即可得到“正排索引的解析”。

但是如果我们需要进行搜索,比如搜索与“消息队列”相关的内容,就可能需要做全表的扫描,性能开销急剧提升。这就需要引入倒排索引来有效地处理用户的检索需求。

倒排索引(Inverted Index)是一种数据结构,用于在大规模文档集合中快速定位包含特定关键词的文档。相对于正排索引,倒排索引以关键词为中心,将每个关键词映射到包含该关键词的文档列表。这种颠倒的结构使得搜索引擎能够高效地响应用户的查询,快速返回相关的文档。

同样以上面的博客集合作为示例,

  1. 博客1:《LangChain学习笔记——Model I/O》

    • 内容包含关键词:LangChain、学习笔记、Model I/O。

  2. 博客2:《Docker存储驱动初探》

    • 内容包含关键词:Docker、存储驱动、初探。

  3. 博客3:《几种常见的消息队列介绍》

    • 内容包含关键词:消息队列、介绍、常见。

倒排索引示例:

关键词

文档ID列表

LangChain

1

学习笔记

1

Model I/O

1

Docker

2

存储驱动

2

初探

2

消息队列

3

介绍

3

常见

3

倒排索引示例:

通过这个倒排索引示例,我们可以看到每个关键词都与包含该关键词的博客的文档ID关联。例如,如果用户查询关键词“消息队列”,搜索引擎可以迅速找到文档ID列表为3的博客,即《几种常见的消息队列介绍》。这种方式使得搜索引擎能够快速过滤掉与查询无关的文档,提高检索效率。

倒排索引构建过程:

构建倒排索引是一个复杂而关键的过程,它涉及多个步骤,可以归纳为两个阶段:

  • 文档预处理阶段

    • 分词(Tokenization): 将文档拆分成单词或词汇单元。这个过程使用分词器,将文本切分成有意义的词语,形成一个词汇列表。

    • 去停用词(Stopword Removal): 移除常见且在搜索中没有实际意义的词语,如“的”、“是”等。这有助于提高倒排索引的效率和准确性。

    • 词干提取(Stemming): 将词语还原为其词干形式,去除词尾,以便将相关的词汇映射到同一词根,减少索引的大小。

  • 倒排生成阶段

    • 建立词汇表: 将预处理后的文档中的所有唯一词语构建成一个词汇表。每个词汇都有一个唯一的标识符。

    • 映射关键词到文档ID: 遍历每个文档,对于文档中的每个关键词,将其映射到文档的唯一标识符(文档ID)。这样的映射关系通常以字典的形式保存。

    • 生成倒排列表: 对于每个关键词,创建一个倒排列表,其中包含映射到该关键词的所有文档ID。倒排列表实际上是一个映射,将关键词与包含该关键词的文档关联起来。

3. ik 分词器插件

下载地址:https://github.com/infinilabs/analysis-ik/releases

解压,然后放到ES的plugin目录下,然后使用elasticsearch-plugin list命令查看是否加载成功,注意,读取的是文件一级目录下的plugin-descriptor.properties,失败看是否有多级目录。

分词:简单理解就是智能的将一段话,分成几个词语,便于建立倒排索引

包含两个算法

  • ik_smart为最少切分

  • ik_max_word为最细颗粒度切分

# 使用分析器分析字段
GET _analyze
{
  "analyzer": "ik_max_word",
  "text": "中国共产党"
}

4. 基本操作

补充

postman方式参考连接https://blog.51cto.com/u_16213720/11004319,kibana控制台命令类似,以下以kibana操作为例

注意字段值都有其对应的字段类型,参考链接https://blog.csdn.net/qq_26664043/article/details/136101716

需要注意的是,text类型会被分析器处理单独的词项(term)适合全文搜索,keyword不会被分析器处理,适合精确搜索,如身份证,姓名等

当索引已创建时,修改索引字段的mapping映射,方式参考:https://blog.csdn.net/jdcdev_/article/details/141063672

基础命令

method

url地址

描述

PUT

localhost:9200/索引名称/类型名称/文档id

创建指定文档(指定文档id)

GET

localhost:9200/索引名称/类型名称/文档id

通过文档id查询文档

DELETE

localhost:9200/索引名称/类型名称/文档id

删除文档

POST

localhost:9200/索引名称/类型名称

创建文档(随机文档id)

POST

localhost:9200/索引名称/类型名称/文档id/_update

修改文档

POST

localhost:9200/索引名称/类型名称/_search

查询所有文档

新增数据一般使用PUT方式,修改数据使用POST + /_update的方式,因为PUT是覆盖的方式更新,意味着如果只更新一个字段也必须附加上其他字段,不然其他字段没有了,而POST方式则不需要。

精确查询

# 创建索引
PUT /test
{
  "mappings": {
    "properties": {
      "name": {
        "type": "keyword"
      },
      "nicheng": {
        "type": "text"
      },
      "age": {
        "type": "integer",
        "null_value": -1 
      }
    }
  }
}
​
# 获取索引信息
GET /test
​
# 删除索引
DELETE test
​
# PUT方式新增
PUT /test/_doc/1
{
  "name": "张三",
  "nicheng": "法外狂徒",
  "age": 99
}
​
# POST方式新增
PUT /test/_doc/2
{
  "name": "李四",
  "age": 100
}
​
# POST方式新增,随机文档ID
POST /test/_doc
{
  "name": "王五",
  "nicheng": "钻石王老五",
  "age": 100
}
​
# POST方式查询
POST /test/_doc/_search
​
# POST更新文档
POST /test/_doc/2/_update
{
  "doc":{
    "name": "褒姒",
    "nicheng": "烽火戏诸侯" 
  }
}
​
# 根据文档ID查询
GET test/_doc/1
​
# 精准匹配查询
GET test/_doc/_search?q=name:张三

5. 复杂查询

高级搜索,参考链接https://blog.51cto.com/u_93011/11724660

查询结果中的文档都会包含一个分数值,这个值越大,代表越符合查询条件,有很多因素会影响评分,如bool查询中的

  • must:相当于JAVA中的 && 操作符,必须匹配,支持评分。

  • should:相当于 JAVA 中的 || 操作符,选择性匹配,支持评分。

  • must_not:相当于JAVA中的 !,必须不匹配,不支持评分。

  • filter:简单的过滤,不支持评分。

# 查询,多条件复合查询,指定查询字段,排序,分页查询,分组中的数据查询,精确查询,多个值精确查询,高亮
# 查询
GET /test/_doc/_search
{
  "query": {
    "match": {
      "name": "王五"
    }
  }
}
​
# 多条件查询
GET /test/_doc/_search
{
  "query": {
    "bool": {
      "must": [{
        "match": {
          "name": "王五"
        }
      }],
      "filter":[{
        "term": {
          "age": 100
        }
      }]
    }
  }
}
​
# 范围查询
GET /test/_doc/_search
{
  "query": {
    "range": {
      "age": {
        "gte": 0,
        "lte": 200
      }
    }
  }
}
​
# 指定查询字段,排序,分页查询
GET /test/_doc/_search
{
  "query": {
    "range": {
      "age": {
        "gte": 0,
        "lte": 200
      }
    }
  },
  "_source": ["name", "age"],
  "sort": [{
    "age":{
      "order": "desc"
    }
  }],
  "from":0,
  "size":1
}
​
# POST更新文档
POST /test/_doc/1/_update
{"doc":{"tags": ["技术宅", "红", "男"]}}
POST /test/_doc/2/_update
{"doc":{"tags": ["皇后", "白", "女"]}}
POST /test/_doc/tCQ5f5EB4R2zXQlvUJoz/_update
{"doc":{"tags": ["技术宅", "黑", "男"]}}
​
# 匹配分组中的数据
GET /test/_doc/_search
{
  "query": {
    "match": {
      "tags": "男 红"
    }
  }
}
​
# 多个值精确查询
GET /test/_doc/_search
{
  "query": {
    "bool":{
      "should":[
        {"term": {"age": 99}},
        {"term": {"age": 100}}
      ]
    }
  }
}
​
# POST更新文档
POST /test/_doc/1/_update
{"doc":{"nicheng": "法外狂徒测试"}}
POST /test/_doc/2/_update
{"doc":{"nicheng": "烽火戏诸侯测试"}}
# 高亮查询,使用highlight,可以给条件中的文字加样式
GET /test/_doc/_search
{
  "query": {
    "match": {
      "nicheng" : "测试"
    }
  },
  "highlight": {
    "pre_tags": "<p class='key' style='color:red'>",
    "post_tags": "</p>",
    "fields": {
      "nicheng":{}
    }
  }
}
​
​

三、CRUD实践

1. 集成SpringBoot

导入依赖

<!-- ES -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

添加配置

spring:
  elasticsearch:
    rest:
      uris: http://127.0.0.1:9200

简单查询实例

@Test
public void testQueryDocument() {
    QueryBuilder queryBuilder = QueryBuilders.matchQuery("name", "名字");
    String indexName = "spring_boot_es";
    Integer pageNum = 1;
    Integer pageSize = 2;
    //1、条件搜索 参数 索引
    SearchRequest searchRequest = new SearchRequest(indexName);
    //2、构建搜索条件
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    searchSourceBuilder.from((pageNum - 1) * pageSize);
    searchSourceBuilder.size(pageSize);
    //3、注入执行查询条件
    searchSourceBuilder.query(queryBuilder);
    //4、设置查询超时时间
    searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
    //5、执行查询 返回结果
    searchRequest.source(searchSourceBuilder);
    SearchResponse response = null;
    try {
        response = client.search(searchRequest, RequestOptions.DEFAULT);
    } catch (IOException e) {
        e.printStackTrace();
    }
    List<Map<String, Object>> list = Arrays.stream(response.getHits().getHits()).map(o -> o.getSourceAsMap()).collect(Collectors.toList());
​
    list.forEach(s -> {
        if(!Objects.isNull(s)) {
            log.info(s.get("name").toString());
        }
    });
​
}

2. 复杂查询

参考文档:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high.html

3. 聚合查询

类似于SQL里的group,max,avg等聚合查询,对查询结果进行统计分析

参考链接:https://blog.csdn.net/chuige2013/article/details/129635792