ElasticSearch搜索数据基本概念全解析
ElasticSearch 核心概念
索引(Index)
在 ElasticSearch 中,索引是一个存储和组织数据的逻辑容器,类似于关系型数据库中的数据库概念,但功能更为灵活和强大。从本质上讲,索引是一个文档集合,这些文档具有相似的结构和语义。例如,你可以创建一个名为 “products” 的索引来存储所有商品相关的信息,每个商品就是该索引中的一个文档。
在 ElasticSearch 中创建索引非常简单,使用 Elasticsearch 的 RESTful API 可以轻松实现。以下是使用 Python 的 Elasticsearch 客户端库来创建索引的代码示例:
from elasticsearch import Elasticsearch
es = Elasticsearch([{'host': 'localhost', 'port': 9200}])
index_name = 'test_index'
body = {
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
}
}
es.indices.create(index=index_name, body=body)
上述代码首先连接到本地运行的 Elasticsearch 实例,然后定义了一个名为 test_index
的索引,并设置了索引的分片数量为 3,副本数量为 1。这些设置对于索引的性能和可用性有重要影响。分片允许 Elasticsearch 将数据分布在多个节点上,提高存储和查询的并行处理能力;副本则提供数据冗余,增强系统的容错性。
类型(Type,在 Elasticsearch 7.x 及以上逐渐弃用)
在早期版本中,类型是索引内部的一种逻辑分类机制,用于区分不同类型的文档。例如,在 “products” 索引中,你可以定义 “electronics” 和 “clothes” 两种类型,分别存储电子产品和服装产品的文档。然而,随着 Elasticsearch 的发展,发现类型机制在某些情况下会导致混淆和复杂性,特别是在映射管理和跨类型搜索方面。
从 Elasticsearch 7.x 版本开始,逐渐弃用类型的概念,鼓励将不同类型的文档存储在不同的索引中。虽然在某些旧版本的代码和配置中仍可能看到类型的使用,但在新的开发中应尽量避免依赖它。
文档(Document)
文档是 Elasticsearch 中最基本的数据单元,它是一个 JSON 格式的数据结构,包含了一个或多个字段及其对应的值。每个文档都存储在一个特定的索引中,并且可以属于某个类型(在支持类型的版本中)。例如,一个商品文档可能如下所示:
{
"product_name": "iPhone 14",
"price": 999,
"category": "Smartphone",
"description": "The latest iPhone model with advanced features."
}
在 Elasticsearch 中,每个文档都有一个唯一的标识符(ID),可以在创建文档时指定,也可以由 Elasticsearch 自动生成。使用 Python 的 Elasticsearch 客户端库创建文档的代码示例如下:
index_name = 'products'
doc_type = 'electronics'
doc_id = '1'
doc_body = {
"product_name": "iPhone 14",
"price": 999,
"category": "Smartphone",
"description": "The latest iPhone model with advanced features."
}
es.index(index=index_name, doc_type=doc_type, id=doc_id, body=doc_body)
上述代码将一个商品文档存储到 products
索引的 electronics
类型中,并指定了文档 ID 为 1
。
字段(Field)
字段是文档中的最小数据单元,对应于 JSON 对象中的键值对。每个字段都有一个名称和一个或多个值。字段的类型在 Elasticsearch 中非常重要,因为它决定了字段如何存储和索引,进而影响搜索性能。常见的字段类型包括:
- 文本类型(Text):用于存储长文本,如文章内容、产品描述等。文本类型的字段在索引时会进行分词处理,将文本拆分成一个个单词,以便进行全文搜索。
- 关键字类型(Keyword):适用于存储精确值,如产品 ID、品牌名称等。关键字类型的字段不会进行分词,而是直接存储整个值,适合用于精确匹配和聚合操作。
- 数值类型(Numeric):包括整数(Integer)、长整数(Long)、浮点数(Float)等,用于存储数值数据,方便进行数值范围查询和统计。
- 日期类型(Date):用于存储日期和时间信息,可以按照日期范围进行查询和排序。
以下是一个包含不同字段类型的文档示例:
{
"product_id": "P001",
"product_name": "Laptop",
"description": "A high - performance laptop for business use.",
"price": 1299.99,
"release_date": "2023 - 01 - 15"
}
在这个文档中,product_id
是关键字类型,product_name
可以是文本类型,description
是文本类型,price
是数值类型,release_date
是日期类型。
ElasticSearch 数据存储与分布式原理
分片(Shard)
Elasticsearch 通过分片机制将索引数据分布在多个节点上,以实现水平扩展和提高性能。每个分片是一个独立的 Lucene 索引,包含了索引数据的一部分。当索引数据量较大时,将其分散到多个分片上可以提高查询的并行处理能力。例如,一个包含数百万文档的索引可以分成多个分片,每个分片存储一部分文档,查询时 Elasticsearch 可以并行地在多个分片上执行查询,然后将结果合并返回。
在创建索引时可以指定分片数量,如前面创建索引的代码示例中设置了 number_of_shards
为 3。Elasticsearch 会自动在集群中的节点上分配这些分片。分片分为主分片(Primary Shard)和副本分片(Replica Shard)。主分片负责处理文档的写入和读取请求,而副本分片则是主分片的拷贝,用于提供数据冗余和提高查询性能。副本分片可以处理读请求,分担主分片的负载,并且在主分片所在节点故障时,可以提升为新的主分片,保证数据的可用性。
分布式存储与复制
Elasticsearch 的分布式存储机制基于分片和副本的概念。当一个文档被索引时,Elasticsearch 首先根据文档的 ID 计算出它应该存储在哪个主分片上,然后将文档发送到对应的节点上的主分片进行存储。同时,主分片会将文档复制到它的副本分片上,以实现数据冗余。
例如,假设集群中有 3 个节点(Node1、Node2、Node3),一个索引有 3 个主分片(P1、P2、P3)和 1 个副本分片(R1、R2、R3,分别对应 P1、P2、P3 的副本)。当一个文档被索引时,它可能被存储在 P1 上,然后 P1 会将文档复制到 R1 上。如果 Node1(存储 P1 的节点)发生故障,Elasticsearch 可以从 R1 所在的节点(假设是 Node2)将 R1 提升为新的主分片,继续提供服务,同时可以在其他节点上重新创建一个新的副本分片来恢复冗余。
ElasticSearch 搜索原理
倒排索引(Inverted Index)
倒排索引是 Elasticsearch 实现高效搜索的核心数据结构。与传统的正向索引(从文档到词项的映射)不同,倒排索引是从词项到文档的映射。例如,假设有两个文档: 文档 1:“Elasticsearch is a powerful search engine.” 文档 2:“Search engines are used for information retrieval.”
倒排索引会将每个词项(如 “Elasticsearch”、“search”、“engine” 等)与其出现的文档列表及位置信息关联起来。如下是一个简化的倒排索引示例:
词项 | 文档列表 |
---|---|
Elasticsearch | 文档 1 |
is | 文档 1 |
a | 文档 1 |
powerful | 文档 1 |
search | 文档 1、文档 2 |
engine | 文档 1 |
engines | 文档 2 |
are | 文档 2 |
used | 文档 2 |
for | 文档 2 |
information | 文档 2 |
retrieval | 文档 2 |
当进行搜索时,Elasticsearch 首先对查询语句进行分词,得到一系列词项,然后在倒排索引中查找这些词项,快速定位到包含这些词项的文档,再根据相关性算法对这些文档进行排序,最终返回给用户。
相关性算法(Relevance Algorithm)
Elasticsearch 使用多种相关性算法来计算文档与查询的相关性得分,其中最常用的是 TF - IDF(Term Frequency - Inverse Document Frequency)算法及其变体。
- 词频(Term Frequency, TF):指的是一个词项在文档中出现的频率。词项在文档中出现的次数越多,说明该文档与该词项的相关性可能越高。例如,在文档 “This is a sample document. This document is for testing.” 中,“document” 出现了 2 次,其词频为 2。
- 逆文档频率(Inverse Document Frequency, IDF):衡量一个词项在整个索引中的普遍程度。如果一个词项在大多数文档中都出现,那么它对于区分不同文档的作用就不大,其逆文档频率就低;反之,如果一个词项只在少数文档中出现,其逆文档频率就高。IDF 通过计算包含该词项的文档数量的倒数来得到。
相关性得分是 TF 和 IDF 的乘积,再结合其他因素(如字段权重、查询语句结构等)进行调整。Elasticsearch 还支持其他相关性算法,如 BM25 等,并且可以根据具体的应用场景进行参数调整,以获得更符合需求的搜索结果。
ElasticSearch 查询语法
简单查询(Simple Query)
简单查询是 Elasticsearch 中最基本的查询方式,用于对单个字段进行简单的匹配。例如,要在 “products” 索引中查找价格为 999 的商品,可以使用以下的查询语句(以 JSON 格式表示):
{
"query": {
"match": {
"price": 999
}
}
}
在 Python 中使用 Elasticsearch 客户端库执行上述查询的代码如下:
index_name = 'products'
query = {
"query": {
"match": {
"price": 999
}
}
}
result = es.search(index=index_name, body=query)
print(result)
上述代码通过 match
查询在 products
索引中查找 price
字段值为 999 的文档,并打印查询结果。
复合查询(Compound Query)
复合查询允许将多个简单查询组合起来,以实现更复杂的搜索逻辑。常见的复合查询包括 bool
查询,它可以包含 must
(必须满足的条件)、should
(应该满足的条件)和 must_not
(必须不满足的条件)子句。例如,要查找价格在 500 到 1000 之间且类别为 “electronics” 的商品,可以使用以下 bool
查询:
{
"query": {
"bool": {
"must": [
{
"range": {
"price": {
"gte": 500,
"lte": 1000
}
}
},
{
"match": {
"category": "electronics"
}
}
]
}
}
}
在 Python 中执行该查询的代码如下:
index_name = 'products'
query = {
"query": {
"bool": {
"must": [
{
"range": {
"price": {
"gte": 500,
"lte": 1000
}
}
},
{
"match": {
"category": "electronics"
}
}
]
}
}
}
result = es.search(index=index_name, body=query)
print(result)
上述代码使用 bool
查询结合 range
查询和 match
查询,满足 price
字段在 500 到 1000 之间且 category
为 “electronics” 的条件,然后打印查询结果。
全文搜索(Full - Text Search)
全文搜索是 Elasticsearch 的核心功能之一,用于在文本字段中进行模糊匹配和语义理解。例如,要在 “products” 索引的 “description” 字段中搜索包含 “high performance” 的商品,可以使用以下查询:
{
"query": {
"match": {
"description": "high performance"
}
}
}
match
查询会对查询语句进行分词,然后在倒排索引中查找相关的词项。Elasticsearch 还支持更高级的全文搜索功能,如 match_phrase
查询,用于匹配短语。例如,要精确匹配 “high performance” 这个短语,可以使用以下查询:
{
"query": {
"match_phrase": {
"description": "high performance"
}
}
}
match_phrase
查询要求词项在文档中以相同的顺序相邻出现,从而实现更精确的短语匹配。
ElasticSearch 聚合(Aggregation)
聚合的基本概念
聚合是 Elasticsearch 中用于对数据进行统计和分析的强大功能。它允许你对文档集合进行分组、计算统计指标(如平均值、总和、最大值、最小值等)以及执行其他复杂的数据分析操作。例如,在 “products” 索引中,你可以按类别对商品进行分组,并计算每个类别的平均价格、商品数量等。
聚合的类型
- 桶聚合(Bucket Aggregation):桶聚合用于将文档分组到不同的桶中,每个桶代表一个特定的条件或范围。常见的桶聚合类型包括
terms
聚合(按关键字字段分组)、range
聚合(按数值范围分组)、date_range
聚合(按日期范围分组)等。例如,使用terms
聚合按类别对商品进行分组的查询如下:
{
"aggs": {
"product_categories": {
"terms": {
"field": "category"
}
}
}
}
上述查询会在 “products” 索引中按 category
字段对商品进行分组,并返回每个类别的文档数量。
- 度量聚合(Metric Aggregation):度量聚合用于计算统计指标,如平均值、总和、标准差等。度量聚合通常与桶聚合一起使用,在每个桶内进行计算。例如,要计算每个类别商品的平均价格,可以在上述
terms
聚合的基础上添加avg
度量聚合:
{
"aggs": {
"product_categories": {
"terms": {
"field": "category"
},
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
}
}
}
}
}
上述查询会按类别对商品进行分组,并计算每个类别的平均价格。
- 嵌套聚合(Nested Aggregation):嵌套聚合允许在一个聚合的结果上再应用其他聚合。例如,你可以先按类别分组,然后在每个类别中再按品牌分组,并计算每个品牌的商品数量。
{
"aggs": {
"product_categories": {
"terms": {
"field": "category"
},
"aggs": {
"brand_count": {
"terms": {
"field": "brand"
}
}
}
}
}
}
上述查询会先按 category
字段分组,然后在每个类别组内再按 brand
字段分组,并返回每个品牌的文档数量。
ElasticSearch 映射(Mapping)
映射的定义与作用
映射定义了索引中文档的字段结构、数据类型以及如何进行索引。它类似于关系型数据库中的表结构定义,但更加灵活。通过映射,Elasticsearch 可以知道如何存储和索引每个字段,以及如何在搜索时处理这些字段。例如,对于文本字段,映射可以指定分词器,对于数值字段,可以指定是否进行范围查询优化等。
动态映射(Dynamic Mapping)
Elasticsearch 具有动态映射功能,当你索引一个新文档时,如果索引不存在,Elasticsearch 会自动创建索引,并根据文档的字段类型推测出映射。例如,如果你索引一个包含 price
字段且值为数值的文档,Elasticsearch 会自动将 price
字段映射为数值类型。动态映射方便了快速开发和部署,但在一些生产环境中,可能需要更精确的控制,因此也可以手动定义映射。
手动映射(Manual Mapping)
手动映射允许你精确指定每个字段的类型、属性等。以下是一个手动定义映射的示例:
{
"mappings": {
"properties": {
"product_name": {
"type": "text",
"analyzer": "standard"
},
"price": {
"type": "float"
},
"category": {
"type": "keyword"
},
"description": {
"type": "text",
"analyzer": "english"
}
}
}
}
上述映射定义了 product_name
字段为文本类型,使用标准分词器;price
字段为浮点数类型;category
字段为关键字类型;description
字段为文本类型,使用英语分词器。在创建索引时,可以将这个映射作为请求体发送给 Elasticsearch 来创建具有指定映射的索引。
通过对 Elasticsearch 搜索数据基本概念的全面解析,包括索引、文档、查询、聚合、映射等方面,你可以更好地理解和使用 Elasticsearch 进行高效的数据搜索和分析。在实际应用中,根据具体的业务需求灵活运用这些概念和功能,可以构建出强大的搜索和数据分析系统。