基于Lucene的分布式全文搜索引擎,Lucene提供索引、查询引擎。
支持海量数据的 实时检索 (根据关键词内容查文档)、聚合分析、日志存储 等场景。
1、原理:倒排索引(关键词 到 文档ID+位置 的对应) + FST优化(合并后缀,只存一次,省堆内存还好查)
2、ES典型应用场景:ELK日志分析、电商搜索、知识库文档检索。
3、结构(与MySQL类比):index(库/表)、Document(行,基础结构,json序列化存储)、Field(列)、Mapping(Schema表结构)
字段类型:text(存储名称,用于全文检索,会分词);keyword(存储 ID、分类,精确匹配,不会分词);double/integer/long(数值计算,如价格、销量);date(时间筛选);ip(用户IP 地址)
4、集群+分片:拆分数据(分片),多节点存储(主节点、数据节点、协调节点)。
- 主节点:分片分配,管理集群状态
- 数据节点:存储分片数据,负责读写。
- 协调节点:转发请求,合并查询结果。
index:多个数据节点 多个主分片 多个副本。
- 主分片:数据写入、不可重复,数据按哈希算法分配到主分片。主分片数量无法修改(因分片是哈希分配的基础)
- 副本分片:拷贝主分片数据,主分片故障时,副本自动升为主分片、同时承担读请求。副本数量可随时修改。
一、工作流程
理解 "数据怎么写进去" 和 "查询怎么查出来"
(一)数据写入
(以 "写入一条商品文档" 为例)
ES 写入是 "先内存、后磁盘" 的异步流程,平衡了实时性和性能:
- 请求分发 :客户端发送写入请求到任意节点(协调节点),协调节点根据文档 ID 的哈希值,计算出该文档应写入的 主分片,并将请求转发到主分片所在的节点。
- 主分片写入 :主分片节点验证文档(如字段类型是否匹配),通过后将文档写入 内存缓冲区 ,同时记录 事务日志(Translog,磁盘文件)------ Translog 是 "数据安全保障",若节点宕机,未刷盘的数据可从 Translog 恢复。
- 副本同步 :主分片写入成功后,将请求同步到所有 副本分片 所在节点,副本分片重复 "写入内存缓冲区 + 记录 Translog" 的操作,同步完成后向主分片返回 "成功"。
- 响应客户端:主分片收到所有副本的 "成功" 响应后,向协调节点返回 "写入成功",协调节点再通知客户端。
- 刷新(Refresh) :默认每隔 1 秒,ES 会将内存缓冲区的文档写入 段文件(Segment,磁盘文件),此时文档可被检索(这就是 "近实时" 的原因);内存缓冲区清空,但 Translog 保留。
- 刷盘(Flush) :当 Translog 大小超过阈值(默认 512MB)或间隔超过 30 分钟,ES 会触发 Flush:将所有段文件刷到磁盘,清空 Translog------ 这一步是 "最终持久化"。
(二)查询流程
(以 "搜索'手机快充'的商品" 为例)
查询分 "查询阶段(Query)" 和 "取回阶段(Fetch)",分布式查询的核心是 "并行计算 + 结果合并":
- 请求分发 :客户端发送查询请求到协调节点,协调节点将查询请求广播到 所有主分片和副本分片(可通过路由规则指定分片,减少广播范围)。
- 分片查询(Query 阶段):每个分片节点执行查询,筛选出 "符合条件的文档 ID 和排序得分(Score)",并将结果(仅 ID 和 Score,不包含完整文档)返回给协调节点。
- 结果合并(Query 阶段) :协调节点收集所有分片的结果,按 Score 排序,取前 N 条(如分页查询的 "from=0,size=10"),得到 "最终的文档 ID 列表"。
- 文档取回(Fetch 阶段) :协调节点根据 "文档 ID 列表",向对应的分片节点请求 "完整文档数据",分片节点返回文档后,协调节点整合数据,返回给客户端。
二、全文检索
全文检索的关键是 分析器(Analyzer) ------ 定义 "如何将文本拆分为词条(Term)",ES 内置分析器(如 standard 分词器),也支持自定义(如中文需用 IK 分词器)。
创建索引时,对 "名称" 字段使用自定义分析器
三、聚合分析
比如:按 "商品分类" 分组,统计每组的 "平均价格" 和 "商品数量",可实现复杂的统计需求。
支持 "指标聚合"、"桶聚合"、"管道聚合"。
四、索引设计
1、主分片数量规划
- 原则:每个主分片容量控制在 20-40GB(分片过大会导致查询慢、分片迁移耗时),集群节点数 ≥ 主分片数(避免主分片集中在单个节点,导致负载不均)。
- 反例:1 个索引分 100 个主分片,每个分片仅 1GB------ 分片过多会导致 "元数据管理开销大""查询时广播请求多",反而变慢。
2、Mapping 设计
- 禁用动态映射(
"dynamic": "strict"):避免未知字段自动生成类型(如将 "商品 ID" 误设为text类型)。 - 合理选择字段类型:如 "时间" 用
date类型(支持范围查询,如 "查询近 7 天的日志"),"IP 地址" 用ip类型(支持 IP 段查询)。 - 关闭无用字段的索引:如 "商品描述" 的长文本若不需要检索,可设置
"index": false,减少存储和分词开销。
3、索引生命周期管理(ILM)
- 对于日志等 "时序数据",按时间分索引(如
log-2024-05-01、log-2024-05-02),通过 ILM 自动实现 "热索引(实时写入查询)→ 温索引(只读)→ 冷索引(压缩存储)→ 删除过期索引",降低存储成本。
五、索引优化
1、Mapping 设计优化
Mapping 一旦确定(字段类型无法修改),后期调整需重建索引,
- 字段类型要匹配具体的业务场景
- 分词器,避免过度分词(精确匹配用粗粒度分词、模糊匹配用细粒度分词、英文用词根提取分词)
- 禁用 "动态映射",避免字段泛滥
- 关闭无用字段的 "索引" 或 "存储"
2、分片与副本
分片数量不能过多(查询时广播请求多 性能下降),容量不能过大(查询延迟高、故障恢复慢)。
副本数不能超过 "集群节点数 - 1",热数据、冷数据、写密集 的副本数依次减少。
写入时指定路由键(tenant_id),数据会写入该路由 对应的分片,查询时指定相同路由键,仅扫描该分片,避免广播到索引的所有分片。
3、写入
批量写入 + 并行发送_bulk 请求 1000-5000条/批
4、实时性刷新优化
根据实时性需求,调整refresh次数 和 refresh_interval时间间隔
5、查询优化
(1)优先用 filter 代替 query:利用缓存,减少计算
ES 的查询分为query和filter:
- query:计算文档与查询的相关性得分(Score),结果不缓存,耗时较长;
- filter:仅判断文档是否匹配,结果缓存(缓存 key 为查询条件),不计算得分,耗时短。
优化原则 :过滤条件(如价格范围、时间范围、分类)用filter,全文检索用query。
(2)避免 "深分页":用 search_after 代替 from+size
默认的分页方式from+size存在严重缺陷:当from较大时(如from=10000,size=10),ES 会从所有分片获取前 10010 条数据,在协调节点排序后取最后 10 条,导致内存占用高、延迟大。
优化方案 :用search_after实现 "续页",基于上一页最后一条文档的sort字段值(如id、timestamp),仅获取下一页数据,无深分页问题。
6、存储优化
- 冷数据提升压缩比
- 对冷 / 温数据执行强制合并(
force merge),将小段合并为 1 个或少数几个大段,减少段文件数量,释放磁盘空间。 - 清理过期数据
性能优化(常见场景)
| 优化场景 | 关键手段 |
|---|---|
| 写入性能优化 | 1. 批量写入(用 _bulk API,每次批量 1000-5000 条,减少请求次数);2. 调大 refresh_interval(如从 1s 改为 30s,减少段文件生成频率);3. 临时关闭副本(写入完成后恢复),避免同步开销。 |
| 查询性能优化 | 1. 过滤先行(用 filter 代替 query,filter 结果会缓存,不计算 Score);2. 避免通配符开头查询(如 *phone,会导致全分片扫描);3. 分页查询用 search_after 代替 from+size(from+size 会在协调节点排序所有结果,数据量大时慢)。 |
| 存储优化 | 1. 对冷索引启用压缩(如 index.codec: best_compression);2. 删除无用字段(用 _source 过滤不需要的字段);3. 避免存储重复数据(如日志中的 "用户 ID" 可只存一次,不重复嵌入)。 |