Elasticsearch实战
# 为何学习 Elasticsearch
Elasticsearch 是一款开源的、分布式的、实时的搜索与数据分析引擎,它具有高度的可伸缩性、灵活性和实时性,能够帮助用户在大量数据中进行高效的搜索、分析和数据挖掘。在当今这个信息爆炸的时代,学习 Elasticsearch 成为了一项重要的技能,原因如下:
高性能搜索:Elasticsearch 基于 Apache Lucene 构建,提供了全文搜索、模糊匹配和多语言支持等功能,使得用户可以在海量数据中快速找到相关信息。
实时数据分析:Elasticsearch 支持实时的数据分析和聚合,用户可以在短时间内对大量数据进行处理,以获得有价值的洞察和业务智能。
分布式与可伸缩性:Elasticsearch 具有分布式架构,能够在多台服务器上横向扩展,轻松应对不断增长的数据量和查询负载。
丰富的生态系统:Elasticsearch 是 ELK(Elasticsearch、Logstash 和 Kibana)技术栈的核心组件,结合其他组件可以实现日志分析、监控、数据可视化等多种场景。
广泛的应用领域:Elasticsearch 在各种领域都有广泛的应用,如电商、金融、医疗、教育等,学习 Elasticsearch 有助于提高在这些领域的工作技能。
# 发展历史
Elasticsearch 的发展历程如下:
2004年:Doug Cutting 和 Mike Cafarella 开始开发 Apache Lucene,一款开源的全文搜索引擎库,后来成为 Elasticsearch 的基础技术。
2010年:Shay Banon 创立 Elasticsearch 项目,以解决分布式环境下的搜索和分析问题。他的启发源自于他为家人开发的食谱搜索应用。
2012年:Elasticsearch 成立公司,开始提供商业支持和服务。
2013年:公司推出 Logstash 和 Kibana,与 Elasticsearch 配合使用,形成了 ELK 技术栈。
2015年:Elasticsearch 公司正式更名为 Elastic,继续扩展其产品线,包括 Beats、Elastic Cloud 等。
2018年:Elastic 在纳斯达克上市,股票代码为 "ESTC"。
从诞生至今,Elasticsearch 已经成为全球最受欢迎的搜索与分析引擎之一,并得到了众多知名企业和开发者的广泛认可与支持。
# 基础概念
# 集群
集群是由多个 Elasticsearch 节点组成的一个完整的系统,这些节点共同工作以提供分布式搜索和数据分析功能。集群具有以下特点:
高可用性:集群中的节点可以相互协作,当某个节点出现故障时,其他节点会接管其工作,确保系统的正常运行。
数据分布:集群可以将数据分布在多个节点上,实现数据的负载均衡和性能优化。
水平扩展:当集群需要处理更多的数据和查询时,可以通过添加节点来提高集群的处理能力。
# 节点
节点是 Elasticsearch 集群中的一个独立的服务器,负责处理数据和执行搜索、分析任务。节点有以下类型:
主节点:负责管理集群的元数据,如索引的创建、删除、映射配置等。一个集群只能有一个主节点。
数据节点:负责存储数据和执行数据相关的操作,如搜索、分析、聚合等。
协调节点:负责分发客户端的请求到各个数据节点,并汇总、返回结果。
摄取节点:负责预处理数据,如数据转换、过滤等,然后将处理后的数据传递给数据节点。
在一个Elasticsearch集群中,主节点和协调节点的选择是由集群自动处理的。它们的选择过程是:
主节点的选举:集群中会根据特定的选举算法自动选择一个主节点。主节点负责协调集群的整体操作,如管理索引和分片的分配、集群状态的维护等。主节点的选举依赖于集群配置中的一些因素,例如节点的优先级等。
协调节点的选择:协调节点负责接收来自客户端的请求并将其转发给合适的数据节点处理。协调节点的选择通常简单,可以由负载均衡机制(如客户端的负载均衡器或代理)在请求到达时自动选择一个可用的协调节点。
一旦主节点和协调节点被选出,它们的角色将在运行时保持稳定。如果主节点因任何原因不可用,集群将自动选择新的主节点。同样,如果协调节点不可用,客户端的请求将被直接转发给可用的协调节点。
需要注意的是,主节点和协调节点的角色不是互斥的。一个节点可以同时充当主节点和协调节点,或者一个节点可以作为主节点,另一个节点则作为专门的协调节点。
有时候人工干预也可以用于选择主节点和协调节点。例如,通过设置节点的优先级来指定特定节点担任主节点。但通常情况下,集群自动进行主节点和协调节点的选举,并由内部机制管理节点角色的稳定性和故障转移。
# 分片
分片是 Elasticsearch 的一个核心概念,它可以将一个大的索引拆分成多个较小的、独立的部分。分片有以下优点:
水平扩展:通过增加分片数量,可以在更多节点上分布数据,提高查询和写入的性能。
容错性:当某个分片所在的节点出现故障时,其他节点上的分片仍然可以提供服务。
并行处理:分片可以让多个节点同时处理数据,提高查询和分析的速度。
# 副本
副本是分片的一个备份,它可以提高数据的可用性和容错性。副本有以下作用:
容错性:当一个分片所在的节点发生故障时,副本可以替代原始分片继续提供服务。
负载均衡:副本可以在多个节点上分布,提供搜索和读取的负载均衡(非写入,相当于读写分离的概念)。
高可用性:副本可以确保在节点故障或网络分区等异常情况下,数据仍然可以被访问。
在设计 Elasticsearch 集群时,需要根据实际需求和场景来合理配置分片和副本的数量,以达到最佳的性能和可靠性。
# 索引
在 Elasticsearch 中,索引是一个用于存储和检索文档的逻辑空间。它类似于传统数据库中的表,是组织和管理数据的基本单位。索引具有以下特点:
映射:索引定义了数据的结构和类型,称为映射。映射包括字段名、数据类型、分析器等属性,可以帮助 Elasticsearch 更高效地处理和查询数据。
分片与副本:索引由多个分片组成,每个分片可以有一个或多个副本。分片和副本可以在集群的多个节点上分布,提高查询和写入的性能,以及数据的可用性和容错性。
动态索引:Elasticsearch 支持动态创建索引和映射,当接收到新的文档时,可以根据预定义的规则自动创建对应的索引和映射。
别名:为了方便管理和查询,可以为索引设置别名。使用别名可以让你在不改变查询逻辑的情况下,轻松地对索引进行重建或者迁移。
# 文档
文档是 Elasticsearch 中存储和处理的基本数据单位,它代表了一个具体的实体或对象。文档具有以下特点:
JSON 格式:文档使用 JSON 格式表示,这是一种轻量级、易于阅读和编写的数据交换格式。JSON 格式使得文档可以包含复杂的数据结构,如数组、嵌套对象等。
唯一标识:每个文档都有一个唯一的标识,称为
_id
。你可以在创建文档时自定义_id
,或者让 Elasticsearch 自动生成。元数据:除了用户定义的数据之外,文档还包含一些元数据,如
_index
(所属索引)、_type
(文档类型,已经在 Elasticsearch 7.x 中废弃)和_version
(文档版本)等。全文搜索:Elasticsearch 可以对文档的内容进行全文搜索、模糊匹配和多语言支持等功能,使得用户可以在大量数据中快速找到相关信息。
在 Elasticsearch 中,你可以执行各种操作来创建、更新、删除和查询文档,如使用 RESTful API、Java 客户端或者其他语言的客户端库。同时,Elasticsearch 还提供了一系列高级功能,如排序、过滤、聚合和地理位置搜索等,帮助你更有效地处理和分析数据。
在 Elasticsearch 中,setting 和 mapping 是用于定义和配置索引结构的两个重要概念。
# 倒排索引
Elasticsearch使用倒排索引(Inverted Index)来快速查询文档。倒排索引是一个数据结构,它存储了每个术语(Term)在哪些文档中出现过。对于一个查询语句,Elasticsearch会在倒排索引中寻找关键词所在的文档,然后对这些文档进行计算,得出符合查询条件的结果。
倒排索引的构建过程主要包括以下两个步骤:
分词(Tokenization):将文档中的字符串切分成一个个单词或术语。这里的单词或术语是指被定义了的一些基本单位,例如英文字母或数字等。
建立词条表(Term Dictionary):对于文档中的每个术语,建立一张该术语出现的文档列表。这样就可以查找包含某个词语的文档。
在倒排索引中,术语是一个基本单位,也称为Term。每个Term都有一个Document Frequency(DF),即该术语出现在的文档数。此外,每个Term在每个文档中可能会出现多次,为了存储更多信息,索引还会维护一个Positions List,用于记录Term在文档中的位置信息。
倒排索引使得Elasticsearch能够以较小的代价(O(1)级别)查找包含某个术语的文档列表。倒排索引也允许Elasticsearch支持高效的全文搜索、短语搜索等复杂查询。可以说,倒排索引是Elasticsearch的核心特性之一,也是其高效检索的关键所在。
# Setting
Setting 是用于配置索引参数和行为的设置。它包括了诸如分片数量、副本数量、分词器配置等方面的内容。Setting 可以在创建索引时进行配置,也可以在索引创建后进行部分修改。以下是一些常见的 setting 配置:
分片数量:设置索引的主分片数量,这个值在索引创建后不能修改。例如:
"number_of_shards": 5
。副本数量:设置每个分片的副本数量,这个值在索引创建后可以修改。例如:
"number_of_replicas": 1
。分词器配置:配置自定义分词器、分词过滤器等,以支持特定语言或业务场景的文本分析。例如,可以定义一个用于处理英文文本的自定义分词器。
通过设置合适的 setting 参数,可以根据具体应用场景和需求对索引进行优化,以提高查询性能和数据可用性。
# Mapping
Mapping 是用于定义索引中文档的字段结构和属性的映射。它相当于传统关系型数据库中的表结构定义。Mapping 包括字段名、字段类型、分析器等信息。以下是一些常见的 mapping 配置:
字段类型:指定字段的数据类型,如字符串、整数、日期等。例如:
"title": { "type": "text" }
。字段分析器:指定字段的分析器,用于处理全文搜索的文本分割和分析。例如:
"description": { "type": "text", "analyzer": "english" }
。嵌套对象:定义嵌套的对象结构,以支持更复杂的数据模型。例如,一个文档可以包含一个作者对象,该对象包含姓名和电子邮件等属性。
多字段:可以为同一个字段定义多个子字段,以支持不同的查询和分析需求。例如,一个字段可以同时支持全文搜索和精确匹配。
Mapping 的配置对于 Elasticsearch 的查询性能和搜索结果准确性至关重要。一个合适的 mapping 设置可以提高查询效率,减少误匹配和无关结果。
# 示例
以下是一个使用 REST API 创建 Elasticsearch 索引的示例,其中包括了 setting 和 mapping 的设置。在本例中,我们创建一个名为 "blog" 的索引。
PUT /blog
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1,
"analysis": {
"analyzer": {
"my_custom_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": [
"lowercase",
"asciifolding"
]
}
}
}
},
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "my_custom_analyzer"
},
"content": {
"type": "text",
"analyzer": "my_custom_analyzer"
},
"publish_date": {
"type": "date"
},
"tags": {
"type": "keyword"
},
"author": {
"properties": {
"name": {
"type": "text"
},
"email": {
"type": "keyword"
}
}
}
}
}
}
在这个例子中:
在
settings
部分,我们设置了索引的分片数(number_of_shards
)为 1,副本数(number_of_replicas
)为 1。我们还自定义了一个名为 "my_custom_analyzer" 的分析器,它使用标准分词器(standard
)和两个过滤器:小写过滤器(lowercase
)和 ASCII 折叠过滤器(asciifolding
)。在
mappings
部分,我们定义了文档的字段结构和类型。包括:title
(文本类型,使用自定义分析器)、content
(文本类型,使用自定义分析器)、publish_date
(日期类型)、tags
(关键词类型)以及一个嵌套的author
对象(包含name
和email
字段)。
通过这个示例,你可以看到如何使用 REST API 来设置 Elasticsearch 索引的 setting 和 mapping。
示例中setting包含了分片和副本数,上面章节已经有基本介绍。还用到mapping配置和分析器,后面内容会讲到。
# 文档的CURD
- 创建(Create):向 "blog" 索引中添加一篇博客文章:
POST /blog/_doc/
{
"title": "Elasticsearch: Getting Started",
"content": "This is a beginner's guide to Elasticsearch.",
"publish_date": "2023-04-25T12:00:00Z",
"tags": ["Elasticsearch", "Guide"],
"author": {
"name": "John Doe",
"email": "john.doe@example.com"
}
}
- 读取(Read):获取刚刚创建的博客文章。假设文章的
_id
为1
:
GET /blog/_doc/1
- 更新(Update):更新博客文章的部分内容。在这个例子中,我们为文章添加一个新的标签:
POST /blog/_doc/1/_update
{
"doc": {
"tags": ["Elasticsearch", "Guide", "Tutorial"]
}
}
另外,你还可以使用脚本来更新文档的内容:
POST /blog/_doc/1/_update
{
"script": {
"source": "ctx._source.tags.add(params.new_tag)",
"params": {
"new_tag": "Search"
}
}
}
- 删除(Delete):删除博客文章。假设文章的
_id
为1
:
DELETE /blog/_doc/1
- 批量操作:使用bulk api
POST _bulk
{ "index" : { "_index" : "test", "_id" : "1" } }
{ "field1" : "value1" }
{ "delete" : { "_index" : "test", "_id" : "2" } }
{ "create" : { "_index" : "test", "_id" : "3" } }
{ "field1" : "value3" }
{ "update" : {"_id" : "1", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }
先是添加了一个文档,然后删除,然后添加,然后更新。
# 更新setting
在 Elasticsearch 中,一旦创建了索引,某些 settings 和 mapping 的属性无法再进行修改。
可以动态修改的setting设置通常与分片、副本或搜索相关。要更新 settings,请使用 _settings API。以下是一个示例:
PUT /my_index/_settings
{
"index": {
"number_of_replicas": 2
}
}
关于动态和静态属性,参考Index Setting (opens new window)
# 动态映射(Dynamic Mapping)和常见字段类型
Elasticsearch 提供了一种名为 Dynamic Mapping 的功能,它可以在索引文档时自动识别字段类型并创建相应的映射。这对于不确定文档结构或者想要快速尝试的场景非常有用。
对于生产环境来说,建议使用显式映射来控制字段类型和属性,以获得更精确的控制和更好的性能。
# Dynamic Mapping配置
Dynamic Mapping 的行为可以通过以下三种方式进行配置:
true
(默认):允许自动创建映射。false
:禁止自动创建映射,需要手动定义映射。strict
:若遇到未映射的字段,会抛出异常。
要配置 Dynamic Mapping,您可以在创建索引时设置 index.mapping.dynamic
参数,如下所示:
{
"settings": {
"index.mapping.dynamic": "strict"
}
}
# 常见字段类型
Elasticsearch 支持多种字段类型,以满足不同类型数据的索引和查询需求。以下是一些常见的字段类型及其简要介绍:
- text:用于全文搜索的字符串类型。text 字段会被分析,支持全文搜索和相关性评分。
- keyword:主要用于非全文搜索,例如过滤、聚合和排序等操作。它将整个字符串作为一个单词进行索引,而不进行分词。因此,它对大小写敏感,并且只能进行精确匹配。
- constant_keyword:一个常量固定值字符串,不会占用额外的磁盘空间和内存,因为它的值在所有文档中都是相同的。constant_keyword 类型的字段可以用于过滤、聚合和排序等操作。设置字段类型为它的时候,必须设置一个value。插入文档是,此字段可以不赋值;赋值的话,值必须为mapping中设置的值,且不可变更
- wildcard:主要用于优化带通配符的查询,如 *(匹配任意字符序列)和 ?(匹配任意单个字符)。它将字符串数据进行 n-gram 分词处理,并将分词结果存储在倒排索引中。与 keyword 类型相比,wildcard 类型的字段在执行带通配符的查询时性能更高,但需要占用更多的磁盘空间。 示例:
PUT /my_files
{
"mappings": {
"properties": {
"filename": {
"type": "wildcard"
}
}
}
}
POST /my_files/_doc/1
{
"filename": "document-001.txt"
}
POST /my_files/_doc/2
{
"filename": "document-002.docx"
}
POST /my_files/_doc/3
{
"filename": "image-001.jpg"
}
搜索:
GET /my_files/_search
{
"query": {
"wildcard": {
"filename": {
"value": "document*.txt"
}
}
}
}
结果会返回_doc/1
文档
- double、long:浮点型数字和整数数字
- date:日期类型,支持多种日期格式。可以通过
format
参数自定义日期格式,format
默认是strict_date_optional_time||epoch_second
,epoch_second
就是时间戳;strict_date_optional_time
表示严格的日期可选时间格式,符合此格式的示例:- 日期:
2023-04-26
- 日期和时间:
2023-04-26T12:34:56Z
- 日期、时间和时区:
2023-04-26T12:34:56+01:00
- 日期:
- date_nanos:用法和date一致,不同的是它存储的纳秒
- binary:存储二进制数据的字段类型。这种类型主要用于存储非文本数据,如图片、音频、视频等。Elasticsearch 本身对于二进制数据的处理能力相对较弱,所以一般建议将这类数据存储在其他更适合存储和处理二进制数据的系统中,如文件存储服务、对象存储服务等。在 Elasticsearch 中使用 binary 类型时,需要将二进制数据转换为 Base64 编码格式,然后将编码后的字符串存储到 binary 类型的字段中。这是因为 Elasticsearch 的底层存储是基于 JSON 文档的,而 JSON 文档只支持文本数据。
- boolean:布尔类型,表示真或假的值。
- alias:为现有字段提供别名
- ip:IP 地址类型,支持 IPv4 和 IPv6。
- geo_point:地理坐标类型,用于表示地理位置。支持多种坐标表示方式,如经纬度数组、字符串和对象。
- long_range、double_range、date_range、ip_range:围(range)相关的数据类型。这些类型可以让你存储具有数值范围、日期范围、地理位置范围等的数据,并支持基于范围的查询。 查询示例:
GET /my_index/_search
{
"query": {
"range": {
"price": {
"gte": 50,
"lte": 100
}
}
}
}
object:对象类型,用于表示嵌套的 JSON 对象。object 字段允许存储多个键值对,并支持多层嵌套。 和object类型很像的是nested类型,请注意他们的区别:
- 索引方式:
object
:当将object
类型的字段添加到文档中时,Elasticsearch 会将其内部的子字段展平(flattened),然后添加到父文档的索引中。这意味着子字段将与父文档一起被存储和索引。nested
:nested
类型字段在索引时会将内部的子对象作为单独的、独立的文档进行存储和索引。这意味着子对象会被独立于父文档进行存储和索引,同时保持与父文档的关联关系。
- 查询方式:
object
:当查询object
类型的字段时,你可以直接使用点号(.)表示法来查询子字段。然而,在查询时,由于字段展平的特性,无法区分子对象之间的关系,因此可能会产生错误的匹配结果。nested
:查询nested
类型的字段时,需要使用特殊的nested
查询,它可以正确地处理子对象之间的关系。使用nested
查询时,可以确保只有满足查询条件的子对象被视为匹配。
总结一下,
nested
和object
类型字段在表示复杂数据结构时有以下区别:object
类型字段在索引时将子字段展平,并将其添加到父文档的索引中。查询时可能会产生错误的匹配结果,因为无法区分子对象之间的关系。nested
类型字段在索引时将子对象作为独立的文档进行存储和索引。查询时需要使用特殊的nested
查询,以便正确地处理子对象之间的关系。
如果需要保留子对象之间的关系并执行更精确的查询,建议使用
nested
类型字段。如果子对象之间的关系不重要,可以使用object
类型字段。
上面介绍了常用的基础类型,Elasticsearch 还提供了version、murmur3、histogram、dense_vector、rank_feature 等类型,请参见:Field data types (opens new window)
# 对于null值的处理
在 Elasticsearch 中,null
值的处理方式取决于字段类型。下面是关于 null
值处理的一些概述:
对于数值类型(如
integer
、long
、float
和double
)和布尔类型(boolean
),Elasticsearch 会忽略包含null
值的字段。这意味着在索引文档时,该字段不会被存储,且不会占用任何磁盘空间。对于字符串类型(如
text
和keyword
)以及其他复杂类型(如date
、ip
、geo_point
和nested
),同样会忽略null
值。该字段不会被存储在索引中,也不会对查询和聚合操作产生影响。如果需要在 Elasticsearch 中表示
null
值,可以使用一个特殊的值来代替,例如使用一个特殊的字符串或者数值。这种方法在进行查询和聚合时会更加灵活。例如,可以使用一个不会出现在实际数据中的字符串(如__NULL__
)来表示null
值。
例如,在创建索引时,可以为某个字段设置一个 null_value
,以便在接收到 null
值时使用指定的默认值:
{
"properties": {
"age": {
"type": "integer",
"null_value": -1
},
"status": {
"type": "keyword",
"null_value": "__NULL__"
}
}
}
在这个例子中,当 age
字段接收到 null
值时,会将其替换为 -1
;当 status
字段接收到 null
值时,会将其替换为 __NULL__
。这样,在进行查询和聚合时,可以使用这些特殊值来表示 null
值。
# 运行时字段
运行时字段(Runtime fields)是 Elasticsearch 中的一个功能,允许在查询时动态计算字段值,而不是在索引时将其存储在文档中。运行时字段的计算基于现有字段的数据,使用 Painless 脚本语言进行计算。这种方式提供了更大的灵活性和更少的存储开销,因为这些字段的值不会占用磁盘空间。
运行时字段的主要用途包括:
- 动态计算字段值:例如,根据现有的数值字段计算总和、平均值、百分比等。
- 根据现有字段创建新的派生字段:例如,从日期字段提取年份、月份、星期等。
- 临时实验和调整字段:在不重新索引数据的情况下,尝试新的计算方法或字段映射。
要定义运行时字段,可以将其添加到索引模板、索引映射或查询请求中。以下是一个运行时字段的示例,该字段根据两个现有的数值字段计算价格与税费之和:
PUT /my-index
{
"mappings": {
"runtime": {
"total_price": {
"type": "double",
"script": {
"source": "doc['price'].value + doc['tax'].value"
}
}
}
}
}
在这个示例中,我们创建了一个名为 total_price
的运行时字段,它将在查询时动态计算价格与税费之和。要查询这个字段,可以像查询普通字段一样使用它:
GET /my-index/_search
{
"query": {
"range": {
"total_price": {
"gte": 100
}
}
}
}
这个查询将返回 total_price
字段值大于等于 100 的文档。请注意,运行时字段的计算可能会增加查询延迟,因为它在查询时进行计算。因此,对于查询性能要求较高的场景,需要权衡存储开销与查询性能的需求。
# 元数据字段
在 Elasticsearch 中,metadata fields(元数据字段)是一组预定义的、特殊用途的字段,用于存储文档的元数据。元数据是关于文档本身的信息,而不是文档内容的信息。这些字段在 Elasticsearch 内部用于管理、检索和排序文档。
以下是一些常见的元数据字段:
_index
:存储文档所属的索引名称。_id
:存储文档的唯一标识符。在索引文档时,可以手动指定或让 Elasticsearch 自动生成。_type
:存储文档的类型。在 Elasticsearch 7.x 版本中,每个索引只能有一个类型,名为_doc
。在更早的版本中,一个索引可以有多个类型,但这种功能已被弃用。_source
:存储原始文档的 JSON 源数据。可以在搜索结果中返回或者在更新文档时使用。_score
:存储文档的相关性评分。在执行全文搜索时,Elasticsearch 会根据文档与查询的匹配程度计算一个评分。默认情况下,搜索结果按评分降序排列。_routing
:存储文档的路由值。在分片数据时,可以使用自定义路由值控制文档存储在哪个分片上。
这些元数据字段在 Elasticsearch 内部自动管理,用户不需要手动创建或维护。在查询时,可以通过这些字段对文档进行筛选、排序和聚合。例如,根据 _index
字段筛选特定索引的文档,或根据 _score
字段对搜索结果进行排序。
# 更新mapping
对于生产环境,一般要显式设置mapping。常见的做法是,往临时索引上写入少量数据,得到临时索引mapping,修改一下得到正式索引的mapping。在生成环境中,创建索引的时候使用此mapping就可以了。
某些 mapping 属性可以在创建索引后进行更新。这些属性通常与字段类型、索引选项或分析器相关。要更新 mapping,请使用 _mapping API。以下是一个示例:
PUT /my_index/_mapping
{
"properties": {
"new_field": {
"type": "text"
},
"existing_field": {
"type": "keyword",
"ignore_above": 256
}
}
}
在这个示例中,我们为索引 my_index 添加了一个名为 new_field 的新字段,并更新了现有字段 existing_field 的 ignore_above 属性。
请注意,只有部分 mapping 属性可以被更新。例如,您无法更改现有字段的类型,也无法删除现有字段。请参考:Update mapping API (opens new window)
# 映射参数
在 Elasticsearch 中,映射参数(mapping parameters)用于定义字段的属性和行为。以下是一些主要的映射参数:
type
:指定字段的数据类型(如text
、keyword
、integer
、date
等)。index
:指定字段是否可以被搜索。值可以是true
(默认)或false
。如果设置为false
,该字段将不会被索引,无法用于搜索。store
:指定字段值是否单独存储。默认情况下,字段值会存储在_source
字段中,但可以通过设置store
为true
将其单独存储。这可以在某些情况下提高查询性能,但会增加存储开销。doc_values
:指定是否为字段生成 doc_values。doc_values 用于排序、聚合和脚本操作。默认情况下,大多数字段类型会自动生成 doc_values,但可以通过设置doc_values
为false
禁用它们。analyzer
:为text
类型的字段指定分词器。分词器用于将文本分解为单词或词条,以便进行索引和搜索。可以使用内置的分词器(如standard
、simple
、whitespace
等)或自定义分词器。search_analyzer
:为text
类型的字段指定搜索时使用的分词器。如果未指定,则默认使用analyzer
参数值。normalizer
:为keyword
类型的字段指定规范器。规范器用于在索引和搜索时对字段值进行规范化(如转换为小写、去除重音符号等)。format
:为date
类型的字段指定日期格式。可以使用内置的格式(如strict_date_optional_time
、epoch_millis
等)或自定义格式。coerce
:指定是否强制将不符合数据类型的值转换为有效值。例如,将浮点数值强制转换为整数。默认为true
。null_value
:指定当字段值为null
时使用的替代值。这可以在排序和聚合操作中避免空值造成的问题。ignore_malformed
:指定是否忽略格式错误的值。默认为false
,如果设置为true
,则格式错误的值将被忽略,而不会导致索引操作失败。copy_to
:指定将字段值复制到其他字段。这可以用于在多个字段上执行搜索或聚合操作。
这些映射参数可根据字段类型和用途进行配置,以满足不同的搜索和存储需求。在定义索引映射时,可以为每个字段指定适当的映射参数。
# 重建索引
如果您需要对 settings 或 mapping 进行大规模更改,可以考虑使用 _reindex
API 重建索引。这个 API 可以将现有索引中的数据复制到一个新的索引,并在新索引上应用新的 settings 和 mapping。在完成重建后,可以将别名切换到新索引,以实现无缝迁移。以下是一个示例:
- 创建一个新的索引,应用新的 settings 和 mapping:
PUT /new_index
{
"settings": {
// 新的 settings
},
"mappings": {
// 新的 mapping
}
}
- 使用
_reindex
API 将数据从旧索引复制到新索引:
POST /_reindex
{
"source": {
"index": "old_index"
},
"dest": {
"index": "new_index"
}
}
- 在完成数据迁移后,将别名切换到新索引:
POST /_aliases
{
"actions": [
{
"remove": {
"index": "old_index",
"alias": "my_alias"
}
},
{
"add": {
"index": "new_index",
"alias": "my_alias"
}
}
]
}
在这个示例中,我们先从旧索引 old_index
移除别名 my_alias
,然后将别名添加到新索引 new_index
。这样,应用程序可以继续使用别名 my_alias
访问新索引,而无需对其进行修改。
请注意,重建索引可能需要一定的时间,具体取决于数据量和硬件性能。在重建索引期间,您需要确保旧索引和新索引都可用,以避免对业务造成影响。
# Analyzer
在 Elasticsearch 中,Analyzer(分析器)是用于处理全文搜索中的文本分析过程的组件。文本分析是将文本数据转换成可以被倒排索引存储和查询的词项的过程。
分析器通常由三个部分组成:
- 字符过滤器(Char Filter):字符过滤器对原始文本进行预处理,例如删除 HTML 标签、转换特殊字符等。
- 分词过滤器(Token Filter):分词过滤器对从分词器产生的词项进行处理和过滤。常见的分词过滤器包括小写过滤器(将词项转换为小写)、停用词过滤器(去除常见的停用词,如 "and"、"the" 等)和词干提取过滤器(将词项转换为其词干,以便在搜索时匹配不同形式的单词)。
- 分词器(Tokenizer):分词器将文本拆分成独立的词项。例如,标准分词器会将文本按空格和标点符号拆分成单词。
# 字符过滤器 使用
下面给出一些常见的Character Filters的例子,以便更好地理解它们的作用:
- HTML Strip Char Filter
该过滤器可以去除HTML标签,将文本转换为纯文本。
POST _analyze
{
"tokenizer": "standard",
"char_filter": [ {
"type": "html_strip"
}],
"text": "<p>Hello, <b>world</b>!</p>"
}
输出结果为:
{
"tokens" : [
{
"token" : "Hello",
"start_offset" : 3,
"end_offset" : 8,
"type" : "<ALPHANUM>",
"position" : 0
},
{
"token" : "world",
"start_offset" : 13,
"end_offset" : 22,
"type" : "<ALPHANUM>",
"position" : 1
}
]
}
- Mapping Char Filter
该过滤器可以将指定字符映射为其他字符。例如,将所有的"&"替换为"and"。
POST _analyze
{
"char_filter": [
{
"type": "mapping",
"mappings": [
"& => and",
"| => or"
]
}
],
"text": "apples & oranges | bananas"
}
输出结果为:
{
"tokens" : [
{
"token" : "apples and oranges or bananas",
"start_offset" : 0,
"end_offset" : 26,
"type" : "word",
"position" : 0
}
]
}
# 分词过滤器 使用
下面给出一些常见的Token Filters的例子,以便更好地理解它们的作用:
- Lowercase Token Filter
小写分词器会将分词结果转换为小写字母。
POST _analyze
{
"tokenizer": "standard",
"filter": ["lowercase"],
"text": "Hello, world! "
}
输出结果为:
{
"tokens" : [
{
"token" : "hello",
"start_offset" : 0,
"end_offset" : 5,
"type" : "<ALPHANUM>",
"position" : 0
},
{
"token" : "world",
"start_offset" : 7,
"end_offset" : 12,
"type" : "<ALPHANUM>",
"position" : 1
}
]
}
- Stop Token Filter 停用词分词器会去除文本中的停用词,如“and”、“the”等,然后再进行分词和转换操作。停用词分词器可以通过以下方式进行配置:
POST _analyze
{
"tokenizer": "standard",
"filter": ["stop"],
"text": "Elasticsearch is awesome and great."
}
输出结果为:
{
"tokens" : [
{
"token" : "Elasticsearch",
"start_offset" : 0,
"end_offset" : 13,
"type" : "<ALPHANUM>",
"position" : 0
},
{
"token" : "awesome",
"start_offset" : 17,
"end_offset" : 24,
"type" : "<ALPHANUM>",
"position" : 2
},
{
"token" : "great",
"start_offset" : 29,
"end_offset" : 34,
"type" : "<ALPHANUM>",
"position" : 4
}
]
}
# 分词器 使用
下面给出一些常见的Tokenizer的例子,以便更好地理解它们的作用:
- Standard Tokenizer
标准分词器是Elasticsearch中最常用的分词器之一,它会将文本按照标点符号、空格等进行分词,同时会将文本转换为小写字母。标准分词器可以通过以下方式进行配置:
POST _analyze
{
"tokenizer": "standard",
"text": "Hello, world!"
}
输出结果为:
{
"tokens" : [
{
"token" : "Hello",
"start_offset" : 0,
"end_offset" : 5,
"type" : "<ALPHANUM>",
"position" : 0
},
{
"token" : "world",
"start_offset" : 7,
"end_offset" : 12,
"type" : "<ALPHANUM>",
"position" : 1
}
]
}
- Keyword Tokenizer
关键字分词器会将整个文本作为一个关键字进行索引,不会进行任何分词和处理操作。
POST _analyze
{
"tokenizer": "keyword",
"text": "Hello, world! Elasticsearch is awesome."
}
输出结果为:
{
"tokens" : [
{
"token" : "Hello, world! Elasticsearch is awesome.",
"start_offset" : 0,
"end_offset" : 39,
"type" : "word",
"position" : 0
}
]
}
# 自定义分析器
理解了分析器的组成部分,就可以未索引创建自定义分析器,并把自定义分析器绑定到某个字段上使用了。示例参考 基础概念-示例
章节
# 内置分析器
Elasticsearch 提供了许多内置的分析器:
Standard Analyzer
标准分析器是Elasticsearch中最常用的分析器之一,它会将文本按照标点符号、空格等进行分词,同时会将文本转换为小写字母。Standard Analyzer适用于一般性的全文搜索场景,可以快速地进行分词和检索。Simple Analyzer
简单分析器是一种比标准分析器更加简单的分析器,它只会将文本按照空格进行分词,并且不会进行任何转换操作。Simple Analyzer适用于不需要考虑大小写和单复数等问题的场景,比如数字、代码等。Whitespace Analyzer
空格分析器会将文本按照空格进行分词,不会进行任何转换操作。Whitespace Analyzer适用于不需要进行复杂分词和处理的场景,比如标签、ID等。Keyword Analyzer
关键字分析器会将整个文本作为一个关键字进行索引,不会进行任何分词和处理操作。Keyword Analyzer适用于需要精确匹配的场景,比如产品编号、邮编等。Stop Analyzer
停用词分析器会去除文本中的停用词,如“and”、“the”等,然后再进行分词和转换操作。Stop Analyzer适用于需要排除常用词语的场景,比如文章标题、关键字等。Pattern Analyzer
模式分析器会按照正则表达式对文本进行分词和转换操作,可以根据需要进行自定义。Pattern Analyzer适用于需要自定义分词规则的场景,比如特殊字符、中文词语等。Language Analyzer
语言分析器会根据文本所属的语言进行分词和转换操作,可以提高分析的准确性和效率。Elasticsearch中支持多种语言的分析器,如中文、英文、法文等。
内置分析器可以直接使用,比如mapping
中给字段指定分析器:
"mappings": {
"properties": {
"text": {
"type": "text",
"analyzer": "simple"
}
}
}
# 中文分词
目前流行的库是IK Analysis (opens new window)
官网有详细用法,这里就不赘述了。
# 拼音分词
拼音分词对于繁简体搜索很有用,使用方法请参考:Pinyin Analysis (opens new window)
# 数据流
数据流(Data Streams)是一种新的数据存储方式,它把数据按照时间顺序分为多个只读的时间序列索引(time series indices)。数据流适用于追加写入和查询的场景,例如日志、指标、事件等时间序列数据。
数据流的目标是简化时间序列数据的管理,使其更易于扩展和维护。数据流提供了以下优点:
自动管理索引生命周期:通过与索引生命周期管理(ILM)策略结合,数据流可以自动地创建、删除和优化索引,以满足数据的存储和性能需求。
自动生成索引模板:数据流在创建时会自动为索引生成一个模板,该模板包含映射和设置。这样,新创建的索引将自动继承这些配置。
易于查询:数据流提供了一个统一的查询入口,可以直接针对数据流执行搜索和聚合操作,而无需关心具体的索引。
创建:
PUT /_data_stream/my-data-stream
{
"timestamp_field": {
"name": "@timestamp"
},
"template": {
"settings": {
"index.lifecycle.name": "my-ilm-policy"
},
"mappings": {
"properties": {
"@timestamp": {
"type": "date"
},
"message": {
"type": "text"
}
}
}
}
}
追加数据:
POST /my-data-stream/_doc/
{
"@timestamp": "2023-05-01T12:34:56Z",
"message": "This is a log message."
}
查询和普通索引一样。
# 索引生命周期管理
在 Elasticsearch 中,Index Lifecycle Management(ILM,索引生命周期管理)是一种对索引进行管理和优化的机制。ILM 可以帮助你自动处理索引的创建、优化、删除等操作,以满足性能和资源使用的要求。ILM 主要应用于时间序列数据,如日志、指标和事件等,但也可用于其他类型的数据。
ILM 使用策略(policy)来定义一个索引的生命周期。策略中包含一系列阶段(phases),每个阶段都有相应的操作和配置。Elasticsearch 提供了以下四个阶段:
- Hot:数据刚写入时所处的阶段。在这个阶段,数据通常被频繁查询和更新,因此需要保持在高性能的硬件上。
- Warm:数据不再被频繁更新,但仍然会被查询的阶段。在这个阶段,可以对数据进行优化,如降低副本数量、压缩存储空间等,以节省资源。
- Cold:数据很少被查询,但仍需要保留的阶段。在这个阶段,可以将数据迁移到低性能、低成本的硬件上,以降低成本。
- Delete:数据不再需要保留的阶段。在这个阶段,会自动删除索引及其数据。
要使用 ILM,首先需要创建一个 ILM 策略。以下是一个创建 ILM 策略的示例:
PUT /_ilm/policy/my_ilm_policy
{
"policy": {
"phases": {
"hot": {
"min_age": "0ms",
"actions": {
"rollover": {
"max_size": "50gb",
"max_age": "30d"
},
"set_priority": {
"priority": 100
}
}
},
"delete": {
"min_age": "90d",
"actions": {
"delete": {}
}
}
}
}
}
这个示例中,我们创建了一个名为 my_ilm_policy
的策略。策略包含两个阶段:hot
和 delete
。在 hot
阶段,当索引大小达到 50GB 或者存在时间超过 30 天时,会触发 rollover
操作。在 delete
阶段,当索引存在时间超过 90 天时,会触发删除操作。
创建策略后,需要将其应用到索引或数据流上。这可以通过在索引模板或数据流的设置中指定 ILM 策略名称来实现。以下是一个将 ILM 策略应用到索引模板的示例:
PUT /_index_template/my_index_template
{
"index_patterns": ["my-index-*"],
"template": {
"settings": {
"index.lifecycle.name": "my_ilm_policy",
"index.lifecycle.rollover_alias": "my-index"
},
"mappings": {
"_source": {
"enabled": true
},
"properties": {
"timestamp": {
"type": "date"
},
"message": {
"type": "text"
}
}
}
}
}
在这个示例中,我们创建了一个名为 my_index_template
的索引模板,将 ILM 策略 my_ilm_policy
应用到了索引模板中。所有与模板匹配的新索引(例如 my-index-000001
,my-index-000002
等)将自动应用此策略。
# 滚动
在 Elasticsearch 中,rollover
操作是指当一个索引达到一定的条件(如大小、文档数量或年龄),系统会自动创建一个新的索引来接收新写入的数据,同时将原索引设置为只读。这个过程可以帮助更好地管理和优化数据存储,尤其是对于时间序列数据(例如日志和指标)。
rollover
操作通常与索引别名(index alias)一起使用。索引别名是一个指向一个或多个索引的虚拟名称。在执行 rollover
操作时,系统会自动将别名指向新创建的索引,以确保查询和写入操作始终指向正确的索引。这样,应用程序无需知道具体的索引名称,只需与别名交互即可。
以下是一个执行 rollover
操作的示例:
POST /my-index-alias/_rollover
{
"conditions": {
"max_age": "30d",
"max_docs": 1000000,
"max_size": "50gb"
}
}
在这个示例中,我们对别名 my-index-alias
执行了 rollover
操作。当满足以下任一条件时,系统将创建一个新的索引并更新别名:
- 索引的最大年龄达到 30 天;
- 索引包含的文档数量达到 1,000,000;
- 索引的大小达到 50GB。
创建新索引的逻辑是:新索引的名称通常是基于原索引名称的自增序列。
还会把原索引设置为只读,所以要注意使用场景,比如归档数据比较适合滚动。
通过使用 rollover
操作,你可以确保数据分布在多个较小的索引中,这有助于提高查询性能和管理效率。同时,与 Index Lifecycle Management(ILM)策略结合使用,可以进一步自动化和优化索引管理过程。
# 索引别名
索引别名(index alias)是一个指向一个或多个索引的虚拟名称。别名可以用来隐藏实际的索引名称,使得应用程序可以使用统一的名称与索引进行读写操作,而无需关注背后的具体索引。
请记住以下规则:
- 别名和索引名称必须是唯一的,不能出现同名的情况。在选择别名名称时,建议采用一种描述性和易于理解的命名规范,以便于后续的管理和维护
- 一个别名可以对应多个索引,设置的时候可以使用通配符*
- 尽量避免直接使用索引名称进行读写操作,而应该使用别名。这样可以更好地抽象底层索引细节,并简化应用程序的开发和维护
# 别名的增删改查
- 创建别名:
POST /_aliases
{
"actions": [
{
"add": {
"index": "index_name",
"alias": "alias_name"
}
}
]
}
- 删除别名:
POST /_aliases
{
"actions": [
{
"remove": {
"index": "index_name",
"alias": "alias_name"
}
}
]
}
- 更新别名:
POST /_aliases
{
"actions": [
{
"remove": {
"index": "index_name",
"alias": "alias_name"
}
},
{
"add": {
"index": "new_index_name",
"alias": "alias_name"
}
}
]
}
- 查询别名:
GET /_cat/aliases/alias_name?v
这将返回与别名相匹配的索引的详细信息。
# 搜索API
Elasticsearch最强大的是其搜索能力,在本章节中,我们将详细介绍 Elasticsearch 的搜索 API。我们将一步步探讨各种不同类型的查询,并深入了解 Elasticsearch 的强大搜索能力。
# 分页
Elasticsearch 提供了基于 from
和 size
参数的分页功能。from
参数表示从哪个索引位置开始返回结果,而 size
参数表示返回结果的数量。例如,如果您需要从第 11 个文档开始获取 10 个文档,可以使用如下查询:
GET /my-index/_search
{
"from": 10,
"size": 10,
"query": {
"match_all": {}
}
}
Elasticsearch 默认最大查询文档数为10000,可以通过修改 indices.query.bool.max_clause_count
和 search.max_buckets
参数的值。这两个参数默认都为10000。修改这些参数需要重启 Elasticsearch 集群。注意,增加这些参数的值可能会导致 Elasticsearch 性能下降,因此应该谨慎使用。
# Search After
对于超过10000元素的查询,早期使用Scroll的方式。7.11之后推荐使用search_after
。
具体来说,search_after 参数可以接收一个数组作为参数,其中包含了最后一个文档的 sort 值,然后 Elasticsearch 将从此文档之后开始继续搜索,直到达到搜索结果的数量限制。
下面是一个简单的示例,演示如何使用 search_after
参数在 Elasticsearch 中进行分页搜索:
GET /my_index/_search
{
"size": 10,
"sort": [
{"timestamp": {"order": "desc"}},
{"_id": {"order": "asc"}}
],
"query": {
"match_all": {}
}
}
在上述示例中,我们使用 sort
参数按照 timestamp
字段降序排序,然后按照 _id
字段升序排序。我们还设置 size
参数为 10,表示每页返回的结果数量为 10。
接下来,我们可以使用 search_after
参数来执行下一页搜索。为此,我们需要提供上一页最后一条文档的 sort 值。假设我们已经执行了一次上述查询,并且找到了最后一个文档的 _id
值为 xyz
,timestamp
值为 1619512900000
。我们可以使用以下查询来获取下一页的结果:
GET /my_index/_search
{
"size": 10,
"sort": [
{"timestamp": {"order": "desc"}},
{"_id": {"order": "asc"}}
],
"query": {
"match_all": {}
},
"search_after": [1619512900000, "xyz"]
}
在上述查询中,我们使用 search_after
参数并传递了上一页最后一条文档的 sort 值。Elasticsearch 将使用这个 sort 值作为起点,继续执行查询,并返回下一页的结果。需要注意的是,search_after
参数的值必须是与 sort 字段的类型和顺序相同的数组。
聪明的同学可能发现了一个问题:搜索过程中,数据变动了,比如插入了多条数据,导致下一页数据出现和上一页数据重复的情况怎么办? 官方给出的解决方式是:point in time (PIT) (opens new window)
简单来说就是创建一个时间点,在搜索中保留当前索引状态。
# 排序
Elasticsearch 允许您根据一个或多个字段对查询结果进行排序。要实现排序,只需在查询中添加一个 sort
参数。例如,以下查询将根据 age
字段进行升序排序:
GET /my-index/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"age": "asc"
}
]
}
# 指定查询字段
在某些情况下,您可能只对文档的某些字段感兴趣。Elasticsearch 允许您使用 _source
参数来指定返回的字段。例如,以下查询将只返回 title
和 author
字段:
GET /my-index/_search
{
"_source": ["title", "author"],
"query": {
"match_all": {}
}
}
# 按照自定义格式返回字段
fields用于实现指定格式返回字段,它从文档中提取字段值,支持通配符和指定日期格式等高级功能。
POST my-index-000001/_search
{
"query": {
"match": {
"user.id": "kimchy"
}
},
"fields": [
"user.id",
"http.response.*",
{
"field": "@timestamp",
"format": "epoch_millis"
}
],
"_source": false
}
在给出的示例中,fields
参数包含以下内容:
"user.id"
:返回文档中user.id
字段的值。"http.response.*"
:使用通配符*
来匹配所有以http.response.
开头的字段,并返回这些字段的值。{"field": "@timestamp", "format": "epoch_millis"}
:返回文档中@timestamp
字段的值,但将其格式化为 epoch 毫秒格式。
同时,示例中设置了 _source
参数为 false
,表示不返回原始文档的 _source
字段。这样,查询结果将仅包含通过 fields
参数提取和格式化后的字段值。
例如,假设文档的原始内容如下:
{
"user": {
"id": "kimchy"
},
"http": {
"response": {
"status_code": 200,
"body": "OK"
}
},
"@timestamp": "2023-05-01T12:34:56.789Z"
}
在上面的查询中,返回的查询结果可能如下:
{
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 1,
"hits": [
{
"_index": "my-index-000001",
"_type": "_doc",
"_id": "1",
"_score": 1,
"fields": {
"user.id": ["kimchy"],
"http.response.status_code": [200],
"http.response.body": ["OK"],
"@timestamp": [1651431296789]
}
}
]
}
}
可以看到,查询结果中的 fields
对象包含了根据 fields
参数提取和格式化后的字段值。
# 折叠搜索结果(group by)
Elasticsearch 的折叠搜索结果功能通过 "collapse" 查询参数实现,它允许你基于一个或多个字段折叠搜索结果。折叠搜索结果的主要目的是从搜索结果中移除重复或高度相似的文档,仅返回每个折叠组的顶部文档。这可以使得搜索结果更为紧凑和多样化。
要使用折叠搜索结果,你需要指定一个字段作为折叠依据。该字段必须是一个关键字类型或可以映射为关键字类型的字段。Elasticsearch 将根据该字段的值对搜索结果进行分组,并仅返回每个分组的一个代表性文档。
以下是一个简单的折叠搜索结果示例:
GET /my_index/_search
{
"query": {
"match_all": {}
},
"collapse": {
"field": "user_id" // 折叠依据字段
}
}
在这个示例中,Elasticsearch 将根据 "user_id" 字段的值对搜索结果进行折叠。每个 "user_id" 值的文档将被归为一个组,搜索结果中只包含每组的顶部文档。
此外,你还可以使用 "inner_hits" 参数来检索每个折叠组的其他文档。例如:
GET /my_index/_search
{
"query": {
"match_all": {}
},
"collapse": {
"field": "user_id",
"inner_hits": { // 返回每个折叠组的其他文档
"name": "other_documents",
"size": 2
}
}
}
这个示例将返回每个折叠组的顶部文档以及每组内的其他两个文档。这些其他文档将在 "inner_hits" 结果中以 "other_documents" 的名称返回。
请注意,折叠搜索结果可能会影响性能,因为它需要对搜索结果进行额外的处理。因此,在使用此功能时,请确保仅在必要时对结果进行折叠。
# 运行时字段
在查询时定义运行时字段,这在一些临时实验和调整字段的情况下非常有用,因为不需要修改索引映射或重新索引数据。
要在搜索请求中定义运行时字段,请在请求体中使用 runtime_mappings
属性。以下是一个示例,其中我们在搜索请求中定义了一个运行时字段 total_price
,它根据现有的 price
和 tax
字段计算总价:
GET /my-index/_search
{
"runtime_mappings": {
"total_price": {
"type": "double",
"script": {
"source": "doc['price'].value + doc['tax'].value"
}
}
},
"query": {
"range": {
"total_price": {
"gte": 100
}
}
}
}
在这个示例中,我们首先定义了运行时字段 total_price
,然后在查询中使用了该字段。这样,我们可以在不修改索引映射的情况下尝试不同的计算方法。
请注意,由于运行时字段是在查询时计算的,这可能会增加查询延迟。在性能要求较高的场景中,请谨慎使用运行时字段,并考虑存储开销与查询性能之间的权衡。
# Query查询
# Match查询
Match 查询是 Elasticsearch 中最基本的全文搜索查询。它会将查询字符串与文档中的一个或多个字段进行比较。例如,以下查询将在 title
字段中查找包含 “Elasticsearch” 的文档:
GET /my-index/_search
{
"query": {
"match": {
"title": "Elasticsearch"
}
}
}
# Match Boolean Prefix查询
Match Boolean Prefix查询是一个将每个查询词条和查询中的一部分布尔前缀进行匹配的查询。查询代表了用户输入的最后一个词是一个前缀而不是一个完整的单词的情况。
示例:
GET /_search
{
"query": {
"match_bool_prefix": {
"message": "quick brown f"
}
}
}
在上面的请求中,将查找带有"quick"和"brown"词项的文档,最后一个词将使用布尔前缀查询匹配"f"的词项。这将匹配"fox","fast","fat"等词。
# Match Phrase查询
Match Phrase 查询用于查找包含指定短语的文档。与 Match 查询不同,Match_phrase 查询会考虑查询字符串中的单词顺序。例如,以下查询将查找 title
字段中包含短语 “Elasticsearch tutorial”的文档:
GET /my-index/_search
{
"query": {
"match_phrase": {
"title": "Elasticsearch tutorial"
}
}
}
# Match Phrase Prefix查询
Match Phrase Prefix查询是在Match Phrase查询的基础上添加了Wildcard查询的功能,用于完成词组模糊匹配的功能。
示例:
GET /_search
{
"query": {
"match_phrase_prefix" : {
"message" : "quick brown f"
}
}
}
在上面的请求中,将查找以"quick brown f"为前缀的文档,如"quick brown fox", "quick brown fawn"等。
# Combined Fields查询
Combined Fields查询支持搜索多个文本字段,就像它们的内容已索引到一个组合字段中一样。
示例:
GET /_search
{
"query": {
"combined_fields" : {
"query": "database systems",
"fields": [ "title", "abstract", "body"],
"operator": "and"
}
}
}
指定操作符是AND,意味着只返回同时包含"database"和"systems"的文档。如果将操作符改为OR,将返回同时包含任一关键词的文档。
查询 combined_fields 提供了一种跨多个 text 字段进行匹配和评分的原则方法。为了支持这一点,它要求所有字段具有相同的搜索 analyzer 。
如果需要单个查询来处理不同类型的字段(如关键字或数字),则该 multi_match 查询可能更适合。它支持文本和非文本字段,并接受不共享同一分析器的文本字段。
# Multi-Match查询
Multi-Match查询允许您在多个字段上运行相同的分析查询。
示例:
GET /_search
{
"query": {
"multi_match" : {
"query": "data test",
"type": "best_fields",
"fields": [ "title", "description" ]
}
}
}
在上面的请求中,"data test"将在"title"和"description"字段中搜索,返回包含这个短语的任何一个字段的文档。
Multi-match查询提供了不同的匹配类型(type字段指定),可以根据需求选择适合的匹配类型来进行查询。以下是Multi-match查询的不同匹配类型的介绍:
best_fields:
- 默认的匹配类型,匹配任意一个字段,并使用最佳字段的得分作为整个文档的得分。
- 适合于文档多个字段中只有一个字段非常重要的情况,比如搜索关键字在标题字段中的匹配更重要。
most_fields:
- 匹配任意一个字段,并将每个字段的得分相加作为整个文档的得分。
- 适合于文档多个字段的内容都很重要,希望将所有匹配结果都考虑在计算得分中的情况。
cross_fields:
- 将所有字段使用相同的文本分析器,看作一个大字段进行匹配。
- 适合于多个字段中的每个字段都包含关键字,并且不希望关键字在单个字段中过度影响得分的情况。
phrase:
- 在每个字段上执行match_phrase查询,并使用最佳字段的得分作为整个文档的得分。
- 适合于希望匹配多个单词组成的短语的情况,如搜索短语"database systems"而不是单独的关键词。
phrase_prefix:
- 在每个字段上执行match_phrase_prefix查询,并使用最佳字段的得分作为整个文档的得分。
- 适合于希望匹配多个前缀词组成的短语的情况,如搜索短语"database syste"来匹配"database systems"。
bool_prefix:
- 在每个字段上创建match_bool_prefix查询,并将每个字段的得分相加作为整个文档的得分。
- 适合于希望匹配多个前缀词组成的短语,并将每个字段的匹配结果都考虑在内的情况。
通过选择不同的匹配类型,可以根据具体需求对查询的结果进行精确控制,以获得最满意的搜索结果。
# Term查询
Term 查询用于精确匹配某个字段的值。与 Match 查询不同,Term 查询不会对查询字符串进行分析(分词)。例如,以下查询将查找 status
字段值为 “published”的文档:
GET /my_index/_search
{
"query": {
"term": {
"status": "published"
}
}
}
# Bool查询
Bool 查询允许您将多个查询条件进行组合。它提供了 must
(必须满足)、should
(至少满足一个)、must_not
(不能满足) 和 filter
(过滤)四种子句来控制查询逻辑。
以下是一个 Bool 查询的示例,它要求满足以下条件:价格在 100 到 500 之间,标题中包含 "手机",品牌为 "Apple" 或 "Samsung" 的商品,但不包括颜色为 "红色" 的商品,且库存大于 10。
GET /products/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"title": "手机"
}
}
],
"should": [
{
"term": {
"brand": "Apple"
}
},
{
"term": {
"brand": "Samsung"
}
}
],
"must_not": [
{
"term": {
"color": "红色"
}
}
],
"filter": [
{
"range": {
"price": {
"gte": 100,
"lte": 500
}
}
},
{
"range": {
"stock": {
"gt": 10
}
}
}
],
"minimum_should_match": 1
}
}
}
这其中的minimum_should_match
参数是可选的。如果在 bool 查询中包含 should
子句但未指定 minimum_should_match
,那么 should
子句的行为将取决于查询中是否存在 must
或 filter
子句。
- 如果
must
或filter
子句存在,则至少需要匹配一个should
子句的条件(相当于minimum_should_match
默认为 1)。 - 如果没有
must
或filter
子句,那么should
子句中的查询条件都是可选的,不需要满足任何条件(相当于minimum_should_match
默认为 0)。
# 形状、地理、跨度查询
除了以上介绍的查询,Elasticsearch还支持以下类型的查询:
# 检索内部命中
inner_hits
是 Elasticsearch 查询中的一个功能,它用于返回嵌套对象或父子关系中的匹配子文档。当查询涉及到嵌套对象(nested objects)或父子关系(parent-child relationships)时,inner_hits
可以帮助我们查看和获取具体匹配的子文档信息。
以下是一个简单的示例,展示如何在使用 nested
查询时使用 inner_hits
功能。假设我们有一个包含博客文章信息的索引,每篇文章都包含一个嵌套对象 "comments",用于存储评论信息。
首先,创建一个包含嵌套对象 "comments" 的索引:
PUT /blogs
{
"mappings": {
"properties": {
"title": {
"type": "text"
},
"content": {
"type": "text"
},
"comments": {
"type": "nested",
"properties": {
"author": {
"type": "text"
},
"comment": {
"type": "text"
}
}
}
}
}
}
添加一篇包含评论的博客文章:
POST /blogs/_doc/1
{
"title": "Elasticsearch 入门教程",
"content": "本文介绍 Elasticsearch 的基本概念和使用方法。",
"comments": [
{
"author": "Alice",
"comment": "非常有用的文章,谢谢!"
},
{
"author": "Bob",
"comment": "我也觉得这篇文章很棒!"
}
]
}
接下来,执行一个查询,搜索包含关键词 "非常有用" 的评论,并返回匹配的子文档(评论)信息。
GET /blogs/_search
{
"query": {
"nested": {
"path": "comments",
"query": {
"match": {
"comments.comment": "非常有用"
}
},
"inner_hits": {}
}
}
}
在这个查询中,我们使用了 nested
查询来搜索包含关键词 "非常有用" 的评论。inner_hits
用于返回匹配的子文档信息。在这个例子中,我们使用了一个空对象 {}
作为 inner_hits
的值,表示使用默认设置。你可以提供一个包含 inner_hits
设置的对象来自定义返回的子文档信息。
查询结果将包含匹配评论的文章,以及具体匹配的子文档(评论)信息。
# 评分
Elasticsearch 使用相关性评分(_score)对查询结果进行排序。_score 是一个浮点数,表示文档与查询条件的匹配程度。默认情况下,Elasticsearch 使用 BM25 算法计算评分。
您可以通过在查询中添加 min_score
参数来过滤掉评分低于指定阈值的结果。例如,以下查询将只返回评分大于等于 1.0 的文档:
GET /my_index/_search
{
"min_score": 1.0,
"query": {
"match": {
"title": "Elasticsearch"
}
}
}
# BM25算法
了解BM25之前,要了解TF-IDF(词频-逆文档频率)。
TF-IDF(Term Frequency-Inverse Document Frequency,词频-逆文档频率):这是一种统计方法,用于评估一个词在文档和整个文档集合中的重要性。TF-IDF 由两部分组成:
- TF(Term Frequency,词频):衡量一个词在某个文档中出现的频率。通常,一个词在文档中出现次数越多,其重要性越高。
- IDF(Inverse Document Frequency,逆文档频率):衡量一个词在文档集合中的罕见程度。一个词在文档集合中出现次数越少,其重要性越高。IDF 的计算方式是:对于每个词,取所有文档的数量除以包含该词的文档数量,然后取对数。
TF-IDF 的值是将 TF 与 IDF 相乘得到的。当一个词在一个特定文档中频繁出现,同时在整个文档集合中出现较少时,其 TF-IDF 值较高,意味着这个词对于该文档的相关性较高。
BM25(Best Matching 25):这是一种基于概率的模型,用于改进 TF-IDF 的计算方法。BM25 对 TF-IDF 的计算进行了优化,主要体现在以下方面:
- 对词频的处理:当词频达到一定程度时,BM25 对词频的增长进行了平滑处理,避免因词频过高而导致得分失衡。
- 考虑文档长度:BM25 考虑了文档长度的影响,使得长文档和短文档具有更公平的比较基础。
- 引入了一个文档特定的参数(K1 和 b):这些参数可以进一步调整相关性得分的计算,以适应特定的搜索需求。
BM25 是 TF-IDF 的改进版本,对词频和文档长度等方面的处理更加合理,能够提供更好的搜索结果。
# 自定义评分逻辑
此外,您还可以使用 function_score
查询来自定义评分逻辑。
以下查询将在 title
字段中查找包含 "Elasticsearch" 的文档,并根据 popularity
字段的值对评分进行加权:
GET /my_index/_search
{
"query": {
"function_score": {
"query": {
"match": {
"title": "Elasticsearch"
}
},
"field_value_factor": {
"field": "popularity",
"factor": 1.2,
"modifier": "sqrt"
}
}
}
}
field_value_factor用于调整文档 _score 的函数。它根据指定字段的值来调整 _score。
此外,function_score还有 script_score、weight、random_score等类型,具体参考:Function score query (opens new window)
# 影响评分之boost
Boost 是一种调整文档 _score 的方法。通过在查询时分配 boost 值,你可以影响 _score 的计算方式,
Boost 可以在查询时应用到多个层面:
- 字段级别的 boost:在查询时,可以为某个字段分配一个 boost 值,以提高或降低该字段的相关性。例如,如果在一个博客搜索场景中,我们希望标题字段(title)比正文字段(content)更重要,可以为标题字段分配一个较高的 boost 值。
GET /my_index/_search
{
"query": {
"multi_match": {
"query": "Elasticsearch",
"fields": ["title^3", "content"]
}
}
}
在这个示例中,我们使用 ^3
为 title
字段分配了一个 boost 值,使其相关性得分为 content
字段的 3 倍。
- 查询子句级别的 boost:在复合查询(如 bool 查询)中,可以为各个查询子句分配不同的 boost 值,以调整它们对最终相关性得分的贡献。
GET /my_index/_search
{
"query": {
"bool": {
"should": [
{ "match": { "title": { "query": "Elasticsearch", "boost": 3 } } },
{ "match": { "content": { "query": "Elasticsearch", "boost": 1 } } }
]
}
}
}
在这个示例中,我们为 title
和 content
字段的查询子句分配了不同的 boost 值。由于 title
字段的 boost 值较高,具有匹配标题字段的文档将获得更高的相关性得分。
通过使用 boost,用户可以根据实际需求定制搜索结果的排序,提高搜索结果的质量。
# 评分查询之boosting
boosting
查询是 Elasticsearch 中一种用于调整相关性得分的查询类型。它允许你结合两个查询,并根据它们的相关性得分来降低或提高匹配文档的最终得分。boosting
查询包含以下三个属性:
positive
:一个查询,用于筛选与之匹配的文档。这些文档将作为最终结果集的一部分返回。negative
:另一个查询,用于筛选与之匹配的文档。这些文档将不会被排除在结果集之外,但它们的相关性得分会受到negative_boost
属性的影响而降低。negative_boost
:一个小于 1 的浮点数,用于降低与negative
查询匹配的文档的相关性得分。negative_boost
的值越小,匹配的文档得分降低得越多。
boosting
查询的主要目的是降低某些条件下的文档在搜索结果中的权重,而不是完全排除它们。这对于某些场景,如在搜索结果中降低过时或不那么相关的信息,是非常有用的。
假设我们有一个博客网站,其中包含文章的标题、内容和发布日期。现在我们希望搜索包含关键词 "Elasticsearch" 的文章,并在搜索结果中降低一年前发布的文章的权重。以下是使用 boosting
查询的示例:
GET /blog/_search
{
"query": {
"boosting": {
"positive": {
"multi_match": {
"query": "Elasticsearch",
"fields": ["title", "content"]
}
},
"negative": {
"range": {
"publish_date": {
"lt": "now-1y"
}
}
},
"negative_boost": 0.5
}
}
}
在这个例子中:
positive
查询使用了multi_match
查询,用于在 "title" 和 "content" 字段中搜索包含关键词 "Elasticsearch" 的文章。negative
查询使用了range
查询,用于筛选一年前发布的文章。negative_boost
设置为 0.5,这意味着与negative
查询匹配的文档的相关性得分将乘以 0.5,从而降低它们在搜索结果中的权重。
通过这个 boosting
查询,我们可以在搜索结果中找到包含关键词 "Elasticsearch" 的文章,同时降低一年前发布的文章的权重,但仍然保留它们在搜索结果中。
# 固定评分
constant_score
是 Elasticsearch 中的一种查询类型,它用于将查询的结果集中的所有文档的相关性得分设置为一个固定值。这在某些情况下可能会很有用,例如当你不关心文档之间的相关性排名,而只关心它们是否匹配给定的查询条件时。
constant_score
查询包含两个主要参数:
filter
:一个查询子句,用于定义筛选条件。只有满足这个条件的文档才会出现在结果集中。boost
:一个浮点数,用于设置结果集中所有文档的固定相关性得分。默认值为 1.0。
以下是一个 constant_score
查询的示例:
GET /example/_search
{
"query": {
"constant_score": {
"filter": {
"term": {
"category": "electronics"
}
},
"boost": 1.5
}
}
}
在这个例子中,我们在 filter
参数中使用了一个 term
查询,用于筛选 "category" 字段为 "electronics" 的文档。所有满足这个条件的文档将出现在结果集中,并且它们的相关性得分都被设置为 1.5(由 boost
参数指定)。这意味着在这个查询中,所有返回的文档都具有相同的相关性得分,而不是根据它们与查询条件的匹配程度来计算得分。
# 最大析取查询
dis_max
是 Elasticsearch 中的一种查询类型,全称 "Disjunction Max Query"(最大析取查询)。它用于在多个子查询中选择最高相关性得分的文档,并为最终结果提供一个组合得分。dis_max
查询适用于那些希望在多个子查询中找到最佳匹配的场景,而不是将所有子查询的相关性得分组合起来。
dis_max
查询包含两个主要参数:
queries
:一个子查询数组,包含了所有要执行的子查询。dis_max
查询将从这些子查询中选择最高得分的结果。tie_breaker
:一个浮点数,范围为 0 到 1(包含 0 和 1)。用于调整其他子查询(除了最高得分子查询)对最终相关性得分的贡献程度。默认值为 0,表示其他子查询的得分不会影响最终得分。当tie_breaker
设置为 1 时,其他子查询的得分将与最高得分子查询的得分相加。
以下是一个 dis_max
查询的示例:
GET /example/_search
{
"query": {
"dis_max": {
"queries": [
{
"term": {
"title": {
"value": "Elasticsearch"
}
}
},
{
"term": {
"content": {
"value": "Elasticsearch"
}
}
}
],
"tie_breaker": 0.3
}
}
}
在这个例子中,我们有两个 term
子查询:一个针对 "title" 字段,另一个针对 "content" 字段。dis_max
查询将从这两个子查询中选择最高得分的结果。同时,通过设置 tie_breaker
为 0.3,我们允许其他子查询的得分(以 0.3 的权重)影响最终得分,从而在一定程度上保留其他子查询的匹配信息。
总之,dis_max
查询用于在多个子查询中选择最高相关性得分的文档,并允许通过 tie_breaker
参数调整其他子查询对最终得分的贡献程度。
# 确切值查询
Term-level queries 是 Elasticsearch 中针对确切值查询的一类查询,主要用于不分析字段的数据(如关键词字段、数字、日期等)。这类查询通常不适用于全文搜索,因为它们不考虑文本的分析过程。常见的 term-level 查询包括:
term
:查询包含指定词项的文档。{ "query": { "term": { "field_name": "value" } } }
terms
:查询包含指定多个词项的文档。{ "query": { "terms": { "field_name": ["value1", "value2"] } } }
range
:查询指定范围内的文档(适用于数字、日期等类型)。{ "query": { "range": { "field_name": { "gte": "value1", "lte": "value2" } } } }
exists
:查询包含指定字段的文档。{ "query": { "exists": { "field": "field_name" } } }
missing
:查询不包含指定字段的文档。{ "query": { "bool": { "must_not": { "exists": { "field": "field_name" } } } } }
prefix
:查询指定字段值前缀匹配的文档。{ "query": { "prefix": { "field_name": "value" } } }
wildcard
:查询指定字段值匹配通配符模式的文档。{ "query": { "wildcard": { "field_name": "val*e" } } }
regexp
:查询指定字段值匹配正则表达式模式的文档。{ "query": { "regexp": { "field_name": "val[0-9]+" } } }
fuzzy
:查询指定字段值与给定词项相似(基于编辑距离)的文档。{ "query": { "fuzzy": { "field_name": { "value": "value", "fuzziness": 2 } } } }
Term-level 查询在处理精确值匹配的场景下非常有用,例如过滤、数据查找和聚合等。
# 特定场景查询
在 Elasticsearch 中,有两类特殊的查询用于处理特定场景:Span queries(跨度查询) (opens new window)和 Specialized queries(专用查询) (opens new window)。
1. Span queries(跨度查询)
跨度查询主要用于处理对邻近性和顺序敏感的全文搜索。它们允许你指定查询中的词汇之间的距离和顺序。常用的跨度查询包括:
span_term
:查询单个词项。span_near
:查询多个词项,要求它们在指定的最大距离内并按指定顺序出现。span_not
:查询多个词项,要求它们在指定的最大距离内出现,但不能包含某些词项。span_or
:查询多个词项,要求它们在指定的最大距离内出现,其中任意一个词项都可以满足条件。span_first
:查询指定词项在文档中出现的位置是否在给定的范围内。span_containing
:查询指定词项是否包含在其他词项的范围内。span_within
:查询指定词项是否在其他词项的范围内。
2. Specialized queries(专用查询)
专用查询是为特定场景设计的查询类型,包括以下常见的查询:
more_like_this
:基于一个或多个参考文档,查找与其相似的文档。script_score
:使用脚本自定义文档的评分逻辑。percolate
:根据给定文档,查找匹配的查询。rank_feature
和rank_features
:查询基于数值特征字段的文档。
这些查询类型提供了更多针对特定场景的功能,让你能够根据需要构建更复杂的查询。
# 高亮
可以使用 highlight
功能来实现查询结果的高亮显示。高亮功能会在查询结果中突出显示匹配的关键词,以便用户更容易地识别和查看相关内容。
以下是一个简单的示例,展示如何在全文搜索查询中使用高亮功能。假设我们有一个包含文章信息的索引,我们想要搜索包含关键词 "Elasticsearch" 的文章,并高亮显示关键词。
GET /articles/_search
{
"query": {
"match": {
"content": "Elasticsearch"
}
},
"highlight": {
"fields": {
"content": {}
},
"pre_tags": ["<strong>"],
"post_tags": ["</strong>"]
}
}
查询结果中的hits大概是这样的:
{
"_index": "articles",
"_type": "_doc",
"_id": "1",
"_score": 0.5753642,
"_source": {
"title": "Elasticsearch 入门教程",
"content": "Elasticsearch 是一个分布式的搜索和分析引擎,它具有高度的可伸缩性和实时性。"
},
"highlight": {
"content": [
"<strong>Elasticsearch</strong> 是一个分布式的搜索和分析引擎,它具有高度的可伸缩性和实时性。"
]
}
}
查询结果中的高亮部分将包含在 highlight
字段下,关键词 "Elasticsearch" 将被 <strong>
和 </strong>
标签包围,突出显示在结果中。
# Suggester
在Elasticsearch中,suggest
并不是一个数据类型,而是一个用于自动补全和搜索建议的功能。它可以帮助用户在输入查询时,快速找到相关的搜索词或短语。suggest
功能在Elasticsearch中主要有以下几种类型:
Term Suggester:Term Suggester用于根据输入的文本提供拼写纠错建议。它基于文档中的词条计算输入词条的可能替代项,从而提供拼写纠错建议。Term Suggester对于检查输入词条的拼写错误以及提供相关的建议非常有用。
Phrase Suggester:Phrase Suggester用于为整个短语提供拼写纠错建议。与Term Suggester不同,Phrase Suggester会考虑短语中的多个词条及其相互之间的顺序和关联性。Phrase Suggester通常用于处理多词查询,以提供更为准确的拼写纠错建议。
Completion Suggester:Completion Suggester用于实现自动补全功能。通过基于用户输入的前缀提供匹配的建议,帮助用户快速找到相关的搜索词或短语。Completion Suggester使用特殊的数据结构(FST,有限状态转换器)存储候选词,以便实现快速的自动补全建议。
为了实现这些suggest功能,需要在索引映射中定义相应的字段类型。例如,要使用Completion Suggester,需要在映射中定义一个completion
类型的字段:
{
"mappings": {
"properties": {
"suggest": {
"type": "completion"
},
"title": {
"type": "text"
}
}
}
}
然后,向索引中添加带有建议的文档:
POST /my_index/_doc/1
{
"suggest": ["elasticsearch", "elastic"],
"title": "Introduction to Elasticsearch"
}
之后,可以使用Suggest API根据用户输入的前缀获取自动补全建议:
POST /my_index/_search
{
"suggest": {
"my-suggestion": {
"prefix": "elas",
"completion": {
"field": "suggest"
}
}
}
}
此查询将返回与输入前缀 "elas" 相关的自动补全建议。
# 搜索模板
搜索模板(Search Template)是一种基于模板的搜索技术,它可以帮助用户更简洁地创建复杂的查询。搜索模板允许用户定义可重用的查询模板,并使用参数动态填充模板中的变量。这可以提高查询的可读性和可维护性,同时减少了错误的可能性。
搜索模板使用 Mustache 模板语言,它是一种逻辑较少的模板语言,可用于 HTML、配置文件和各种编程语言。在 Elasticsearch 中,用户可以使用 Mustache 语法定义查询模板,并通过参数替换模板中的占位符。
以下是一个简单的搜索模板示例:
- 首先,创建一个名为
my_template
的搜索模板:
POST _scripts/my_template
{
"script": {
"lang": "mustache",
"source": {
"query": {
"match": {
"{{field}}": "{{query}}"
}
}
}
}
}
- 接下来,使用刚刚创建的搜索模板进行查询:
GET my-index-000001/_search/template
{
"id": "my_template",
"params": {
"field": "title",
"query": "Elasticsearch"
}
}
在这个示例中,我们创建了一个名为 my_template
的模板,用于执行一个简单的 match
查询。然后,我们使用该模板进行查询,通过 params
参数传递需要搜索的字段名(field
)和查询词(query
)。
这只是一个简单的示例,实际上搜索模板可以用于更复杂的查询场景,包括 bool 查询、聚合等。使用搜索模板可以使复杂查询更容易理解和维护。
# 聚合查询
# 概述
聚合查询是ElasticSearch中一个强大的功能,它实现了基于搜索结果的实时计算和分析。
你可以使用聚合查询对数据进行细粒度的分析,如总和、平均、范围、等等。
聚合查询主要分为三类:
- Bucket Aggregations。分桶聚合,类似于关系型数据库中的 Group By 语法,根据一定规则按照维度进行划分成不同的桶
- Metric Aggregations。指标分析聚合,比如计算某些指标的平均值、最大值,求和
- Pipeline Aggregations。管道分析类型,可以基于已有的聚合结果进行二次聚合运算
基本的聚合查询语法如下:
{
"aggs": {
"< aggregation_name >": {
"< aggregation_type >": {
"< aggregation_field >": "< field >"
}
}
}
}
# Bucket 聚合
Bucket 聚合类似于 Group By 的概念,按照一定的规则将文档分配到不同的桶中,主要分为下面几类:
# Terms 聚合
Terms聚合是基于确切值的字段对文档进行分组的一种方法。换句话说,它提供了一个类似于SQL的group_by操作的功能。
例如,我们有这样的一个需求:统计所有文档中,‘type’字段的统计情况:
{
"size": 0,
"aggs": {
"type_terms": {
"terms": {
"field": "type.keyword"
}
}
}
}
这个聚合查询将返回每个商品类型的计数,以及每个类型的桶(bucket)。每个桶包含两个属性:键(key)和文档计数(doc_count)。 返回的结果将类似于:
{
...
"aggregations": {
"type_terms": {
"buckets": [
{
"key": "Electronics",
"doc_count": 2
},
{
"key": "Clothing",
"doc_count": 2
}
]
}
}
}
在这个结果中,我们可以看到有两个桶,一个表示"Electronics"类型,另一个表示"Clothing"类型。每个桶的键(key)是商品类型的值,文档计数(doc_count)表示该类型的商品数量。
# Histogram 聚合
直方图聚合是一种可以对数值类型的字段文档进行分组的一种聚合方法。它需要一个间隔作为参数(必填字段),然后ElasticSearch会按照这个间隔对数值进行分档,得出每一档的文档数。
如我们有这样的一个需求:统计所有type为apple的文档的价格分布,可以按照以下方式进行查询:
GET products/_search
{
"size": 0,
"query": {
"term": {
"type.keyword": "apple"
}
},
"aggs": {
"price_histogram": {
"histogram": {
"field": "price",
"interval": 10
}
}
}
}
在上述查询中,我们首先使用term查询来筛选出type为apple的文档。然后,我们使用histogram聚合来对这些文档的价格进行分布统计。通过指定"field"参数为"price",我们告诉Elasticsearch要对价格字段进行聚合。"interval"参数指定了聚合的区间大小,这里设置为10表示将价格分为10个区间。
查询结果将返回每个价格区间的文档数量,以及每个区间的上下限。例如:
{
...
"aggregations": {
"price_histogram": {
"buckets": [
{
"key": 0,
"doc_count": 0
},
{
"key": 10,
"doc_count": 2
},
{
"key": 20,
"doc_count": 1
},
{
"key": 30,
"doc_count": 0
},
...
]
}
}
}
在这个结果中,我们可以看到价格区间为0-10的文档数量为2,价格区间为10-20的文档数量为1,其他价格区间的文档数量都为0。
# Date histogram 聚合
日期直方图聚合是直方图聚合的扩展,它在间隔参数上提供更加灵活的设定方式,并自动处理日期型数据的时间区间问题。
使用日期直方图聚合,可以按照指定的时间间隔对日期字段进行分布统计。这个聚合会自动处理不同时间区间的数据,并将其分配到相应的区间中。
以下是一个使用日期直方图聚合的示例:
GET products/_search
{
"size": 0,
"aggs": {
"date_histogram": {
"date_histogram": {
"field": "timestamp",
"interval": "1d",
"format": "yyyy-MM-dd"
}
}
}
}
在上述示例中,我们使用date_histogram聚合来对名为"timestamp"的日期字段进行分布统计。"interval"参数指定了时间间隔,这里设置为"1d"表示按天进行聚合。"format"参数指定了日期的格式,这里设置为"yyyy-MM-dd"。
查询结果将返回每天的文档数量,以及每个日期的时间戳。例如:
{
...
"aggregations": {
"date_histogram": {
"buckets": [
{
"key_as_string": "2022-01-01",
"key": 1640995200000,
"doc_count": 2
},
{
"key_as_string": "2022-01-02",
"key": 1641081600000,
"doc_count": 1
},
{
"key_as_string": "2022-01-03",
"key": 1641168000000,
"doc_count": 0
},
...
]
}
}
}
在这个结果中,我们可以看到2022年1月1日有2个文档,2022年1月2日有1个文档,2022年1月3日没有文档。
# Range 聚合
范围聚合允许在数据集中定义一个或多个范围,并计算每个范围中的文档数量。
使用范围聚合,你可以根据字段的值范围对文档进行分组,并统计每个范围内的文档数量。这个聚合通常用于数值型字段或日期型字段的范围分析。
以下是一个使用范围聚合的示例,假设我们有一个存储了用户年龄的索引,包含字段:年龄(age):
GET users/_search
{
"size": 0,
"aggs": {
"age_ranges": {
"range": {
"field": "age",
"ranges": [
{
"key": "18-25",
"from": 18,
"to": 25
},
{
"key": "26-35",
"from": 26,
"to": 35
},
{
"key": "36-45",
"from": 36,
"to": 45
}
]
}
}
}
}
在上述示例中,我们使用range聚合来对年龄字段进行范围分析。在"ranges"参数中,我们定义了三个范围:18-25、26-35和36-45。每个范围都有一个键(key),表示范围的名称,以及一个起始值(from)和一个结束值(to),表示范围的取值范围。
查询结果将返回每个范围的文档数量。例如:
{
...
"aggregations": {
"age_ranges": {
"buckets": [
{
"key": "18-25",
"doc_count": 10
},
{
"key": "26-35",
"doc_count": 20
},
{
"key": "36-45",
"doc_count": 15
}
]
}
}
}
在这个结果中,我们可以看到18-25岁范围内有10个文档,26-35岁范围内有20个文档,36-45岁范围内有15个文档。
# Metric 聚合
Metric Aggregations 对搜索结果中的数值进行了各种计算,包括:Count、Min、Max、Sum、Avg、Cardinality 等等。
# 单值分析
单值分析主要针对一个字段,计算它的最大值、最小值、平均值、总和、数量等信息,一般输出单个结果。
单值分析主要包括 min、max、avg、sum、cardinality,weight avg,value count 等。
min、max都比较好理解,介绍一下比较特别的三个:
- weight avg。在计算平均数时会使用另外一个字段作为每个文档的权重,比如 score = 99 学生有 3 个,score = 85 的学生有 5 个,求平均分数,人数就是这里的 weight
- cardinality。类似于关系数据库中的 distinct count
- value count。统计某字段所有有值的文档数
通过以下示例来展示sum、cardinality、weighted avg和value count这些Metric Aggregations的用法。
- Sum Aggregation(求和聚合):
GET orders/_search
{
"size": 0,
"aggs": {
"total_amount": {
"sum": {
"field": "amount"
}
}
}
}
上述示例将计算"amount"字段的总和。
查询结果将返回总和的值。例如:
{
...
"aggregations": {
"total_amount": {
"value": 1000
}
}
}
- Cardinality Aggregation(基数聚合):
GET orders/_search
{
"size": 0,
"aggs": {
"unique_customers": {
"cardinality": {
"field": "customer_id"
}
}
}
}
上述示例将计算"customer_id"字段的唯一值数量。
查询结果将返回唯一值数量。例如:
{
...
"aggregations": {
"unique_customers": {
"value": 50
}
}
}
- Weighted Avg Aggregation(加权平均聚合):
GET products/_search
{
"size": 0,
"aggs": {
"weighted_avg_price": {
"weighted_avg": {
"value": {
"field": "price"
},
"weight": {
"field": "quantity"
}
}
}
}
}
上述示例将根据"quantity"字段的值对"price"字段进行加权平均计算。
查询结果将返回加权平均值。例如:
{
...
"aggregations": {
"weighted_avg_price": {
"value": 15.75
}
}
}
- Value Count Aggregation(值计数聚合):
GET products/_search
{
"size": 0,
"aggs": {
"document_count": {
"value_count": {
"field": "_id"
}
}
}
}
上述示例将计算文档数量。
查询结果将返回文档数量。例如:
{
...
"aggregations": {
"document_count": {
"value": 100
}
}
}
# 多值分析
多值分析一般输出多个结果。
下面通过对stats、extended_stats、percentile、percentile rank和top hits的演示,来展示多值分析的用法。
- Stats Aggregation(统计聚合): 一次性返回 min、max、avg、sum、cardinality,weight avg,value count 的所有单值结果。
GET orders/_search
{
"size": 0,
"aggs": {
"order_stats": {
"stats": {
"field": "amount"
}
}
}
}
返回结果示例:
{
...
"aggregations": {
"order_stats": {
"count": 100,
"min": 10,
"max": 100,
"avg": 50,
"sum": 5000
}
}
}
- Extended Stats Aggregation(扩展统计聚合): 对 stats 进行扩展,包含更多,如:方差,标准差,标准差等
GET orders/_search
{
"size": 0,
"aggs": {
"order_extended_stats": {
"extended_stats": {
"field": "amount"
}
}
}
}
返回结果示例:
{
...
"aggregations": {
"order_extended_stats": {
"count": 100,
"min": 10,
"max": 100,
"avg": 50,
"sum": 5000,
"sum_of_squares": 250000,
"variance": 833.33,
"std_deviation": 28.87,
"std_deviation_bounds": {
"upper": 107.74,
"lower": -7.74
}
}
}
}
- Percentiles Aggregation(百分位数聚合): 百分位数统计,比如用于统计索引中 25%, 50%, 75% 的文档的 amount 都分别小于哪些值
GET orders/_search
{
"size": 0,
"aggs": {
"order_percentiles": {
"percentiles": {
"field": "amount",
"percents": [25, 50, 75]
}
}
}
}
返回结果示例:
{
...
"aggregations": {
"order_percentiles": {
"values": {
"25.0": 30,
"50.0": 50,
"75.0": 70
}
}
}
}
结果示例中的键表示百分位数,例如"25.0"表示25%的数据位于30以下,"50.0"表示50%的数据位于50以下,"75.0"表示75%的数据位于70以下。
- Percentile Ranks Aggregation(百分位数排名聚合): 和 percentile 统计方向相反,比如用于统计amount小于 50, 75, 100 的订单落在哪个百分比上
GET orders/_search
{
"size": 0,
"aggs": {
"order_percentile_ranks": {
"percentile_ranks": {
"field": "amount",
"values": [50, 75, 100]
}
}
}
}
返回结果示例:
{
...
"aggregations": {
"order_percentile_ranks": {
"values": {
"50.0": 0.5,
"75.0": 0.8,
"100.0": 1.0
}
}
}
}
这个示例告诉我们"amount"字段的值在数据集中所处的位置百分比。例如,对于"50.0"这个百分位数排名,0.5是一个关键值,表示"amount"字段的值在数据集中的50%位置上。
- Top Hits Aggregation(Top Hits聚合): 一般用于分桶之后,获取每个桶内最匹配的前几个文档的列表,即详情数据,使用时一般需要带上排序信息
//用于查询 sales 索引中按照 type 字段进行聚合分桶,然后返回每个分桶中按照 date 字段降序后的 top 1 的所有文档
POST /sales/_search?size=0
{
"aggs": {
"top_tags": {
"terms": { //terms 分桶,后面会有讲解
"field": "type",
"size": 3
},
"aggs": {
"top_sales_hits": {
"top_hits": {
"sort": [ //对每个桶中的文档按照 date 字段降序,默认情况下按照查询分数进行排序
{
"date": {
"order": "desc"
}
}
],
"_source": { //返回每个文档的 date 和 price 字段
"includes": [ "date", "price" ]
},
"size" : 1 //只返回 top 1
}
}
}
}
}
}
返回结果示例:
{
...
"aggregations": {
"top_tags": {
"buckets": [
{
"key": "type1",
"doc_count": 100,
"top_sales_hits": {
"hits": {
"total": {
"value": 100
},
"max_score": null,
"hits": [
{
"_index": "sales",
"_type": "_doc",
"_id": "1",
"_score": null,
"_source": {
"date": "2022-01-01",
"price": 100
}
}
]
}
}
},
...
]
}
}
}
# Pipeline Aggregation
Pipeline Aggregation 是一种在其他两种聚合查询的结果上,进行二次计算和分析的聚合类型。
有的时候,我们在获取统计结果后,可能会对结果数据进行进一步的分析。比如,我们可能会对某一段时间(比如,每个月)的数据进行统计,然后再对每个月的数据进行比较,看看数据趋势等信息。这就需要使用Pipeline Aggregation。
例如,我们需要统计每月的销售总额,并计算其环比增长:
{
"size": 0,
"aggs": {
"sales_per_month": {
"date_histogram": {
"field": "timestamp",
"calendar_interval": "month"
},
"aggs": {
"sales": {
"sum": {
"field": "price"
}
},
"sales_deriv": {
"derivative": {
"buckets_path": "sales"
}
}
}
}
}
}
在上述示例中,"sales_per_month"
是一个Bucket Aggregation,对每个月的所有数据进行分组;"sales"
是一个Metric Aggregation,对每个月的销售额进行求和;"sales_deriv"
是一个Pipeline Aggregation,通过指定"buckets_path": "sales"
表示对"sales"
的结果进行处理,使用"derivative"
类型表示求销售额的环比增长。
ElasticSearch 的聚合查询功能非常强大,几乎可以满足各种数据分析的需求。单个聚合查询可以解决简单的问题,多个聚合查询可以解决相对复杂的问题,而Pipeline Aggregation 则可以满足我们基于一种或多种聚合结果再次进行计算和分析的需求。
# 其他聚合查询用法
上面的介绍只是抛砖引玉,其实还有很多其他的聚合函数,请参考官网:Aggregations (opens new window)
# Elasticsearch能join么
Elasticsearch 不是关系型数据库,因此它不支持传统意义上的 JOIN 操作。但是,你可以使用两种方法来实现类似 JOIN 的功能:
- 使用两次查询并在客户端进行 JOIN。
- 使用父子文档或嵌套文档来模拟 JOIN。
方法1:两次查询
首先,执行一个查询获取索引 A 中的结果,然后从这些结果中提取所有 id
值。接下来,使用提取的 id
值在索引 B 中执行第二个查询,查询条件为 r_id
与提取的 id
值匹配。最后,在客户端(例如你的应用程序代码中)将两个查询结果进行合并。
这种方法的缺点是需要两次查询,可能导致性能下降,而且合并操作需要在客户端完成。
方法2:父子文档或嵌套文档
Elasticsearch 支持将文档与其他文档建立父子关系或使用嵌套文档。这可以让你在一个索引内存储相关数据,并在查询时实现类似 JOIN 的功能。
父子文档:你可以为两个相关的文档类型定义父子关系。父文档和子文档存储在相同的分片上,因此可以在查询时进行关联。使用
has_child
和has_parent
查询,可以在父子文档之间进行查询。但是,这种方法对性能有影响,因为需要对分片进行全局搜索。嵌套文档:嵌套文档是将多个相关的文档存储在一个单独的文档内。这些内部文档独立索引,并且可以使用
nested
查询进行查询。这种方法在性能上优于父子文档,但需要预先确定嵌套关系并将相关数据放在同一个文档中。
根据你的具体需求和数据结构,可以选择以上方法中的一种来实现类似 JOIN 的功能。但请注意,Elasticsearch 本质上不是关系型数据库,因此它并不适合执行复杂的 JOIN 操作。在设计数据模型时,尽量遵循扁平化和非关系型的原则。
# 父子文档
在 Elasticsearch 中使用父子文档,需要遵循以下几个步骤:
- 创建索引并设置父子关系。
- 索引父文档和子文档。
- 使用
has_child
和has_parent
查询进行搜索。
1. 创建索引并设置父子关系
在创建索引时,你需要为相关的文档类型定义父子关系。通过在映射中设置 _parent
字段,指定父类型。以下示例展示了如何创建一个名为 company
的索引,并定义 employee
类型的文档具有指向 department
类型的父关系:
PUT /company
{
"mappings": {
"properties": {
"department": {
"properties": {
"name": { "type": "text" }
}
},
"employee": {
"_parent": {
"type": "department"
},
"properties": {
"name": { "type": "text" },
"position": { "type": "text" }
}
}
}
}
}
2. 索引父文档和子文档
接下来,你可以索引父文档和子文档。在索引子文档时,需要指定其父文档的 ID。以下示例展示了如何索引一个部门(父文档)和一个员工(子文档):
PUT /company/_doc/1
{
"name": "IT Department"
}
PUT /company/_doc/2?routing=1
{
"name": "Alice",
"position": "Software Engineer",
"_parent": 1
}
注意在索引子文档时,我们需要使用 routing
参数,其值应与父文档的 ID 相同。这样可以确保父子文档存储在相同的分片上。
3. 使用 has_child
和 has_parent
查询进行搜索
你可以使用 has_child
查询在具有子文档的父文档上进行搜索,或者使用 has_parent
查询在具有父文档的子文档上进行搜索。
以下示例展示了如何使用 has_child
查询查找具有名为 "Software Engineer" 的员工的部门:
GET /company/_search
{
"query": {
"has_child": {
"type": "employee",
"query": {
"match": {
"position": "Software Engineer"
}
}
}
}
}
以下示例展示了如何使用 has_parent
查询查找属于 "IT Department" 的员工:
GET /company/_search
{
"query": {
"has_parent": {
"type": "department",
"query": {
"match": {
"name": "IT Department"
}
}
}
}
}
# 嵌套查询
nested
查询用于在 Elasticsearch 中搜索嵌套对象。嵌套对象是将一组相关文档存储在一个父文档内的结构。这些内部文档独立索引,可以在查询时独立评分。当你有一个包含对象数组的文档时,使用嵌套类型和嵌套查询可以更好地处理这些数据。
创建嵌套映射
在使用嵌套查询之前,你需要先创建具有嵌套映射的索引。例如,以下示例创建一个名为 company
的索引,其中包含一个名为 employees
的嵌套字段:
PUT /company
{
"mappings": {
"properties": {
"name": { "type": "text" },
"employees": {
"type": "nested",
"properties": {
"name": { "type": "text" },
"position": { "type": "text" }
}
}
}
}
}
索引嵌套文档
接下来,你可以索引包含嵌套对象的文档。例如,以下示例向 company
索引添加一个包含两名员工的文档:
PUT /company/_doc/1
{
"name": "Awesome Company",
"employees": [
{
"name": "Alice",
"position": "Software Engineer"
},
{
"name": "Bob",
"position": "Product Manager"
}
]
}
执行 nested 查询
要在嵌套对象上执行查询,需要使用 nested
查询。以下示例展示了如何查找名为 "Software Engineer" 的员工所在的公司:
GET /company/_search
{
"query": {
"nested": {
"path": "employees",
"query": {
"match": {
"employees.position": "Software Engineer"
}
},
"inner_hits": {}
}
}
}
此查询的结果将包含满足条件的嵌套对象以及对应的父文档。inner_hits
参数会返回匹配的嵌套文档。
# Easy-Es
Easy-Es (opens new window)(简称EE)是一款基于ElasticSearch(简称Es)官方提供的RestHighLevelClient打造的ORM开发框架,在 RestHighLevelClient 的基础上,只做增强不做改变,为简化开发、提高效率而生,您如果有用过Mybatis-Plus(简称MP),那么您基本可以零学习成本直接上手。
这是一款优秀的Elasticsearch ORM框架,可以简化Es的查询,类SQL语法,快速上手。
# 总结
Elasticsearch是一种功能强大的全文搜索和分析引擎,它可以帮助您轻松地处理大量数据,并从中获取有价值的信息。
Elasticsearch并非没有缺点。它的复杂性和学习曲线可能会使初学者感到困惑,并且在处理大量数据时需要大量的资源和硬件支持。
不过,随着大数据和人工智能的不断发展,Elasticsearch的未来看起来十分光明。它将成为越来越多企业和机构进行搜索和分析的首选工具。鼓励你在实际应用中尝试使用Elasticsearch,并通过与社区互动来进一步了解和探索其功能。
希望本文能够为您提供足够的指导和启示,帮助您更好地利用Elasticsearch的强大功能来分析和理解您的数据。
祝你变得更强!