🔍Elasticsearch 入门:分布式搜索引擎核心概念
约 2373 字大约 8 分钟
ElasticsearchSearchLucene全文搜索
2026-05-26
Elasticsearch(简称 ES)是一个基于 Apache Lucene 的分布式搜索与分析引擎,由 Java 编写,常用于全文搜索、日志分析、安全分析、应用性能监控等场景。
它的核心能力可以概括为三个词:存得下、查得快、说得清。
关系型数据库做不到的事
在进入 ES 之前,先理解为什么需要它。
MySQL 这类关系型数据库用 B+ 树 做索引,擅长精确查找(WHERE id = ?)和区间查找(WHERE age BETWEEN 18 AND 30),但面对以下场景就力不从心:
| 场景 | MySQL 的问题 |
|---|---|
| 在十万篇文档中搜"分布式一致性" | LIKE '%分布式%' 全表扫描,命中不准 |
| "江呃湖" 应该匹配到"江泽民" | 拼音错误、拼写变体无法处理 |
| 搜索结果按品牌/价格/评分多维筛选 | 每个 facet 都要单独 COUNT,实时计算成本极高 |
| 每秒写入 10 万条日志并实时检索 | 频繁写入时 B+ 树维护成本高,查询吞吐受限 |
这些问题指向同一个根本原因:MySQL 的索引结构不适合模糊匹配和全文检索。
倒排索引:ES 快的本质
ES 采用 倒排索引(Inverted Index) 作为核心数据结构,这是它和 MySQL 本质的区别。
正排索引 vs 倒排索引
假设有四本书:
| 文档ID | 书名 |
|---|---|
| Doc 1 | 分布式系统设计 |
| Doc 2 | 分布式数据库原理 |
| Doc 3 | 高可用架构实践 |
| Doc 4 | 数据库系统概论 |
正排索引(传统数据库的思路):文档 → [词1, 词2, 词3]
| 文档 | 包含的词 |
|---|---|
| Doc 1 | 分布式、系统、设计 |
| Doc 2 | 分布式、数据库、原理 |
| Doc 3 | 高可用、架构、实践 |
| Doc 4 | 数据库、系统、概论 |
要搜"分布式",需要扫描所有文档的词列表,O(n)。
倒排索引:建反向映射 词 → [文档列表]
| 词 | 出现在哪些文档 |
|---|---|
| 分布式 | [Doc 1, Doc 2] |
| 数据库 | [Doc 2, Doc 4] |
| 系统 | [Doc 1, Doc 4] |
| 架构 | [Doc 3] |
搜"分布式"直接命中 [Doc 1, Doc 2],O(1) 定位。倒排索引是 ES 查询快的核心原因。
倒排索引的内部结构
实际上倒排索引由两部分组成:
- Term Dictionary(单词词典):所有文档切分出来的词项集合,按排序存放,支持二分查找 O(log n)
- Posting List(倒排列表):每个词对应的文档 ID 列表,还包含词频(TF)、位置、偏移等元数据
但如果 Term Dictionary 很大(百万级词项),全部放磁盘会导致多次磁盘 IO。Lucene 在这之间加了一层 Term Index,结构上使用 FST(Finite State Transducer,有限状态传感器)——一种类似前缀树的结构,把有公共前缀的词项压缩成一个 block,只在内存中存 block 首地址。
查询时先通过 FST 在内存中定位 Term Dictionary 的位置区间,再二分查到 Posting List,整个过程通常只需要 1-2 次磁盘 IO。
这就是为什么 ES 的倒排索引查询能这么快——Term Index 在堆内存中占 50%~70%,是生产环境中可以重点调优的地方。
分词器:决定搜索质量的第一步
倒排索引的质量直接由分词器(Analyzer)决定。分词器分三步处理输入文本:
字符过滤器(Character Filter)
按顺序过一遍:HTML 标签移除、敏感词过滤、自定义字符映射。比如 "<b>分布式</b>" → "分布式"。
分词器(Tokenizer)
按规则切分成词项(Token)。常见策略:
- standard:按空格和标点切分,中文按单字切分("分布式" → "分"、"布"、"式")
- ik_max_word:中文最细粒度分词("分布式系统" → "分布式"、"系统"、"分布"、"式")
- ik_smart:中文最粗粒度("分布式系统" → "分布式"、"系统")
对于中文搜索,ik 分词器几乎是必装的。
词过滤器(Token Filter)
对每个词项做标准化:小写转 lowercase、移除停用词(the、a)、同义词展开("电脑" → "计算机")、词干提取("running" → "run")。
text vs keyword:两种字段类型的区别
{
"mappings": {
"properties": {
"title": { "type": "text", "analyzer": "ik_max_word" },
"author": { "type": "keyword" },
"pubYear": { "type": "integer" },
"tags": { "type": "keyword" }
}
}
}- text 类型:分词后建倒排索引,适合模糊匹配(如文章正文、标题)
- keyword 类型:不分词,整个字符串作为精确词项,适合精确匹配(如作者名、标签、ISBN)
搜 "author": "Martin" 搜不到任何结果,因为 keyword 字段不会对 "Martin Kleppmann" 做分词,"Martin" 根本不是词典里的词项。正确写法是 "term": { "author": "Martin Kleppmann" }。
分片与副本:水平扩展的基础
ES 是分布式系统,数据分布在多个节点上,理解分片(Shard)和副本(Replica)是理解 ES 架构的关键。
为什么要分片
单节点存储有上限,而且存在单点故障风险。ES 将一个索引横向切分成多个主分片(Primary Shard),每块数据独立存储在不同节点上,实现水平扩展。
一个索引默认 1 主分片 1 副本,共 2 个分片:
books(索引)
├── Shard 0(Primary)→ Node A
│ └── Replica 0 → Node B
├── Shard 1(Primary)→ Node B
│ └── Replica 0 → Node C
└── Shard 2(Primary)→ Node C
└── Replica 0 → Node A分片设置的经验值
官方建议每个分片的数据量控制在 10~50 GB,文档数量不超过 2 亿条。
分片过大的问题:故障恢复时数据迁移慢,查询时打分不准(数据分布不均匀);分片过多的问题:开销大,资源利用率低,管理复杂度上升。
如果预估数据量 300GB,设置 5~6 个主分片比较合理,后续无法修改,所以要在建索引前规划好。
主分片 vs 副本分片的行为差异
写入流程:先到主分片,成功后异步复制到所有副本,只有主副本都写入成功才算完成。
查询流程:主分片和副本分片都可以响应查询请求,ES 会负载均衡到任意一个。
如果某个节点挂了,副本会被选举升级为主分片,保证集群可用——这也要求至少 n+1 个节点才能支撑 n 个副本。
查询 DSL:从单字段到布尔组合
ES 提供了一套完整的 JSON 查询语法,叫 Query DSL。
精确查询
// keyword 字段精确匹配(不分词)
GET /books/_search
{
"query": {
"term": { "author": "Martin Kleppmann" }
}
}
// 数值/日期范围查询
GET /books/_search
{
"query": {
"range": {
"pubYear": { "gte": 2015, "lte": 2020 }
}
}
}全文搜索
GET /books/_search
{
"query": {
"match": {
"title": "distributed database"
}
}
}match 会对查询词做分词("distributed database" → ["distributed", "database"]),分别查倒排索引后按 TF-IDF 权重合并得分。
布尔组合
GET /books/_search
{
"query": {
"bool": {
"must": [{ "match": { "title": "distributed" } }],
"should": [{ "match": { "content": "database" } }],
"filter": [{ "range": { "pubYear": { "gte": 2015 } } }]
}
}
}| 子句 | 含义 | 计分? |
|---|---|---|
must | 必须匹配 | ✓(影响得分) |
should | 有则加分,无不扣分 | ✓ |
filter | 必须匹配 | ✗(不参与打分,缓存友好) |
must_not | 必须不匹配 | ✗ |
聚合分析
ES 的聚合不只能计数,还能做统计、分桶、嵌套聚合,一个请求返回多维度结果:
GET /books/_search
{
"size": 0,
"aggs": {
"year_distribution": {
"terms": { "field": "pubYear", "size": 20 }
},
"avg_year": {
"avg": { "field": "pubYear" }
}
}
}返回每年出版书籍数量 Top 20,以及出版年份平均值——不需要额外请求。
适用场景与不适用场景
适合的场景
- 全文检索:站内搜索、文档中心、日志关键词检索
- 日志分析(ELK Stack):Elasticsearch + Logstash + Kibana,基础设施监控标配
- 应用性能监控(APM):分布式请求链路追踪
- 安全分析(SIEM):基于 ES 的安全事件分析平台
不适合的场景
- 强事务需求:ES 做不到 ACID 事务,擅长最终一致性,不适合金融转账类场景
- 复杂关联查询:join 能力弱,数据模型需要反规范化(denormalization),把关联数据打平到一个文档里
- 低延迟简单读取:纯按 ID 取记录,Redis/Cassandra 更快
理解边界比学会用它更重要——ES 是搜索和分析场景的首选,但不等于万能数据库。
生产环境几个常见的调优点
- heap 内存分配:ES 堆内存中 50%~70% 被 FST Term Index 占用,官方建议不超过 32GB(避免指针压缩失效),数据量大时优先扩容磁盘而非压缩堆内存
- 分片数规划:在创建索引前预估数据量,设置合理的分片数,避免事后头疼
- 冷热分离:时序数据(最新日志最热)建议用 Rollover Index API 定期建新索引,老数据索引可以
force merge+shrink减少段数量降低资源占用 _search?explain:排查排序问题时加上这个参数,能看到每条命中的得分构成- 副本数权衡:副本越多可查询节点越多,但写入延迟增加,在写入压力大的场景可以先设为 0,后续再调
