MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

ElasticSearch搜索数据基本概念全解析

2024-09-066.8k 阅读

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” 索引中,你可以按类别对商品进行分组,并计算每个类别的平均价格、商品数量等。

聚合的类型

  1. 桶聚合(Bucket Aggregation):桶聚合用于将文档分组到不同的桶中,每个桶代表一个特定的条件或范围。常见的桶聚合类型包括 terms 聚合(按关键字字段分组)、range 聚合(按数值范围分组)、date_range 聚合(按日期范围分组)等。例如,使用 terms 聚合按类别对商品进行分组的查询如下:
{
  "aggs": {
    "product_categories": {
      "terms": {
        "field": "category"
      }
    }
  }
}

上述查询会在 “products” 索引中按 category 字段对商品进行分组,并返回每个类别的文档数量。

  1. 度量聚合(Metric Aggregation):度量聚合用于计算统计指标,如平均值、总和、标准差等。度量聚合通常与桶聚合一起使用,在每个桶内进行计算。例如,要计算每个类别商品的平均价格,可以在上述 terms 聚合的基础上添加 avg 度量聚合:
{
  "aggs": {
    "product_categories": {
      "terms": {
        "field": "category"
      },
      "aggs": {
        "avg_price": {
          "avg": {
            "field": "price"
          }
        }
      }
    }
  }
}

上述查询会按类别对商品进行分组,并计算每个类别的平均价格。

  1. 嵌套聚合(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 进行高效的数据搜索和分析。在实际应用中,根据具体的业务需求灵活运用这些概念和功能,可以构建出强大的搜索和数据分析系统。