什么是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 (副本)
是什么: 分片的一份或多份拷贝。
类比: 数据库的主从复制中的“从库”。
解释: 副本的主要作用有两个,我们之前也提到过:
- 高可用性 (High Availability): 副本和它的主分片永远不会被分配在同一个节点上。如果持有主分片的节点宕机,ES 会立即将一个副本“提升”为新的主分片,保证服务不中断,数据不丢失。
- 提升读性能 (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:你创建索引时设定的主分片数量。
举个例子:
你创建了一个
products索引,并设定它有 3 个主分片。现在,你要存入一个新商品文档,它的
_id是 “A-123-B-456”。ES 会对 “A-123-B-456” 这个
_id进行哈希计算,假设得到一个数字2096。然后,ES 计算
2096 % 3,结果是2。结论: ES 就知道了,这个文档必须被存到主分片2 (Shard 2) 上。
下次你根据这个 _id 来获取或更新这个文档时,ES 会重复一遍完全相同的计算,再次得到 2,然后直接去主分片2上找,而不需要去问分片0和分片1,效率极高。
现在,我们来理解第二部分,也是最关键的部分:
“主分片的数量在索引创建时就必须固定,之后不能修改”,为什么不能修改?
我们继续用上面的例子。假设 ES 允许你把主分片数量从 3 修改成 4。
现在,你还是想找那个 _id 是 “A-123-B-456” 的文档。ES 再次执行路由计算:
hash("A-123-B-456")的结果依然是2096。但是,现在主分片数量变成了 4,所以公式变成了
2096 % 4。计算结果是
**0**!
灾难发生了!
ES 现在认为这个文档应该在主分片0 (Shard 0) 上。但实际上,它当初被存放在了主分片2上。ES 去分片0上找,结果肯定是“找不到”。
如果你修改了主分片的数量,所有之前文档的路由规则就全部失效了,整个索引的数据就陷入了彻底的混乱,你将无法通过 _id 定位到任何一个文档。
这就是为什么主分片数量一旦设定,就不能再修改的根本原因:为了保证路由规则的永久稳定。
那如果我确实需要扩容怎么办?
这是一个非常实际的问题。如果你的索引真的因为数据增长需要更多的分片来分担压力,正确的做法不是去修改现有索引,而是使用 **Reindex API**:
创建一个新的索引 (比如
products_v2),并为它设置一个更多的主分片数量(比如 6 个)。使用
Reindex API,让 Elasticsearch 自动地、高效地将旧索引 (products) 中的所有数据读取出来,并重新写入到新索引 (products_v2) 中。在重新写入的过程中,ES 会为每一条数据应用新的路由规则(比如
% 6),将它们正确地分布到新的 6 个分片中。数据迁移完成后,你可以将你的应用程序指向新的索引
products_v2,然后安全地删除旧索引。
搜索的流程是什么?
具体来说,它分为几个关键步骤:
分析查询 (Analyze Query): 首先,Elasticsearch 会使用与索引时相同的分析器 (Analyzer) 来处理用户的查询语句。例如,用户搜索“华为手机”,分析器会将其处理成词元(Tokens)
["华为", "手机"]。查找词典 (Term Dictionary Lookup): 接着,系统会拿着处理好的每一个词元(“华为”、“手机”),去那个已经建好的、巨大的词典 (Term Dictionary) 中快速查找。
获取文档列表 (Posting List Retrieval): 词典本身不存储文档ID,但它会告诉系统去哪里找到包含了这个词元的文档列表 (Posting List)。
- 找到“华为” -> 获取到它的文档列表,比如
[Doc1, Doc5, Doc10]。 - 找到“手机” -> 获取到它的文档列表,比如
[Doc1, Doc8, Doc10]。
- 合并与计算 (Merge & Calculate): 这是非常关键的一步。
- 布尔逻辑: 系统会对这些文档列表进行布尔运算。对于“华为手机”这个查询,默认是 AND 逻辑,所以它会取两个列表的交集,得到最终匹配的文档ID:
[Doc1, Doc10]。 - 相关性评分 (
**_score**): 与此同时(对于match查询),ES 还会计算每个匹配文档的相关性分数。它会考虑词频(TF,词在一个文档里出现的次数)、逆文档频率(IDF,词在所有文档中是否罕见)等因素。比如,如果 Doc1 的标题就是“华为手机”,而 Doc10 的描述里只提了一句,那么 Doc1 的得分就会更高。
- 返回结果 (Return Results): 最后,系统根据
_score从高到低进行排序,然后根据这些文档 ID 去获取完整的文档内容(_source),并将最终排好序的结果返回给用户。
总结一下,更精确的说法是:
直接修改主分片数会导致找不到文档,所以 ES 禁止这样做。
正确的扩容方式(Reindex)是一个“先建新,再搬家,最后换门牌”的过程,它通过创建全新的索引来应用新的路由规则,从而保证在任何时候都不会出现找不到文档的情况。
倒排索引 (Inverted Index) 的工作原理是什么?
倒排索引是 Elasticsearch 实现快速全文搜索的核心数据结构,它的核心思想是“词到文档”的映射。传统的关系型数据库是“文档到词”,即根据一条记录(文档)找到里面的内容(词),而倒排索引反了过来。
而像MySQL 和 MongoDB也是做某种“值到记录”的映射,但是跟ElasticSearch有一个根本性的区别,就是分词。
MySQL / MongoDB 的标准索引 (通常是 B-Tree 索引)
- 索引对象: 字段的 完整值 (Entire Value)。