原创

Elasticsearch架构


Elasticsearch架构

2025年10月26日夜里终于读完了Elasticsearch in Action的英文版, 这本书由浅入深, 不仅讲解了Elasticsearch的架构设计和高级特性, 而且还给出很多例子, 整本书的英文浅显易懂, 英语四级便可阅读. 今天总结一下这本书,从架构的角度来阐述.

结构化数据一般存储在数据库中, 有自己的schema, 数据通常是规范化的, 在查询结构化数据时, 要么查询到, 要么查询不到. 而非结构化数据没有固定的schema, 通过相似性算法来生成一个相关性分数, 这个分数表明搜索规则和搜索结果的相关性, 搜索结果根据相关性分数从大到小排序并给出.

Elasticsearch是基于Lucene功能的, 分布式可伸缩的, 能够处理大量数据的搜索和分析引擎, 不仅能够有相关性查询功能, 而且还有模糊查询, 错别字修正, 地理查询和高亮显示等等功能. 使用ElasticSearch的关联查询会有潜在的性能隐患.

设计

文档处理流程

当使用put方法提交一个文档时, es内部会进行下面的处理:

一. 选择分片(Shard)

分片(Shard)是将数据根据一定的规则切分到不同的es节点上, 提高性能. 分片持有数据, 创建支持的数据结构, 管理查询, 分析数据, 是Lucene的一个实例.

因为文档被存储到不同的分片中, 检索时需要根据路由算法得出要查找的分片:

  • 路由算法: 分片下标 = hash(文档ID) % 分片数量

es的伸缩(Scaling)主要有两种: 垂直伸缩和水平伸缩.

  • 垂直伸缩是提升cpu核心数, 内存, 磁盘容量等, 这种伸缩需要集群停机, 需要基于灾难恢复模式.
  • 水平伸缩是添加节点, 更多的机器等, 这种伸缩es会自动分配切片和数据副本到新节点.

二. 分析和处理文档, 将文本内容分词化和规范化

  • 分词化(tokenization): 是根据一系列的规则将文本拆分成单词(token)的过程
  • 规范化(normalization): 将单词进行再次处理的过程, 如查找单词的根词, 同义词, 时态相关词等, 如果输入的单词有拼写错误, 则根据可替换的字母数量找到相应的单词(token).

然后通过上述两步生成的token, 根据一个反向索引的结构关联到文本.

分析过程被相关的分析模块执行, 分析模块包含字符过滤器, 分词器, 分词过滤器.

  • 字符过滤器(character filter), 把文档中不必要的字符进行剔除
  • 分词器(tokenizer), 通过不同的分词逻辑(空格或其他字符)来进行分词
  • 分词过滤器(token filter), 对分词进行处理, 比如变成小写, 同义词, 拼写错误等

在进行文本搜索时, 同样会进行分词化和规范化. 注意使用term查询时, 则不会进行分析处理.

分词器除了标准分词器, 有n-gram, edge n-gram, shingle等等其他的分词器.

假如要分词一个单词coffee

如果是bi-gram(2-gram), 则会分为co, of, ff, fe, ee

如果是tri-gram(3-gram), 则会分为cof, off, ffe, fee

n-gram是根据一个窗口来进行分词的, 而edge n-gram是递增的.

如果是edge n-gram, 则会分为 c, co, cof, coff, coffe, coffee

三. 创建数据结构, 反向索引, 多维BKD树

BKD树: 把一大堆数字按大小切成很多块, 每块写成一个压缩包, 找数时只看包名就能跳过整包.

即排好序 → 切小包 → 压成块 → 建范围索引, 查数时可以整块整块地跳过.

如果是多维, 则先按照一个维度排序分块, 然后再按照另一个维度排序, 同样压缩成包.

反向索引是建立分词和文档的关联. 每个有text字段的索引都会有各自的反向索引. 反向索引是一个以分词为key的HashMap结构, value是一种包含文档ID, 单词出现频率等等信息的数据结构.

相关性(Relevancy)是一个正的浮点数, 用来决定文档排名, es使用BM25(Best Match 25)相似性算法来计算分数, 该算法基于文档中的单词出现的次数(term frequency), 反向索引中的频率(单词在全部文档中出现的次数, document frequency), 单词所在的字段长度等等. term frequency越高, 相关性越高; document frequency越高, 相关性越低; 单词所在的字段长度越低, 相关性越高.

相关性可以通过两个因子进行衡量:

  • 精确率(Precision): 返回的真的相关文档在返回的文档中所占的百分比, 返回不一定相关, 衡量查得准不准.
  • 召回率(Recall): 返回的真的文档中在所有相关的文档中所占的百分比, 相关但没返回, 衡量查得全不全.

这两个因子是关系相反的, 即精确率越高, 召回率越低.

四. 分片存储数据到对应节点的内存中, 并复制给对应的副本

副本(Replica)是将数据复制一份到不同的节点上, 提高可用性和查询性能. 副本能够承担读负载, 并且一般不和其对应的分片在同一个节点上, 防止某些节点宕机后, 导致部分数据不可用的情况.

es集群有三种状态, 称为交通灯.

红灯表明至少有一个分片没有被分配和就绪.

黄灯表明分片都分配和就绪了, 但是副本并没有分配和就绪.

绿灯表明分片和副本都分配和就绪了.

可以通过allocation的API来查看集群的状态, 并使用/explain接口来查看具体原因.

估算分片时, 需要根据当前的数据要求和未来的需求来. 但需要注意的是每个分片都需要有堆内存和计算资源. 一般推荐堆内存是机器内存的一半.

分片节点有不同的角色, 如下表所示

角色功能描述
Master node集群管理, 创建删除索引, 节点操作
Data node文档存储和检索
Ingest node转换从管道(Word, PDF)注入的数据
Machine learning node处理机器学习任务和请求
Transform node生成数据的聚合摘要, 把原始索引生成适合业务查询的汇总索引
Coordination node协调节点, 默认角色, 请求转发到合适的节点, 收集汇总搜索结果

五. 通过一定的频率持久化到文件系统中

起初文档先被复制到分片节点的内存缓存, 然后写入一个segment, 最终segment合并且持久化到文件系统. 一旦文档被移动到segment, 那么可以被检索了.

segment的创建频率是1秒, 且是不可变的, 一旦创建就不能再添加文档到该segment中.

es中的Lucene的segment合并是三个合并成一个.

被索引的文档通过一定的刷新时间持久化到磁盘上, 服务器宕机可能会造成数据的损失. 虽然可以通过减少刷新时间减少该损失, 但也会造成更频繁的IO操作, 导致性能瓶颈, 因此需要找到最优的刷新策略.

es是最终一致性的分布式应用, 意味着文档最终会写入到持久存储中.

零停机迁移数据

当索引需要添加新的字段, 有bug或者不满足业务要求时, 此时从旧索引中迁移数据到新索引中, 使用alias别名可以实现零停机迁移数据, 步骤如下:

  1. 创建一个索引别名alias执行当前的旧索引
  2. 创建一个使用新字段, 修复bug, 满足业务最新配置的新索引
  3. 从旧索引中复制数据(reindex的API)到新索引
  4. 重新创建相同的索引别名alias指向新索引, 此时通过该索引别名alias的查询会使用新索引
  5. 当复制数据执行完成并释放后, 关闭(close的API)并删除旧索引

索引分片伸缩

有时索引会数据过载, 为了避免损失数据和缓慢的查询, 可以重新分配数据到额外的分片. 添加分片能够优化内存和均匀地分配文档. es中split的API可以拆分索引, 添加分片, 但是有以下的规则需要遵守:

  1. 拆分后的目标索引再操作前不能存在, 在拆分的过程中, 一个原索引的副本会转移到目标索引
  2. 目标索引的分片数量必须是原索引的分片数量的整数倍
  3. 目标索引不能有小于原索引的分片数量
  4. 目标索引的节点必须有足够的空间

同样的在使用shrink的API减少分片时, 有以下的规则需要遵守:

  1. 原索引应该设置成只读模式, 虽然不是强制的
  2. 目标索引不能在shrink操作之前存在或创建
  3. 所有的索引分片必须存在于相同的节点上
  4. 目标索引的数量必须是原索引分片数量的因数
  5. 目标索引的节点必须满足内存的要求

检索

当客户端或用户使用search的API发起检索时, es的处理流程如下:

  1. 搜索引擎会转发请求到一个可用的节点, 因为节点默认都是协调节点(Coordination node), 因此都是可以处理请求的.
  2. 一旦请求到达协调节点, 该节点会选择一个副本集, 该副本集有包含数据的分片和副本.
  3. 协调节点将请求发送到副本集包含的节点, 让这些节点执行查询, 每个节点返回结果给协调节点.
  4. 协调节点合并结果和数据, 然后发送给客户端或用户.

如果协调节点也是数据节点, 它同样也会执行查询获取结果.

检索search的API这里不再赘诉.

性能

分片(Shard)主要处理写入数据, 传输数据到副本(Replica); 副本(Replica)主要负责数据的读取. 因此提升副本的数量, 能够提高读取的吞吐量.

影响es的性能有多个, 包括磁盘性能, CPU, 内存, 节点实例的JVM垃圾回收, 数据节点的数量, 分片副本的数量, 对多个字段的查询等等.

对多个字段的查询, 可以在设置mapping的时候使用copy_to配置聚合多个字段的内容到一个字段, 这样查询时只查询一个字段, 可保证性能.

不需要全文搜索的字符串字段可使用keyword类型, 这种类型不会在索引文档和搜索文档时进行分析, 可以减少计算; 如果text的字段可以允许搜索在关键字上, 那么可以将该字段添加一个额外的keyword类型(多字段类型).

如果使用自定义的ID, 那么es会做一项额外的步骤, 检查该ID是否有对应的文档存在. 如果应用可以允许es生成随机的ID, 那么可以跳过这项额外的步骤, 提升性能.

当进行大批数据操作的时候, 推荐使用bulk的API, 该API能够批量的索引文档, 但是一个批次的数量需要在实际中进行评估.

可以通过settings的API控制索引刷新到磁盘的时间间隔, 虽然会造成在该时间间隔内不能查询到最新的数据, 但是会减少昂贵的IO操作, 提高性能. 这个时间间隔仍然需要在实践中进行评估. 刷新到磁盘的线程可以通过配置文件来控制线程池的大小.

安全

快照

在生产环境没有备份和恢复功能是危险的, 因此es提供快照功能来备份和在必要的时候进行恢复. 可使用snapshot的API进行快照的注册, 备份和恢复. 可以使用slm的API来自动地管理快照. 企业版的es还支持可查询的快照.

集群

es集群节点之间的通信默认通过9300端口进行. master节点主要负责集群范围内的操作, 比如分配分片, 索引管理等, 也是保证集群健康的关键. 一个es集群中只能有一个master节点.

master-eligible节点是被打上master标签的节点, 虽然有master的角色, 但是必须通过选举(过半数当选)才能变成真的master节点.

集群的状态包括所有的元数据, 有分片, 副本, 模式, 映射, 字段信息等等. 这些信息作为全局状态, 写入到每个节点上. master节点是唯一能够执行提交集群状态的, 有责任维护最新的集群信息. master节点周期性地提交集群数据(两阶段提交):

  1. master节点计算集群的变化, 发布到各个节点, 然后等待回复.
  2. 每个节点收到集群更新信息, 但是还不能更新节点本地存储的状态; 一旦收到master发过来的信息, 则立即发送回复给master.
  3. 当master节点收到达到一定数量(quorum)的master-eligible节点的回复, 那么它将会提交集群的状态, 不必等待所有节点的回复.
  4. master节点提交集群变化成功后, master节点广播消息到各个节点, 指示它们提交先前收到的集群更新信息.
  5. 每个节点提交集群更新信息.

如果节点在指定的周期内不响应master节点, 则会标记为失败节点, 从集群中删除这些失败节点.

上述的一定数量(quorum)的计算: 最小master-eligible节点数 = (master-eligible节点总数 / 2) + 1.

推荐的最小的master-eligible节点数是3个, 这样会缓解脑裂问题. 有时候集群会因为节点数量不够, 导致原本一个集群的节点被分为两组, 两组都有master节点, 导致两组之间的通信中断, 引起数据差异.

可以将特定节点在配置文件里设置成master的角色, 这样这些特定节点只会进行集群管理的操作, 而不负责数据的读写和一些管道注入的操作.

可以通过_cluster/settings接口来配置磁盘使用的阈值. 如果一个节点超过了低阈值, 那么es就不再把任何新分片或副本分配到该节点, 直到磁盘空间回落到低阈值之下. 如果一个节点超过了高阈值, 那么es会尽全力将该节点的分片或副本移动到其他节点, 直到该磁盘的空间回落到高阈值之下. 如果一个节点超过了洪水闸门阈值, 那么es会停止该节点的所有写操作, 并让分片置于只读模式.

es使用断路器模式来防止客户端等待响应时间过长, 通常该响应只能返回错误. 断路器是一个后备方法(fallback), 当一个响应超过指定的时间(比如内存溢出, 资源被锁定)时会触发该后备方法.

ElasticSearch
全文搜索技术
  • 作者:lzlg520
  • 发表时间:2025-11-03 13:39
  • 版权声明:自由转载-非商用-非衍生-保持署名
  • 公众号转载:请在文末添加作者公众号二维码