ElasticSearch 知识

1. Elasticsearch 概念与架构

  • 集群(Cluster):Elasticsearch 中的多个节点(Node)组成集群。一个集群中有一个主节点(Master Node),负责集群管理,其它节点可能是数据节点(Data Node)、协调节点(Coordinating Node)、以及 Ingest 节点等。
  • 节点(Node):每个 Elasticsearch 实例都被称为节点,节点存储数据并处理请求。节点可以有不同角色,如masterdataingest 等。
  • 索引(Index):Elasticsearch 中数据的存储单位,每个索引包含多个分片(Shards),可以进行高效的存储与查询。
  • 分片(Shard):每个索引会被分割成多个分片,分片是数据存储的基本单位,主分片和副本分片提供了数据的分布与冗余

2. 核心原理

1.1 倒排索引(Inverted Index)

  • 原理:通过词项(Term)映射到文档表,实现快速全文检索
    • 构建过程:文档分词→生成词项→记录词项所在文档及位置
    • 优势:高效查询,支持复杂搜索(如布尔逻辑、短语匹配)
  • 与正排索引对比:正排索引通过文档 ID 查找内容,倒排索引通过词项找文档

1.2 分布式架构

  • 分片(Shard):
    • 主分片(Primary)*:*处理写操作,数量创建索引时固定
    • 副本分片(Replica):提供读高可用,数量可调整
  • 节点角色
    • Master 节点:管理集群状态(如索引创建、节点上下线)
    • Data 节点:存储分片,处理数据 CURD
    • Coordinating 节点:路由请求,聚合结果(默认所有节点均可担任)
    • Ingest 节点
      1. 数据预处理
        • 功能:通过定义 Pipeline(处理管道),对原始数据进行加工,例如:
          • 解析结构:如将日志中的非结构化文本解析为结构化字段(使用Grok 处理器)。
          • 字段转换:如转换时间格式、大小写转、类型转换(字符串转数字)。
          • 数据增强:如添加地理位置信息、基于 IP 生成 GeoIP 字段。
          • 字段过滤:删除无用字段、重命名字段。
          • 条件处理:根据字段内容动态决定是否执行某些操作。
      2. 减轻数据节点负载
        • 职责分离:将计算密集型的预处理任务从 Data 节点剥离,避免影响索引和查询功能。
        • 资源优化:专用 Ingest 节点可配置更高 CPU 资源,专注于数据处理。
      3. 简化架构
        • 替代部分 Logstash 功能:在不需要复杂 ETL 的场景中,可直接通过 Elasticsearch 完成数据处理,减少外部组件依赖。

1.3 近实时(NRT,Near Real-Time)

  • Refresh 机制:内存缓冲区数据每隔一秒(默认)生成新 Segment(文件系统缓存),实现可搜索
  • Translog 持久化:写入操作记录到事务日志,定期 Flush 到磁盘保证数据安全

1.4 查询流程

  1. 客户端发起请求
    • 请求类型:根据需求,客户端可能发起 Get 请求(通过 ID 获取文档)或 Search 请求(复杂查询)
    • 目标节点:客户端将请求发送到集群中的任意节点,该节点默认担任协调节点(Coordinating Node)
  2. 请求路由与分片定位
    • Get by ID
      • 协调节点通过路由算法(如hash(_id) % number_of_shards )确定文档所在分片
      • 直接向该分片的主分片或副本分片发送请求(副本分片负载均衡)
    • Search 请求
      • 若查询未指定路由,协调节点将请求广播到索引的所有分片(主分片或副本分片)
      • 若指定路由参数,仅查询关联的分片
  3. 分片本地查询(Query Phase)
    • 分片处理
      • 每个分片在本地执行查询,使用倒排索引快速匹配词项
      • 对于搜索请求,分片返回匹配文档的 ID 和排序值(如相关性评分)
      • 分片可能使用缓存(如分片请求缓存)加速查询
    • 结果返回
      • 各分片返回本地排序后的 Top N 结果(N 有size 参数决定)
  4. 全局结果合并(Coordinating Node)
    • 排序与筛选
      • 协调节点收集所有分片的中间结果,按全局排序规则(如相关性评分)合并
      • 筛选最终** Top N 文档的 ID 列表 **(例如用户请求的 size=10 )
    • 分页处理
      • 深度分页优化:若使用from + size ,协调节点需要合并所有分片的from + size 条数据,内存开销大。推荐使用 Search AfterScroll API 替代
  5. 获取文档详情(Fetch Phase)
    • 二次请求
      • 协调节点根据筛选后的文档 ID,向对应分片发送 Multi-Get 请求获取完整文档内容
      • 分片返回文档的原始数据(如_source 字段)
    • 结果聚合
      • 协调节点将最终结果组装,返回给客户端
  6. 实时性处理
    • 新写入文档可见性
      • 搜索请求:依赖 Refresh 机制(默认1秒生成新 Segment),未刷新前不可被搜索
      • Get 请求:通过实施读取 Translog 可立即获取最新文档,无需等待 Refresh
  7. 容错与负载均衡
    • 副本分片利用
      • 读请求优先分发到副本分片,分担主分片压力,提升吞吐量
    • 故障恢复
      • 若某分片不可用,协调节点自动重试其它副本分片
  8. 缓存机制优化
    • 分片请求缓存
      • 缓存聚合结果或频繁查询的响应(仅对不含now 或动态参数的查询生效)
    • 字段数据缓存
      • 用于排序和聚合的字段数据缓存在内存,加速计算

1.5 写入流程

  1. 客户端发起写入请求
    • 请求类型:可以是单文档写入(PUT /index/_doc/1 )、批量写入(Bulk API )或更新/删除操作
    • 协调节点选择:客户端将请求发送到任意节点(默认作为协调节点)
  2. 路由计算与主分片定位
    • 路由规则
      • 若指定文档 ID,使用哈希算法:hash(_id) % number_of_shards 确定目标分片
      • 若未指定 ID(自动生成 ID),Elasticsearch 会随机选择主分片
    • 分片定位:协调节点根据路由结果,将请求转发到该主分片所在的** Data 节点**
  3. 主分片写入处理

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)
    • 若副本分片写入失败,主分片会重试,最终可能标记副本分片为失效
  1. 返回客户端响应
  • 成功条件
    • 主分片和副本分片均为完成内存和 Translog 写入后,协调节点向客户端返回acknowledged: true
  • 失败处理
    • 若副本分片不可用,主分片仍会写入,但集群状态变为yellow (副本未分配)
    • 客户端可能收到ShardFailureException (需重试或处理异常)
  1. 近实时搜索实现(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
  • 持久化过程
    • 清空内存缓冲区,将所有 Segment 持久化到磁盘
    • 清空当前 Translog,生成新的 Translog 文件
    • Segment 只读:写入磁盘后不再修改,删除操作通过.del 文件标记
  1. Segment 合并(Merge)
  • 后台优化
    • 多个 Segment 会被合并为更大的 Segment,提升查询性能
    • 合并过程自动触发,删除过期文档(如标记为删除的文档)
    • 合并期间可能占用较多I/O和 CPU 资源
  1. 容错与恢复机制
  • 节点宕机恢复
    • 重启后,通过重放 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:创建快照上下文,适合离线导出(但消耗资源)

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. 架构运行相关流程图

drawio

快速问答

  1. Translog 如何保证数据安全?
    • Translog 使用追加写入(Append-Only)模式,避免随机写磁盘的性能问题
    • 通过 fsync 系统调度强制刷盘(依赖index.translog.durability 配置)
    • 崩溃恢复:节点重启时,通过 Translog 重放未持久化到 Lucene Segment 的操作
    • 源码关联:org.elasticsearch.index.translog.Translog 类处理日志的写入与恢复
    • 与 Lucene Commit 的关系:Flush 操作会将内存数据生成 Segment 并刷盘,同时清除已提交的 Translog
  2. 使用 Bulk API、调大 Refresh 间隔
    • 实战案例:在日志系统中,将refresh_interval 设为30s ,写入吞吐量提升 3 倍,但需接受搜索延迟
    • 参数调优:调整indices.memory.index_buffer_size (默认 10% 堆内存)增加索引缓冲区
    • 硬件影响:使用 SSD 减少 Flush 耗时,避免写入瓶颈
    • 监控工具:通过 Kibana Monitoring 观察indexing_ratemerge_time ,针对性优化
  3. Elasticsearch 和关系型数据库的写入区别
    • 数据模型:ES 时 Schema-less 的文档模型,支持动态字段;关系数据库需预定义 Schema
    • 事务性:ES 不支持 ACID 事务,依赖版本号(_version )实现乐观锁
    • 写入延迟:ES 的 NRT(近实时)vs 数据库的实时可见
  4. Refresh 机制对写入性能的影响
    • Refresh 操作将内存缓冲区中的数据生成新的 Lucene Segment,写入文件系统缓存(非磁盘),使数据可被搜索)
    • 频繁 Refresh 会导致产生大量小 Segment,增加 Merge 开销(I/O 和 CPU 竞争)
    • 日志数据:设置refresh_interval: 30s 甚至-1 (关闭自动 Refresh),通过手动 Refresh 控制搜索可见性
    • 高查询压力:避免长时间不 Refresh,否则新数据无法被搜索
    • 监控指标:通过_nodes/states/indices/ API 观察refresh_tatolrefresh_time_in_millis ,评估 Refresh 开销
  5. 主分片与副本分片的写入同步
    • 主分片写入成功后,会并行向所有副本分片发送写入请求
    • 副本分片写入成功后,主分片才向客户端返回确认
    • wait_for_active_shards: quorm (默认):多数分片(主 + 副本)可用时写入成功
    • wati_for_active_shards: 1 :仅主分片成功即可(风险:副本可能落后)
    • 若副本分片写入失败,主分片会向 Master 节点报告,触发分片重分配
    • 集群状态变为Yellow ,直到副本恢复
  6. 设计一个支持亿级日志写入的系统,你会如何规划 ES 集群?
    • 分片设计:按时间滚动索引(如logs-2023-10 ),单个分片大小控制在 50GB 以内
    • 写入优化:使用 Bulk API,客户端侧实现批量压缩与重试机制
    • 硬件规划:独立 Ingest 节点处理数据预处理,Data 节点使用 NVMe SSD
    • 可靠性:设置index.translog.durability: async ,副本数设为 1
    • 监控:通过 ILM(索引生命周期管理)自动归档旧索引到冷存储
  7. 为什么 Elasticsearch 查询比数据库快?
    • 倒排序索引:快速定位文档,避免全表扫描。
    • 分布式并行:查询拆分到多个分片并行执行。
    • 文件系统缓存:Segment 文件缓存在 OS Cache,减少磁盘 IO。
    • 列式存储(doc_values ):优化排序与聚合。
  8. 如何处理数据一致性问题?
    • 写入一致性:通过wait_for_active_shards 控制最小成功分片数。
    • 版本控制:
      • 使用_version 实现乐观锁,避免并发覆盖。
      • 外部版本号(version_type=external )兼容数据库同步。
    • 读一致性:
      • preference=_primary :强制读主分片(强一致性,性能低)。
      • replication=async :异步副本更新(最终一致性,性能高)。
  9. 如何设计一个高可用 ES 集群?
    • 节点角色分离:
      • 专用 Master 节点(3台,避免脑裂)。
      • 独立 Data 节点、Ingest 节点、Coordination 节点。
    • 分片策略:
      • 副本数≥1,跨机架分布(awareness 参数)。
      • 使用ILM(索引生命周期管理)自动滚动索引。
    • 容灾备份:
      • 快照(Snapshot)到 S3/NFS,支持跨集群恢复。
      • 多集群同步(CCR,跨集群复制)。
使用 Hugo 构建
主题 StackJimmy 设计