2025年10月26日夜里终于读完了Elasticsearch in Action的英文版, 这本书由浅入深, 不仅讲解了Elasticsearch的架构设计和高级特性, 而且还给出很多例子, 整本书的英文浅显易懂, 英语四级便可阅读. 今天总结一下这本书,从架构的角度来阐述.
结构化数据一般存储在数据库中, 有自己的schema, 数据通常是规范化的, 在查询结构化数据时, 要么查询到, 要么查询不到. 而非结构化数据没有固定的schema, 通过相似性算法来生成一个相关性分数, 这个分数表明搜索规则和搜索结果的相关性, 搜索结果根据相关性分数从大到小排序并给出.
Elasticsearch是基于Lucene功能的, 分布式可伸缩的, 能够处理大量数据的搜索和分析引擎, 不仅能够有相关性查询功能, 而且还有模糊查询, 错别字修正, 地理查询和高亮显示等等功能. 使用ElasticSearch的关联查询会有潜在的性能隐患.
当使用put方法提交一个文档时, es内部会进行下面的处理:
一. 选择分片(Shard)
分片(Shard)是将数据根据一定的规则切分到不同的es节点上, 提高性能. 分片持有数据, 创建支持的数据结构, 管理查询, 分析数据, 是Lucene的一个实例.
因为文档被存储到不同的分片中, 检索时需要根据路由算法得出要查找的分片:
es的伸缩(Scaling)主要有两种: 垂直伸缩和水平伸缩.
二. 分析和处理文档, 将文本内容分词化和规范化
然后通过上述两步生成的token, 根据一个反向索引的结构关联到文本.
分析过程被相关的分析模块执行, 分析模块包含字符过滤器, 分词器, 分词过滤器.
在进行文本搜索时, 同样会进行分词化和规范化. 注意使用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越高, 相关性越低; 单词所在的字段长度越低, 相关性越高.
相关性可以通过两个因子进行衡量:
这两个因子是关系相反的, 即精确率越高, 召回率越低.
四. 分片存储数据到对应节点的内存中, 并复制给对应的副本
副本(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别名可以实现零停机迁移数据, 步骤如下:
有时索引会数据过载, 为了避免损失数据和缓慢的查询, 可以重新分配数据到额外的分片. 添加分片能够优化内存和均匀地分配文档. es中split的API可以拆分索引, 添加分片, 但是有以下的规则需要遵守:
同样的在使用shrink的API减少分片时, 有以下的规则需要遵守:
当客户端或用户使用search的API发起检索时, es的处理流程如下:
如果协调节点也是数据节点, 它同样也会执行查询获取结果.
检索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节点周期性地提交集群数据(两阶段提交):
如果节点在指定的周期内不响应master节点, 则会标记为失败节点, 从集群中删除这些失败节点.
上述的一定数量(quorum)的计算: 最小master-eligible节点数 = (master-eligible节点总数 / 2) + 1.
推荐的最小的master-eligible节点数是3个, 这样会缓解脑裂问题. 有时候集群会因为节点数量不够, 导致原本一个集群的节点被分为两组, 两组都有master节点, 导致两组之间的通信中断, 引起数据差异.
可以将特定节点在配置文件里设置成master的角色, 这样这些特定节点只会进行集群管理的操作, 而不负责数据的读写和一些管道注入的操作.
可以通过_cluster/settings接口来配置磁盘使用的阈值. 如果一个节点超过了低阈值, 那么es就不再把任何新分片或副本分配到该节点, 直到磁盘空间回落到低阈值之下. 如果一个节点超过了高阈值, 那么es会尽全力将该节点的分片或副本移动到其他节点, 直到该磁盘的空间回落到高阈值之下. 如果一个节点超过了洪水闸门阈值, 那么es会停止该节点的所有写操作, 并让分片置于只读模式.
es使用断路器模式来防止客户端等待响应时间过长, 通常该响应只能返回错误. 断路器是一个后备方法(fallback), 当一个响应超过指定的时间(比如内存溢出, 资源被锁定)时会触发该后备方法.