忘机山人
本章导读:本章将深入介绍 Elasticsearch 的架构设计,包括集群架构、节点类型、数据存储原理等,帮助读者理解 ES 的内部工作机制。
┌─────────────────────────────────────────────────────────────────────────────┐
│ 客户端请求 │
│ (REST API / Transport Protocol) │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Elasticsearch 集群 │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 协调层 (Coordinating) │ │
│ │ 接收请求 → 路由到分片 → 聚合结果 → 返回响应 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────────┼──────────────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Master │ │ Data │ │ Ingest │ │
│ │ Node │ │ Node │ │ Node │ │
│ │ │ │ │ │ │ │
│ │ • 集群状态 │ │ • 数据存储 │ │ • 数据预处理│ │
│ │ • 索引管理 │ │ • 搜索执行 │ │ • Pipeline │ │
│ │ • 分片分配 │ │ • 聚合计算 │ │ │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 存储层 (Lucene) │ │
│ │ 倒排索引 / Doc Values / 段文件 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
| 组件 | 职责 |
|---|---|
| 协调层 | 接收客户端请求,路由到正确的分片,聚合各分片结果 |
| Master 节点 | 管理集群元数据,处理索引创建/删除,分片分配决策 |
| Data 节点 | 存储数据,执行数据相关操作(CRUD、搜索、聚合) |
| Ingest 节点 | 执行 Ingest Pipeline,在索引前预处理文档 |
| 存储层 | 基于 Lucene 的底层存储,管理倒排索引和段文件 |
Master 节点负责集群级别的管理操作:
职责:
配置:
# elasticsearch.yml
node.roles: [master]
最佳实践:
cluster.initial_master_nodes 防止脑裂# 专用 Master 节点配置
node.roles: [master]
cluster.initial_master_nodes: ["master-1", "master-2", "master-3"]
Data 节点负责数据存储和处理:
职责:
配置:
# elasticsearch.yml
node.roles: [data]
数据节点分层(8.x 新特性):
| 角色 | 说明 | 适用数据 |
|---|---|---|
data_hot | 热数据节点 | 最新、频繁访问的数据 |
data_warm | 温数据节点 | 较少访问的历史数据 |
data_cold | 冷数据节点 | 很少访问的归档数据 |
data_frozen | 冻结数据节点 | 极少访问,可搜索快照 |
data_content | 内容数据节点 | 非时序数据 |
# 热数据节点配置
node.roles: [data_hot]
# 温数据节点配置
node.roles: [data_warm]
协调节点(也称为客户端节点)专门处理请求路由:
职责:
配置:
# 专用协调节点(不设置任何角色)
node.roles: []
工作流程:
客户端请求
│
▼
┌─────────────────┐
│ Coordinating │
│ Node │
└────────┬────────┘
│ 路由请求
┌────┴────┬────────┐
▼ ▼ ▼
┌───────┐ ┌───────┐ ┌───────┐
│Shard 0│ │Shard 1│ │Shard 2│
└───┬───┘ └───┬───┘ └───┬───┘
│ │ │
└────┬────┴────────┘
│ 聚合结果
▼
┌─────────────────┐
│ Coordinating │
│ Node │
└────────┬────────┘
│
▼
客户端响应
Ingest 节点在文档索引前执行预处理:
职责:
配置:
# elasticsearch.yml
node.roles: [ingest]
Pipeline 示例:
PUT _ingest/pipeline/my-pipeline
{
"description": "处理日志数据",
"processors": [
{
"grok": {
"field": "message",
"patterns": ["%{IP:client_ip} - %{DATA:user} \\[%{HTTPDATE:timestamp}\\]"]
}
},
{
"date": {
"field": "timestamp",
"formats": ["dd/MMM/yyyy:HH:mm:ss Z"]
}
},
{
"geoip": {
"field": "client_ip"
}
}
]
}
机器学习节点执行 ML 任务:
职责:
配置:
# elasticsearch.yml
node.roles: [ml]
xpack.ml.enabled: true
小型集群(开发/测试):
# 所有节点都是全角色
node.roles: [master, data, ingest]
中型集群:
┌─────────────────────────────────────────────────────────┐
│ 3 x Master/Data 节点 │
│ node.roles: [master, data, ingest] │
└─────────────────────────────────────────────────────────┘
大型集群:
┌─────────────────────────────────────────────────────────┐
│ 3 x 专用 Master 节点 │
│ node.roles: [master] │
├─────────────────────────────────────────────────────────┤
│ N x 专用 Data 节点 │
│ node.roles: [data_hot] / [data_warm] / [data_cold] │
├─────────────────────────────────────────────────────────┤
│ 2+ x 协调节点 │
│ node.roles: [] │
├─────────────────────────────────────────────────────────┤
│ 2+ x Ingest 节点 │
│ node.roles: [ingest] │
└─────────────────────────────────────────────────────────┘
Elasticsearch 底层使用 Apache Lucene 作为搜索引擎库。每个分片本质上是一个 Lucene 索引。
┌─────────────────────────────────────────────────────────┐
│ Elasticsearch 分片 │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Lucene Index │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │Segment 0│ │Segment 1│ │Segment 2│ ... │ │
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
倒排索引是全文搜索的核心数据结构:
正向索引 vs 倒排索引:
正向索引(文档 → 词项):
┌─────────┬────────────────────────────┐
│ Doc ID │ Content │
├─────────┼────────────────────────────┤
│ 1 │ "Elasticsearch is fast" │
│ 2 │ "Elasticsearch is scalable"│
│ 3 │ "Lucene is fast" │
└─────────┴────────────────────────────┘
倒排索引(词项 → 文档):
┌───────────────┬─────────────────────────┐
│ Term │ Posting List │
├───────────────┼─────────────────────────┤
│ elasticsearch │ [1, 2] │
│ fast │ [1, 3] │
│ is │ [1, 2, 3] │
│ lucene │ [3] │
│ scalable │ [2] │
└───────────────┴─────────────────────────┘
倒排索引结构:
┌─────────────────────────────────────────────────────────┐
│ 倒排索引 │
├─────────────────────────────────────────────────────────┤
│ Term Dictionary (词项字典) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ elasticsearch → offset: 0 │ │
│ │ fast → offset: 100 │ │
│ │ scalable → offset: 200 │ │
│ └─────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Posting List (倒排列表) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Doc IDs: [1, 2] │ │
│ │ Term Frequency: [1, 1] │ │
│ │ Positions: [[0], [0]] │ │
│ │ Offsets: [[0-13], [0-13]] │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
Doc Values 是列式存储,用于排序、聚合和脚本访问:
倒排索引(搜索用): Doc Values(聚合/排序用):
Term → Doc IDs Doc ID → Field Value
"apple" → [1, 3] 1 → "apple"
"banana" → [2] 2 → "banana"
"cherry" → [4] 3 → "apple"
4 → "cherry"
Doc Values vs Fielddata:
| 特性 | Doc Values | Fielddata |
|---|---|---|
| 存储位置 | 磁盘 | 内存 |
| 构建时机 | 索引时 | 查询时 |
| 适用类型 | keyword, numeric, date, ip, geo | text |
| 内存占用 | 低 | 高 |
| 默认状态 | 启用 | 禁用 |
// 禁用 Doc Values(节省磁盘,但无法排序/聚合)
PUT /my-index
{
"mappings": {
"properties": {
"description": {
"type": "keyword",
"doc_values": false
}
}
}
}
段是 Lucene 索引的基本单位,是不可变的:
段的生命周期:
1. 文档写入内存缓冲区
┌─────────────────┐
│ Memory Buffer │ ← 新文档
└─────────────────┘
2. Refresh:内存缓冲区 → 新段(可搜索)
┌─────────────────┐
│ Segment 0 │ ← 可搜索
└─────────────────┘
3. 更多文档写入,创建更多段
┌─────────┐ ┌─────────┐ ┌─────────┐
│Segment 0│ │Segment 1│ │Segment 2│
└─────────┘ └─────────┘ └─────────┘
4. Merge:多个小段合并为大段
┌─────────────────────────────────┐
│ Merged Segment │
└─────────────────────────────────┘
段合并(Merge):
// 强制合并(谨慎使用,资源消耗大)
POST /my-index/_forcemerge?max_num_segments=1
_source 存储文档的原始 JSON:
// 禁用 _source(节省存储,但无法更新/reindex)
PUT /my-index
{
"mappings": {
"_source": {
"enabled": false
}
}
}
// 部分存储
PUT /my-index
{
"mappings": {
"_source": {
"includes": ["title", "date"],
"excludes": ["content"]
}
}
}
┌─────────────────────────────────────────────────────────────────┐
│ Lucene Segment │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Inverted Index │ │ Doc Values │ │ _source │ │
│ │ │ │ │ │ │ │
│ │ • Term Dict │ │ • 列式存储 │ │ • 原始 JSON │ │
│ │ • Posting List │ │ • 排序/聚合用 │ │ • 更新/高亮用 │ │
│ │ • 全文搜索用 │ │ │ │ │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Stored Fields │ │ Term Vectors │ │ Norms │ │
│ │ │ │ │ │ │ │
│ │ • 存储的字段值 │ │ • 词项位置信息 │ │ • 字段长度归一化│ │
│ │ │ │ • 高亮/MLT 用 │ │ • 评分用 │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
文档通过路由算法分配到特定分片:
shard_num = hash(routing) % number_of_primary_shards
默认 routing 值为文档 _id。
// 自定义路由
PUT /products/_doc/1?routing=user123
{
"name": "Product A",
"user_id": "user123"
}
// 查询时指定路由(提高效率)
GET /products/_search?routing=user123
{
"query": {
"match": { "user_id": "user123" }
}
}
1. 客户端发送写入请求到协调节点
2. 协调节点根据路由计算目标主分片
3. 请求转发到主分片所在节点
4. 主分片执行写入
5. 主分片并行复制到所有副本分片
6. 所有副本确认后,返回成功响应
┌────────┐ ┌─────────────┐ ┌─────────────┐
│ Client │────>│ Coordinating│────>│ Primary │
└────────┘ │ Node │ │ Shard │
└─────────────┘ └──────┬──────┘
│
┌───────────┼───────────┐
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐
│Replica │ │Replica │ │Replica │
│ 1 │ │ 2 │ │ 3 │
└────────┘ └────────┘ └────────┘
搜索分为两个阶段:Query Phase 和 Fetch Phase。
Query Phase:
1. 协调节点将请求发送到所有相关分片
2. 每个分片执行本地搜索,返回匹配文档的 ID 和排序值
3. 协调节点合并结果,确定最终的 Top N 文档
┌────────┐ ┌─────────────┐
│ Client │────>│ Coordinating│
└────────┘ │ Node │
└──────┬──────┘
│ Query
┌────────────┼────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Shard 0 │ │ Shard 1 │ │ Shard 2 │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
└────────────┼────────────┘
│ Doc IDs + Scores
▼
┌─────────────┐
│ Coordinating│
│ Node │
└─────────────┘
Fetch Phase:
1. 协调节点向包含目标文档的分片发送 Fetch 请求
2. 分片返回完整文档内容
3. 协调节点组装最终结果返回客户端
┌─────────────┐
│ Coordinating│
│ Node │
└──────┬──────┘
│ Fetch (Doc IDs)
┌────────────┼────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Shard 0 │ │ Shard 1 │ │ Shard 2 │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
└────────────┼────────────┘
│ Full Documents
▼
┌─────────────┐
│ Coordinating│──────> Client
│ Node │
└─────────────┘
集群状态(Cluster State)包含:
// 查看集群状态
GET /_cluster/state
// 查看集群状态大小
GET /_cluster/state?filter_path=metadata.indices.*.state
Elasticsearch 使用基于 Raft 的选举算法(7.0+):
┌─────────────────────────────────────────────────────────┐
│ 主节点选举流程 │
├─────────────────────────────────────────────────────────┤
│ │
│ 1. 节点启动,进入候选状态 │
│ 2. 候选节点向其他节点请求投票 │
│ 3. 获得多数票的节点成为主节点 │
│ 4. 主节点定期发送心跳 │
│ 5. 主节点失联,重新选举 │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Node 1 │ │ Node 2 │ │ Node 3 │ │
│ │(Master) │◄───│ │ │ │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │ ▲ ▲ │
│ │ 心跳 │ │ │
│ └──────────────┴──────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
防止脑裂:
# 配置初始主节点(仅首次启动时需要)
cluster.initial_master_nodes: ["node-1", "node-2", "node-3"]
本章深入介绍了 Elasticsearch 的架构原理:
理解这些架构原理有助于更好地设计和优化 Elasticsearch 应用。

评论
0暂无评论