ElasticSearch 知识
- ElasticSearch Database
1. Elasticsearch 概念与架构
- 集群(Cluster):Elasticsearch 中的多个节点(Node)组成集群。一个集群中有一个主节点(Master Node),负责集群管理,其它节点可能是数据节点(Data Node)、协调节点(Coordinating Node)、以及 Ingest 节点等。
- 节点(Node):每个 Elasticsearch 实例都被称为节点,节点存储数据并处理请求。节点可以有不同角色,如
master
、data
、ingest
等。 - 索引(Index):Elasticsearch 中数据的存储单位,每个索引包含多个分片(Shards),可以进行高效的存储与查询。
- 分片(Shard):每个索引会被分割成多个分片,分片是数据存储的基本单位,主分片和副本分片提供了数据的分布与冗余
2. 核心原理
1.1 倒排索引(Inverted Index)
- 原理:通过词项(Term)映射到文档表,实现快速全文检索
- 构建过程:文档分词→生成词项→记录词项所在文档及位置
- 优势:高效查询,支持复杂搜索(如布尔逻辑、短语匹配)
- 与正排索引对比:正排索引通过文档 ID 查找内容,倒排索引通过词项找文档
1.2 分布式架构
- 分片(Shard):
- 主分片(Primary)*:*处理写操作,数量创建索引时固定
- 副本分片(Replica):提供读高可用,数量可调整
- 节点角色:
- Master 节点:管理集群状态(如索引创建、节点上下线)
- Data 节点:存储分片,处理数据 CURD
- Coordinating 节点:路由请求,聚合结果(默认所有节点均可担任)
- Ingest 节点:
- 数据预处理
- 功能:通过定义 Pipeline(处理管道),对原始数据进行加工,例如:
- 解析结构:如将日志中的非结构化文本解析为结构化字段(使用
Grok
处理器)。 - 字段转换:如转换时间格式、大小写转、类型转换(字符串转数字)。
- 数据增强:如添加地理位置信息、基于 IP 生成 GeoIP 字段。
- 字段过滤:删除无用字段、重命名字段。
- 条件处理:根据字段内容动态决定是否执行某些操作。
- 解析结构:如将日志中的非结构化文本解析为结构化字段(使用
- 功能:通过定义 Pipeline(处理管道),对原始数据进行加工,例如:
- 减轻数据节点负载
- 职责分离:将计算密集型的预处理任务从 Data 节点剥离,避免影响索引和查询功能。
- 资源优化:专用 Ingest 节点可配置更高 CPU 资源,专注于数据处理。
- 简化架构
- 替代部分 Logstash 功能:在不需要复杂 ETL 的场景中,可直接通过 Elasticsearch 完成数据处理,减少外部组件依赖。
- 数据预处理
1.3 近实时(NRT,Near Real-Time)
- Refresh 机制:内存缓冲区数据每隔一秒(默认)生成新 Segment(文件系统缓存),实现可搜索
- Translog 持久化:写入操作记录到事务日志,定期 Flush 到磁盘保证数据安全
1.4 查询流程
- 客户端发起请求
- 请求类型:根据需求,客户端可能发起 Get 请求(通过 ID 获取文档)或 Search 请求(复杂查询)
- 目标节点:客户端将请求发送到集群中的任意节点,该节点默认担任协调节点(Coordinating Node)
- 请求路由与分片定位
- Get by ID
- 协调节点通过路由算法(如
hash(_id) % number_of_shards
)确定文档所在分片 - 直接向该分片的主分片或副本分片发送请求(副本分片负载均衡)
- 协调节点通过路由算法(如
- Search 请求
- 若查询未指定路由,协调节点将请求广播到索引的所有分片(主分片或副本分片)
- 若指定路由参数,仅查询关联的分片
- Get by ID
- 分片本地查询(Query Phase)
- 分片处理:
- 每个分片在本地执行查询,使用倒排索引快速匹配词项
- 对于搜索请求,分片返回匹配文档的 ID 和排序值(如相关性评分)
- 分片可能使用缓存(如分片请求缓存)加速查询
- 结果返回:
- 各分片返回本地排序后的 Top N 结果(N 有
size
参数决定)
- 各分片返回本地排序后的 Top N 结果(N 有
- 分片处理:
- 全局结果合并(Coordinating Node)
- 排序与筛选:
- 协调节点收集所有分片的中间结果,按全局排序规则(如相关性评分)合并
- 筛选最终** Top N 文档的 ID 列表 **(例如用户请求的
size=10
)
- 分页处理:
- 深度分页优化:若使用
from + size
,协调节点需要合并所有分片的from + size
条数据,内存开销大。推荐使用 Search After 或 Scroll API 替代
- 深度分页优化:若使用
- 排序与筛选:
- 获取文档详情(Fetch Phase)
- 二次请求:
- 协调节点根据筛选后的文档 ID,向对应分片发送 Multi-Get 请求获取完整文档内容
- 分片返回文档的原始数据(如
_source
字段)
- 结果聚合:
- 协调节点将最终结果组装,返回给客户端
- 二次请求:
- 实时性处理
- 新写入文档可见性:
- 搜索请求:依赖 Refresh 机制(默认1秒生成新 Segment),未刷新前不可被搜索
- Get 请求:通过实施读取 Translog 可立即获取最新文档,无需等待 Refresh
- 新写入文档可见性:
- 容错与负载均衡
- 副本分片利用:
- 读请求优先分发到副本分片,分担主分片压力,提升吞吐量
- 故障恢复:
- 若某分片不可用,协调节点自动重试其它副本分片
- 副本分片利用:
- 缓存机制优化
- 分片请求缓存:
- 缓存聚合结果或频繁查询的响应(仅对不含
now
或动态参数的查询生效)
- 缓存聚合结果或频繁查询的响应(仅对不含
- 字段数据缓存:
- 用于排序和聚合的字段数据缓存在内存,加速计算
- 分片请求缓存:
1.5 写入流程
- 客户端发起写入请求
- 请求类型:可以是单文档写入(
PUT /index/_doc/1
)、批量写入(Bulk API
)或更新/删除操作 - 协调节点选择:客户端将请求发送到任意节点(默认作为协调节点)
- 请求类型:可以是单文档写入(
- 路由计算与主分片定位
- 路由规则:
- 若指定文档 ID,使用哈希算法:
hash(_id) % number_of_shards
确定目标分片 - 若未指定 ID(自动生成 ID),Elasticsearch 会随机选择主分片
- 若指定文档 ID,使用哈希算法:
- 分片定位:协调节点根据路由结果,将请求转发到该主分片所在的** Data 节点**
- 路由规则:
- 主分片写入处理
3.1 写入内存缓冲去
- 内存缓冲区(Index Buffer):
- 文档首先被写入主分片的内存缓冲区
- 此时数据不可被搜索(尚未生成 Segment)
- 分词与索引构建:
- 对文档字段进行分词(根据 mapping 定义的分词器)
- 生成倒排序索引的临时数据结构
3.2 写入 Translog (事务日志)
- Translog 作用:确保数据未持久化到磁盘前不会丢失
- 所有写入操作会同步追加到 Translog
- Translog 默认每次请求后刷新(
index.translog.durability: request
),保证可靠性 - 可配置为异步刷新(
async
)以提高性能,但可能丢失部分数据
3.3 副本分片同步(Replication)
- 同步模式:
- 主分片将写入操作并行发送到所有副本分片
- 副本分片执行相同的写入流程(写入内存缓冲区 + Translog)
- 写入确认条件:
- 默认要求多数分片成功(
wait_for_active_shards: quorum
) - 若副本分片写入失败,主分片会重试,最终可能标记副本分片为失效
- 默认要求多数分片成功(
- 返回客户端响应
- 成功条件:
- 主分片和副本分片均为完成内存和 Translog 写入后,协调节点向客户端返回
acknowledged: true
- 主分片和副本分片均为完成内存和 Translog 写入后,协调节点向客户端返回
- 失败处理:
- 若副本分片不可用,主分片仍会写入,但集群状态变为
yellow
(副本未分配) - 客户端可能收到
ShardFailureException
(需重试或处理异常)
- 若副本分片不可用,主分片仍会写入,但集群状态变为
- 近实时搜索实现(Refresh)
- Refresh机制:
- 默认每个1秒(
index.refresh_interval
),内存缓冲区的数据会生成新的 Lucene Segment - Segment 写入文件系统缓存(非磁盘),此时数据可被搜索
- Refresh 操作开销较大,高频写入场景可适当调大间隔(如30秒)
- 默认每个1秒(
- 数据持久化(Flush)
- Flush 触发条件:
- Translog 大小达到阈值(默认512MB,
index.translog.flush_threshold_size
) - 时间间隔(默认30分钟,
index.translog.sync_interval
) - 手动调用
_flush
API
- Translog 大小达到阈值(默认512MB,
- 持久化过程:
- 清空内存缓冲区,将所有 Segment 持久化到磁盘
- 清空当前 Translog,生成新的 Translog 文件
- Segment 只读:写入磁盘后不再修改,删除操作通过
.del
文件标记
- Segment 合并(Merge)
- 后台优化:
- 多个 Segment 会被合并为更大的 Segment,提升查询性能
- 合并过程自动触发,删除过期文档(如标记为删除的文档)
- 合并期间可能占用较多I/O和 CPU 资源
- 容错与恢复机制
- 节点宕机恢复:
- 重启后,通过重放 Translog 恢复未持久化的数据
- 若主分片宕机,集群选举新的主分片(优先选择最新数据的副本)
- 数据一致性:
- 使用
_seq_no
和_primary_term
保证写入顺序和冲突解决 - 写入时可通过
version
参数实现乐观锁控制
- 使用
3. 倒排索引的底层实现
3.1 核心数据结构
- 词典(Term Dictionary):
- 使用** FST(有限状态转换机)** 压缩存储词项,减少内存占用(Lucene 6.0 后默认)。
- 支持快速前缀查询(如通配符
*
),但需权衡内存与查询性能。
- 倒排列表(Postings List):
- DocID 列表:使用差值编码(Delta Encoding)和 **Frame Of Reference(FOR)**压缩。
原始ID:100, 200, 300 → 差值:100, 100, 100 → 压缩为单值100 + 重复次数。
- 词频(TF)与位置(Position):使用**位压缩(Bit Packing)和游程编码(RLE)**。
3.2 倒排索引的局限性
- 更新代价高:文档修改需要重建索引,适合读多写少场景。
- 内存压力:字段数据缓存(如排序字段)可能引发堆内存溢出(需结合
doc_values
优化)。
4. 分布式设计的深度剖析
4.1 分片策略的权衡
- 分片数选择:
- 过少:单分片数据量大,扩容困难,写入易成瓶颈。
- 过多:元数据管理开销大(每个分片是独立 Lucence 索引),查询合并成本高。
- 黄金法则:单个分片大小建议在** 10-50GB **,结合数据增长速率设计。
- 路由优化:
- 自定义路由:指定
routing
参数将相关文档存入同一分片,提升查询效率。
- 自定义路由:指定
PUT /logs/_doc/1?routing=node-1
{ "message": "error in node-1" }
- 弊端:可能导致数据倾斜(如某个路由值数据量过大)
4.2 分布式一致性模型
- 写入一致性:
- 参数:
wait_for_active_shards
(默认为quorum
,即(主分片数+副本数)/ 2 + 1)
。 - 场景:若副本分片故障,主分片写入成功后仍返回成功,但集群状态为
yellow
。
- 参数:
- 读一致性:
- 默认最终一致性:主分片写入成功后,副本分片可能短暂落后。
- 强制读主分片:设置
preference=_primary
,但牺牲性能。
4.3 脑裂问题与防范
- 原因:网络分区导致多个 Master 节点被选举。
- 解决方案:
discovery.zen.minimun_master_nodes
(ES 7.x前):设置为(master 节点数 / 2)+ 1
。
5. 查询流程深度优化
5.1 Query 与 Filter 的执行差异
- Query 上下文
- 计算相关性评分(
score
),无法缓存,适合全文搜索 - 代价:频繁访问到排序索引,CPU 消耗高。
- 计算相关性评分(
- Filter 上下文
- 仅判断是否匹配,结果可缓存(使用
bitset
),适合精准过滤。 - 优化技巧:将范围查询、
term
查询至于filter
中。
- 仅判断是否匹配,结果可缓存(使用
5.2 聚合(Aggregation)的底层实现
- 全局排序限制:
terms
聚合默认返回每个分片的 Top 结果再合并,可能导致精度损失。- 解决方案:设置
size: 10000
+shard_size: 50000
(增加分片级计算量)。
- 内存控制:
- 聚合结果构建在堆内存中,需监控
fielddata
使用(避免CircuitBreaker
触发)。
- 聚合结果构建在堆内存中,需监控
5.3 查询性能优化
- 索引设计:
- 使用
keyword
类型替代text
,避免分词开销。 - 禁用
_all
字段(ES 6.0+默认禁用,替换为copy_to
)。
- 使用
- 查询重写
- 布尔查询顺序:高选择性条件(如
term
)放在前面,减少后续计算量。 - 分页替代方案:
- Search After:利用上一页的排序值(如
@timestamp
)避免深分页。 - Scroll API:创建快照上下文,适合离线导出(但消耗资源)
- Search After:利用上一页的排序值(如
- 布尔查询顺序:高选择性条件(如
6. 高级调优参数
参数 | 作用 | 推荐值 |
---|---|---|
indices.memory.index_buffer_size | 控制索引缓冲区大小(堆内存百分比) | 10%(默认) |
index.refresh_interval | 刷新间隔,影响搜索实时性 | 30s (写入优化场景) |
index.translog.flush_threshold_size | Translog刷盘阈值 | 1gb (高吞吐场景) |
index.merge.scheduler.max_thread_count | 合并线程数(需 SSD 支持) | 4 (默认) |
search.max_buckets | 聚合返回的最大桶数 | 100000 (大数据聚合场景) |
6. 架构运行相关流程图
快速问答
- Translog 如何保证数据安全?
- Translog 使用追加写入(Append-Only)模式,避免随机写磁盘的性能问题
- 通过 fsync 系统调度强制刷盘(依赖
index.translog.durability
配置) - 崩溃恢复:节点重启时,通过 Translog 重放未持久化到 Lucene Segment 的操作
- 源码关联:
org.elasticsearch.index.translog.Translog
类处理日志的写入与恢复 - 与 Lucene Commit 的关系:Flush 操作会将内存数据生成 Segment 并刷盘,同时清除已提交的 Translog
- 使用 Bulk API、调大 Refresh 间隔
- 实战案例:在日志系统中,将
refresh_interval
设为30s
,写入吞吐量提升 3 倍,但需接受搜索延迟 - 参数调优:调整
indices.memory.index_buffer_size
(默认 10% 堆内存)增加索引缓冲区 - 硬件影响:使用 SSD 减少 Flush 耗时,避免写入瓶颈
- 监控工具:通过 Kibana Monitoring 观察
indexing_rate
和merge_time
,针对性优化
- 实战案例:在日志系统中,将
- Elasticsearch 和关系型数据库的写入区别
- 数据模型:ES 时 Schema-less 的文档模型,支持动态字段;关系数据库需预定义 Schema
- 事务性:ES 不支持 ACID 事务,依赖版本号(
_version
)实现乐观锁 - 写入延迟:ES 的 NRT(近实时)vs 数据库的实时可见
- Refresh 机制对写入性能的影响
- Refresh 操作将内存缓冲区中的数据生成新的 Lucene Segment,写入文件系统缓存(非磁盘),使数据可被搜索)
- 频繁 Refresh 会导致产生大量小 Segment,增加 Merge 开销(I/O 和 CPU 竞争)
- 日志数据:设置
refresh_interval: 30s
甚至-1
(关闭自动 Refresh),通过手动 Refresh 控制搜索可见性 - 高查询压力:避免长时间不 Refresh,否则新数据无法被搜索
- 监控指标:通过
_nodes/states/indices/
API 观察refresh_tatol
和refresh_time_in_millis
,评估 Refresh 开销
- 主分片与副本分片的写入同步
- 主分片写入成功后,会并行向所有副本分片发送写入请求
- 副本分片写入成功后,主分片才向客户端返回确认
wait_for_active_shards: quorm
(默认):多数分片(主 + 副本)可用时写入成功wati_for_active_shards: 1
:仅主分片成功即可(风险:副本可能落后)- 若副本分片写入失败,主分片会向 Master 节点报告,触发分片重分配
- 集群状态变为
Yellow
,直到副本恢复
- 设计一个支持亿级日志写入的系统,你会如何规划 ES 集群?
- 分片设计:按时间滚动索引(如
logs-2023-10
),单个分片大小控制在 50GB 以内 - 写入优化:使用 Bulk API,客户端侧实现批量压缩与重试机制
- 硬件规划:独立 Ingest 节点处理数据预处理,Data 节点使用 NVMe SSD
- 可靠性:设置
index.translog.durability: async
,副本数设为 1 - 监控:通过 ILM(索引生命周期管理)自动归档旧索引到冷存储
- 分片设计:按时间滚动索引(如
- 为什么 Elasticsearch 查询比数据库快?
- 倒排序索引:快速定位文档,避免全表扫描。
- 分布式并行:查询拆分到多个分片并行执行。
- 文件系统缓存:Segment 文件缓存在 OS Cache,减少磁盘 IO。
- 列式存储(
doc_values
):优化排序与聚合。
- 如何处理数据一致性问题?
- 写入一致性:通过
wait_for_active_shards
控制最小成功分片数。 - 版本控制:
- 使用
_version
实现乐观锁,避免并发覆盖。 - 外部版本号(
version_type=external
)兼容数据库同步。
- 使用
- 读一致性:
preference=_primary
:强制读主分片(强一致性,性能低)。replication=async
:异步副本更新(最终一致性,性能高)。
- 写入一致性:通过
- 如何设计一个高可用 ES 集群?
- 节点角色分离:
- 专用 Master 节点(3台,避免脑裂)。
- 独立 Data 节点、Ingest 节点、Coordination 节点。
- 分片策略:
- 副本数≥1,跨机架分布(
awareness
参数)。 - 使用ILM(索引生命周期管理)自动滚动索引。
- 副本数≥1,跨机架分布(
- 容灾备份:
- 快照(Snapshot)到 S3/NFS,支持跨集群恢复。
- 多集群同步(CCR,跨集群复制)。
- 节点角色分离: