Elasticsearch
概述
The Elastic Stack, 包括 Elasticsearch、Kibana、Beats 和 Logstash(也称为 ELK Stack)。能够安全可靠地获取任何来源、任何格式的数据,然后实时地对数据进行搜索、分析和可视化。Elasticsearch是一个基于 Lucene 的搜索服务器,简称为 ES,ES 是一个开源的高扩展的分布式全文搜索引擎,是整个 Elasticsearch 技术栈的核心。它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理 PB 级别的数据
Google、百度类的网站搜索,它们都是根据网页中的关键字生成索引,我们在搜索的时候输入关键字,它们会将该关键字即索引匹配到的所有网页返回;还有常见的项目中应用日志的搜索等等。对于这些非结构化的数据文本,关系型数据库搜索不是能很好的支持
一般传统数据库,全文检索都实现的很鸡肋,因为一般也没人用数据库存文本字段。进行全文检索需要扫描整个表,如果数据量大的话即使对 SQL 的语法优化,也收效甚微。建立了索引,但是维护起来也很麻烦,对于 insert 和 update 操作都会重新构建索引
基于以上原因可以分析得出,在一些生产环境中,使用常规的搜索方式,性能是非常差的:
- 搜索的数据对象是大量的非结构化的文本数据
- 文件记录量达到数十万或数百万个甚至更多
- 支持大量基于交互式文本的查询
- 需求非常灵活的全文搜索查询
- 对高度相关的搜索结果的有特殊需求,但是没有可用的关系数据库可以满足
- 对不同记录类型、非文本数据操作或安全事务处理的需求相对较少的情况。为了解决结构化数据搜索和非结构化数据搜索性能问题,我们就需要专业,健壮,强大的全文搜索引擎
这里说到的全文搜索引擎指的是目前广泛应用的主流搜索引擎。它的工作原理是计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。这个过程类似于通过字典中的检索字表查字的过程。本篇文章主要介绍 Elasticsearch 8.x,对于更早之前的 7.x 请自行查阅资料
索引操作
创建索引
在 Elasticsearch中,索引是一个包含文档的集合。我们可以使用 PUT 命令来创建一个新的索引。例如,要创建一个名为 "my_index" 的索引,可以使用以下命令:
1 | PUT my_index |
注意:索引名称必须小写且不能重复。很多时候我们可能忘记了有没有创建过这个索引,这个时候我们可以使用 HEAD
来查看所以 是否存在
1 | HEAD my_idnex |
如果不存在它回返回 404 - Not Found
查询索引
使用 GET 命令来查询索引。例如,要查询一个名为 "my_index" 的索引,可以使用以下命令:
1 | GET my_idnex |
也可以使用 _cat
查询所有的索引:
1 | GET _cat/indices |
修改索引
ES 软件是不允许修改索引信息的,如果你想修改,只能是创建一个新的索引
删除索引
删除索引使用 DELETE 命令 后面加上索引名称
1 | DELETE my_index |
相应结果 acknowledged 为 true,则表示删除成功
文档操作
创建文档
在 Elasticsearch 中,文档是我们要搜索和分析的数据。我们可以使用 PUT 或 POST 命令将文档添加到索引中。使用 PUT 的时候需要添加一个唯一性标识(在 _doc/
后面加)例如,要将一个名为 "my_document" 的文档添加到 "my_index" 索引中,可以使用以下命令:
1 | PUT my_index/_doc/1001 |
查询文档
使用 GET 命令查询文档
1 | GET my_index/_doc/1001 |
如果是使用 POST 方法创建的文档,id 很长或者你不知道,你也可以查询索引中的所有文档数据
1 | GET my_index/_search |
修改文档
修改文档和创建文档类似,只需要你把数据换成你想要的即可
1 | PUT my_index/_doc/1001 |
删除文档
使用 DELETE 命令来删除文档。例如,要删除 "my_index" 索引中 id 为 1
1 | DELETE my_index/_doc/1 |
文档搜索
在 Elasticsearch 中,你可以使用多种方式进行文档搜索,例如:
1 | GET my_index/_search |
注意:Elasticsearch match 使用的是分词查询。它会将查询字符串进行分词,然后在字段中匹配任何一个分词。如果你想要完整的匹配可以用 term,term 查询用于精确匹配,它不会对查询字符串进行分词
1 | GET my_index/_search |
在 Elasticsearch 中查询时,你可以使用布尔查询将多个条件进行组合,并使用 sort 和 from / size 参数对结果进行排序分页。下面是一个示例查询,该查询包括两个条件:
- match 查询:匹配 title 字段包含 "apple" 的文档
- range 查询:过滤时间戳字段 timestamp,只包括在过去一天的文档
1 | GET my_index/_search |
聚合操作
在 Elasticsearch 中,聚合是一种强大的功能,用于按照特定条件对文档进行分组、计数、统计等操作。以下是一些常见的聚合操作示例:
- Terms Aggregation: 根据字段值进行分组,并计算每个分组的文档数量。例如,聚合统计 "category" 字段的不同取值及其文档数量:
1 | GET my_index/_search |
- Range Aggregation: 将文档按某个字段的范围进行分组,并计算每个分组的文档数量。例如,根据 "price" 字段将文档按价格区间分组,统计每个价格区间的文档数量:
1 | GET my_index/_search |
- Date Histogram Aggregation: 将文档按日期字段进行分组,并计算每个时间段内的文档数量或其他统计指标。例如,按 "timestamp" 字段将文档按天进行分组,统计每天的文档数量:
1 | GET my_index/_search |
索引模版
索引模板(Index Templates)是在 Elasticsearch 中创建和管理索引的重要工具。通过创建索引模板,可以在创建新索引时自动应用某些设置、映射模板和配置,从而使索引的管理和维护更加容易和一致。以下是一个索引模板的示例,它会自动为以 my-index 开头的索引创建设置:
1 | PUT _template/my_template |
在此示例中,我们创建了一个名为 my_template 的索引模板。index_patterns 项定义了与模板匹配的索引名称模式,例如 my-index-*
将匹配所有以 my-index-
开头的索引。settings 项定义了所有与这些索引相关的设置,例如 number_of_shards 参数设置了主分片数为 1;mappings 项定义了索引中各个字段的数据类型和其他属性,例如 message 字段的类型为 text。当新的符合模板匹配模式的索引被创建时,它将自动应用这些设置和映射模板
中文分词
在 Elasticsearch 中使用中文分词时,推荐使用 ik 分词器,它是开源分词器 Lucene 和 Elasticsearch 的联合产物。使用 ik 分词器需要进行以下步骤:
- 安装 ik 分词器插件。在 Elasticsearch 安装目录下 bin 目录中运行以下命令安装 ik 分词器插件:
1 | ./Elasticsearch-plugin install https://github.com/medcl/Elasticsearch-analysis-ik/releases/download/v7.10.2/Elasticsearch-analysis-ik-7.10.2.zip |
以上命令中的版本号需要与 Elasticsearch 的版本号对应,具体请根据实际情况修改
- 创建索引时使用 ik 分词器。在创建索引时,可以指定使用 ik 分词器。例如:
1 | PUT /my_index |
以上示例创建了一个名为 my_index 的索引,并使用 ik 分词器。analyzer 使用 ik_max_word 指定使用最细粒度分词,而search_analyzer 使用 ik_smart 指定使用最粗粒度分词。可以根据需要选择合适的分词器
- 在查询时使用 ik 分词器。在查询时,可以指定使用 ik 分词器。例如:
1 | POST /my_index/_search |
在查询结果中匹配 content 字段时,使用了 ik_smart 分词器。总体来说,使用 ik 分词器可以很好地实现中文分词,提高索引和检索的准确性
文档评分机制
Elasticsearch 中的文档评分机制是通过内置的相关性算法来对查询结果进行排序,并生成一个相对的分数,表示文档与查询的相关性。这个分数可以帮助你理解和排序查询结果。文档评分主要是基于以下两个核心概念:
- 逆向文档频率(Inverse Document Frequency,IDF):表示一个术语在索引中的罕见程度。当一个术语在更多文档中出现时,它的 IDF 值较低,因为它不太具有唯一性。而当一个术语在较少文档中出现时,它的 IDF 值较高,因为它很有可能是与查询相关的关键术语
- 词频(Term Frequency,TF):表示一个术语在一个特定文档中的出现频率。当一个术语在文档中出现的次数越多,它的 TF 值就越高。TF 反映了一个术语在查询中的重要性
这两个概念结合起来,形成了一种称为 TF-IDF
的计算公式,用于计算文档与查询之间的相关性。Elasticsearch 使用默认的 BM25 算法(BM stands for Best Matching,25 是其改进版本的编号)来计算相关性得分,该算法综合考虑了词频、逆向文档频率以及文档长度等因素。
通过文档评分机制,Elasticsearch 会根据查询条件和相关性算法为每个文档赋予一个分数,并按照分数对结果进行排序(默认降序)。这样,查询结果中的文档将按照与查询相关性从高到低的顺序排列,以便用户更容易获得最相关的结果。以下是一个简单的示例:
假设我们有一个索引,其中包含了一些商品的文档,每个文档都有一个 title 字段和一个 description 字段。我们希望搜索包含关键词 "手机" 的商品,并按与查询相关性进行排序
- 建立索引
1 | PUT /products |
- 插入文档
1 | POST /products/_doc/1 |
- 执行搜索
1 | POST /products/_search |
运行结果如下:
1 | "hits": [ |
在这个结果中,相关性得分越高的文档排名越靠前。在我们的示例中,含有关键词 "手机" 的文档得分最高的是 "小米 11",其次是 "iPhone 12",最后是 "华为 P40"
如果你想要在不改变数据的情况下改变数据的顺序,可以使用 boost
来设置特定的搜索查询元素的权重,以便在文档评分过程中提高或降低相关性得分。boost
可以应用于查询中的整个查询语句、特定字段、特定词项或字符。例如,如果 "title" 字段对搜索结果更加重要,则可以这样写:
1 | POST /products/_search |
JAVA API
随着 Elasticsearch 8.x 新版本的到来,Type 的概念被废除,为了适应这种数据结构的改变,Elasticsearch 官方从 7.15 版本开始建议使用新的 Elasticsearch Java Client。根据官方文档提示,想要使用需要配置以下环境:
- Java 8 or later.
- A JSON object mapping library to allow seamless integration of your application classes with the Elasticsearch API. The examples below show usage with Jackson.
1 | <dependency> |
设置依赖项后,应用程序可能会失败,并显示 ClassNotFoundException: jakarta.json.spi.JsonProvider
,如果发生这种情况,则必须显式添加 jakarta.json:jakarta.json-api:2.0.1
依赖项
1 | <dependency> |
Connecting
You can connect to the Elastic Cloud using an API key and the Elasticsearch endpoint.
1 | // URL and API key |
Creating an index
This is how you create the product index:
1 | esClient.indices().create(c -> c |
Indexing documents
This is a simple way of indexing a document, here a Product application object:
1 | Product product = new Product("bk-1", "City bike", 123.0); |
Getting documents
You can get documents by using the following code:
1 | GetResponse<Product> response = esClient.get(g -> g |
Searching documents
This is how you can create a single match query with the Java client:
1 | String searchText = "bike"; |
Updating documents
This is how you can update a document, for example to add a new field:
1 | Product product = new Product("bk-1", "City bike", 123.0); |
Deleting documents
1 | esClient.delete(d -> d.index("products").id("bk-1")); |
Deleting an index
1 | esClient.indices().create(c -> c |
Bulk
BulkRequest
包含一个操作集合,每个操作都是一个具有多个变体的类型。要创建此请求,可以方便地将构建器对象用于主请求,并为每个操作使用流畅的 DSL。下面的示例演示如何为列表或应用程序对象编制索引:
1 | List<Product> products = fetchProducts(); |
- 添加操作(请记住,列表属性是累加的)。 op 是一个生成器,其 BulkOperation 是变体类型。此类型具有 index 、 create update 和 delete 变体
- 选择 index 操作变型, idx 是 IndexOperation 的构建器
- 设置索引操作的属性,类似于单个文档索引:索引名称、标识符和文档
EQL
Elasticsearch EQL(Elasticsearch Query Language)是一种专门为 Elasticsearch 设计和优化的查询语言。它与传统的 Elasticsearch 查询语言(如 Lucene 查询字符串)相比,更加灵活、易用和可扩展。EQL 使用自然语言风格的语法,可以轻松地编写和理解复杂的查询逻辑。当使用 Elasticsearch EQL 进行查询时,可以按不同的需求和数据结构进行编写。以下是一些示例:
- 全文搜索
1 | GET /my_index/_eql/search |
这个查询会在名为 "my_index" 的索引中搜索所有具有 "source.ip" 字段值为 "192.168.0.1" 的文档
- 范围查询
1 | GET /my_index/_eql/search |
这个查询会在 "my_index" 索引中搜索所有具有 "response_time" 值大于 100 且小于 200 的文档
- 布尔查询
1 | GET /my_index/_eql/search |
这个查询会在 "my_index" 索引中搜索满足以下条件的文档:具有 "user.role" 值为 "admin",且 "status" 值为 "active" 或 "pending"
SQL
Elasticsearch SQL 是一个用于在 Elasticsearch 中执行 SQL 风格查询的插件。它允许你使用熟悉的 SQL 语法来查询和操作 Elasticsearch 中的数据。以下是一些示例,首先我们先创建一些数据:
1 | PUT /library/_bulk?refresh |
然后可以使用 SQL search API 执行 SQL:
1 | POST /_sql?format=txt |
运行结果如下:
1 | author | name | page_count | release_date |
还可以使用 SQL CLI。x-pack 的 bin 目录中有一个脚本可以启动它:
1 | $ ./bin/elasticsearch-sql-cli |
SQL CLI同样可以运行相同的查询:
1 | sql> SELECT * FROM library WHERE release_date < '2000-01-01'; |