ElasticSearch 复习


什么是ElasticSearch

ElasticSearch是一个基于Lucene的开源分布式搜索引擎,由Elastic公司开发。它具有以下特点:

核心特性

  • 分布式搜索引擎:基于Lucene,提供分布式的全文搜索功能

  • RESTful API:通过HTTP使用JSON进行数据交互

  • 实时分析:支持实时数据分析

  • 高可用性:分布式架构确保无单点故障

  • 可扩展性:可以从小规模扩展到PB级数据

应用场景

  • 站内搜索:网站或应用内的搜索功能

  • 日志分析:与Logstash和Kibana组成ELK栈,用于日志收集和分析

  • 数据分析:结构化数据的快速分析

  • 全文检索:文档、商品描述等全文内容的检索

  • 监控系统:用于系统性能指标的存储和分析

主要概念

Elasticsearch 概念 关系型数据库 (MySQL) 类比 一句话解释
Index (索引) Database (数据库) 一个存放一类数据的“库”。
Document (文档) Row (一行数据) 一条可被搜索的记录。
Field (字段) Column (一列) 一条记录中的一个数据项。
Mapping (映射) Schema (表结构定义) 定义每个字段类型的规则。
Cluster (集群) 整个数据库服务 协同工作的整个 ES 服务。
Node (节点) 一个数据库服务器实例 集群中的一台服务器。

1. Document (文档)

  • 是什么: Elasticsearch 中最基本的、可被索引的信息单元

  • 类比: 数据库表中的一行 (Row) 数据。

  • 解释: 它是一个用 JSON 格式表示的数据对象。你存入 ES 的每一条商品信息、每一篇博客、每一条日志,都是一个独立的文档。这是你进行搜索和分析的最小对象。

示例:

{
  "product_id": "A-123-B-456",
  "title": "华为 Mate 60 Pro",
  "price": 6999.00
}

2. Field (字段)

  • 是什么: 组成文档的一个个键值对 (Key-Value Pair)

  • 类比: 数据库表中的一列 (Column)

  • 解释: 每个字段都有一个特定的数据类型(比如 text 用于全文搜索,keyword 用于精确值,integer 用于整数,date 用于日期等)。你在 Mapping 里定义的就是每个字段的类型和规则。

示例: 在上面的文档中,"title": "华为 Mate 60 Pro" 就是一个字段。title 是字段名,"华为 Mate 60 Pro" 是字段值。

3. Index (索引)

  • 是什么: 一个文档的集合。它拥有相似的特征,是 ES 中数据管理的最高层级单位。

  • 类比: 一个数据库 (Database)

  • 解释: 比如,你可以创建一个 products 索引来存放所有商品文档,再创建一个 logs 索引来存放所有日志文档。我们所有的查询、更新、删除操作,都是针对一个或多个索引来进行的。索引的名称必须是小写。

4. Shard (分片)

  • 是什么: 索引的物理拆分。每个分片都是一个功能齐全、独立的“子索引”。

  • 类比: 把一个巨大的数据库表水平分区 (Partitioning) 成多个小表。

  • 解释: 这是 Elasticsearch 实现水平扩展和高并发的核心。当一个索引的数据量太大,单个节点存不下或处理不过来时,ES 会将这个索引拆分成多个分片,并将这些分片分布到不同的节点上。这样,一个查询请求可以同时在多个分片上并行执行,极大地提升了处理能力。

    • 主分片 (Primary Shard): 索引的每个文档都只属于一个主分片。主分片的数量在索引创建时就必须固定,之后不能修改。

5. Replica (副本)

  • 是什么: 分片的一份或多份拷贝

  • 类比: 数据库的主从复制中的“从库”。

  • 解释: 副本的主要作用有两个,我们之前也提到过:

  1. 高可用性 (High Availability): 副本和它的主分片永远不会被分配在同一个节点上。如果持有主分片的节点宕机,ES 会立即将一个副本“提升”为新的主分片,保证服务不中断,数据不丢失。
  2. 提升读性能 (Increase Read Throughput): 查询请求(读操作)可以由主分片或任何一个副本分片来处理,从而将读请求的压力分摊到更多的节点上。

6. Node (节点)

  • 是什么: 集群中的一个服务器实例

  • 类比: 一台运行着 MySQL 实例的服务器

  • 解释: 它是构成集群的单个成员,负责存储数据、参与索引和搜索。每个节点都有自己的名字,并通过 cluster.name 加入到指定的集群中。我们之前讨论的选举和心跳检测,都是在节点之间进行的。

7. Cluster (集群)

  • 是什么:一个或多个节点组成的集合。

  • 类比: 整个高可用的数据库集群

  • 解释: 它将所有节点的数据和计算能力汇集在一起,对外提供统一的服务。你与 ES 的所有交互,都是通过集群中的某个节点进行的。集群负责管理所有索引、分片、副本的健康和分布,确保整个系统的稳定运行。

为什么说“索引的每个文档都只属于一个主分片。主分片的数量在索引创建时就必须固定,之后不能修改。”

我们先来理解第一部分:“索引的每个文档都只属于一个主分片”

这句话意味着,当你保存一个文档时,Elasticsearch 必须有一个确定的、可重复的方法来决定这个文档应该存到哪个主分片里。它不能这次存到分片1,下次又存到分片2,那样就乱套了,永远也找不到数据。

这个决定过程,就叫做 “路由” (Routing)

路由是如何工作的?

Elasticsearch 使用一个非常简单的公式:

shard_number = hash(routing_value) % number_of_primary_shards

我们来解释这个公式里的每一项:

  • routing_value (路由值):默认情况下,这个值就是文档的 **_id**。你也可以手动指定一个值。

  • hash():一个哈希函数,它能把任意一个字符串(比如 _id)转换成一个固定的数字。

  • %:取余数运算符。

  • number_of_primary_shards:你创建索引时设定的主分片数量。

举个例子:

  1. 你创建了一个 products 索引,并设定它有 3 个主分片

  2. 现在,你要存入一个新商品文档,它的 _id“A-123-B-456”

  3. ES 会对 “A-123-B-456” 这个 _id 进行哈希计算,假设得到一个数字 2096

  4. 然后,ES 计算 2096 % 3,结果是 2

  5. 结论: ES 就知道了,这个文档必须被存到主分片2 (Shard 2) 上。

下次你根据这个 _id 来获取或更新这个文档时,ES 会重复一遍完全相同的计算,再次得到 2,然后直接去主分片2上找,而不需要去问分片0和分片1,效率极高。

现在,我们来理解第二部分,也是最关键的部分:

“主分片的数量在索引创建时就必须固定,之后不能修改”,为什么不能修改?

我们继续用上面的例子。假设 ES 允许你把主分片数量从 3 修改成 4

现在,你还是想找那个 _id 是 “A-123-B-456” 的文档。ES 再次执行路由计算:

  1. hash("A-123-B-456") 的结果依然是 2096

  2. 但是,现在主分片数量变成了 4,所以公式变成了 2096 % 4

  3. 计算结果是 **0**

灾难发生了!

ES 现在认为这个文档应该在主分片0 (Shard 0) 上。但实际上,它当初被存放在了主分片2上。ES 去分片0上找,结果肯定是“找不到”。

如果你修改了主分片的数量,所有之前文档的路由规则就全部失效了,整个索引的数据就陷入了彻底的混乱,你将无法通过 _id 定位到任何一个文档。

这就是为什么主分片数量一旦设定,就不能再修改的根本原因:为了保证路由规则的永久稳定。

那如果我确实需要扩容怎么办?

这是一个非常实际的问题。如果你的索引真的因为数据增长需要更多的分片来分担压力,正确的做法不是去修改现有索引,而是使用 **Reindex API**

  1. 创建一个新的索引 (比如 products_v2),并为它设置一个更多的主分片数量(比如 6 个)。

  2. 使用 Reindex API,让 Elasticsearch 自动地、高效地将旧索引 (products) 中的所有数据读取出来,并重新写入到新索引 (products_v2) 中。

  3. 在重新写入的过程中,ES 会为每一条数据应用新的路由规则(比如 % 6),将它们正确地分布到新的 6 个分片中。

  4. 数据迁移完成后,你可以将你的应用程序指向新的索引 products_v2,然后安全地删除旧索引。

搜索的流程是什么?

具体来说,它分为几个关键步骤:

  1. 分析查询 (Analyze Query): 首先,Elasticsearch 会使用与索引时相同的分析器 (Analyzer) 来处理用户的查询语句。例如,用户搜索“华为手机”,分析器会将其处理成词元(Tokens) ["华为", "手机"]

  2. 查找词典 (Term Dictionary Lookup): 接着,系统会拿着处理好的每一个词元(“华为”、“手机”),去那个已经建好的、巨大的词典 (Term Dictionary) 中快速查找。

  3. 获取文档列表 (Posting List Retrieval): 词典本身不存储文档ID,但它会告诉系统去哪里找到包含了这个词元的文档列表 (Posting List)

  • 找到“华为” -> 获取到它的文档列表,比如 [Doc1, Doc5, Doc10]
  • 找到“手机” -> 获取到它的文档列表,比如 [Doc1, Doc8, Doc10]
  1. 合并与计算 (Merge & Calculate): 这是非常关键的一步。
  • 布尔逻辑: 系统会对这些文档列表进行布尔运算。对于“华为手机”这个查询,默认是 AND 逻辑,所以它会取两个列表的交集,得到最终匹配的文档ID:[Doc1, Doc10]
  • 相关性评分 (**_score**): 与此同时(对于 match 查询),ES 还会计算每个匹配文档的相关性分数。它会考虑词频(TF,词在一个文档里出现的次数)、逆文档频率(IDF,词在所有文档中是否罕见)等因素。比如,如果 Doc1 的标题就是“华为手机”,而 Doc10 的描述里只提了一句,那么 Doc1 的得分就会更高。
  1. 返回结果 (Return Results): 最后,系统根据 _score 从高到低进行排序,然后根据这些文档 ID 去获取完整的文档内容(_source),并将最终排好序的结果返回给用户。

总结一下,更精确的说法是:

  • 直接修改主分片数会导致找不到文档,所以 ES 禁止这样做。

  • 正确的扩容方式(Reindex)是一个“先建新,再搬家,最后换门牌”的过程,它通过创建全新的索引来应用新的路由规则,从而保证在任何时候都不会出现找不到文档的情况。

倒排索引 (Inverted Index) 的工作原理是什么?

倒排索引是 Elasticsearch 实现快速全文搜索的核心数据结构,它的核心思想是“词到文档”的映射。传统的关系型数据库是“文档到词”,即根据一条记录(文档)找到里面的内容(词),而倒排索引反了过来。

而像MySQL 和 MongoDB也是做某种“值到记录”的映射,但是跟ElasticSearch有一个根本性的区别,就是分词

MySQL / MongoDB 的标准索引 (通常是 B-Tree 索引)

  • 索引对象: 字段的 完整值 (Entire Value)

文章作者: Sean
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Sean !
  目录