0. 先把术语摆正
- Index(索引):逻辑数据集合,≈ MySQL 的库。
- Document(文档):一条 JSON 数据,≈ MySQL 的行。
- Field(字段):文档里的键值,≈ MySQL 的列。
- Shard(分片):一个索引被水平拆成 N 份(primary主 + replica副本)。
- Segment:Lucene 最小存储单元(文件不可变);很多机制围着它转。
- 倒排索引:term → posting list(文档 ID 列表)。
- Doc Values:列式存储,用于排序/聚合(不是倒排索引)。
- File System Cache:OS 级缓存,并非落盘。
ES 搜索是近实时(NRT),不是强实时。写入到可被搜索看到,中间隔着一个 refresh。
1. 单机写入:一条写请求到底发生了什么?
1.1 写入过程(Index/Insert/Update)
-
Analyzer 分析:对
text
字段分词(如 IK),keyword/数值/日期
不分词;可能生成多字段(text
+keyword
)。 -
写入内存 buffer:用于构建倒排索引/列存结构;此时搜索不可见。
-
追加 translog:同时把这次操作追加写入 translog 文件(磁盘上的操作日志文件,但默认先进 OS page cache,有个参数控制什么时候真正落盘,1.3会提到,其实默认就是直接刷盘)。
-
refresh(默认 1s):把内存 buffer 的内容刷新为新的 segment;
- 新 segment 进入 file system cache,从此搜索可见;
- 这一步不是持久化,只是“可被搜索到”。
-
flush(定期或阈值触发):强制把缓存中的 segment 落到磁盘,清空 translog,形成安全的持久化点(commit point)。
-
merge(后台线程):自动把多个小 segment 合并成更大的 segment,清理删除标记,降低查询开销。
1.2 GET-by-id 与搜索的可见性不同
GET index/_doc/id
:默认实时(realtime=true
),即使没 refresh,也能从 translog 读取最新版本。_search
查询:需要 refresh 后的新 segment 才能看到(NRT)。
1.3 translog 的持久化策略
-
index.translog.durability=request
(默认):每次写请求结束前执行 fsync,确保 translog 已真正落到物理盘。- 安全但慢。
-
index.translog.durability=async
:写入 translog 后不立刻 fsync,按index.translog.sync_interval
(默认 5s)定期批量 fsync。- 快,但节点崩溃时可能丢失最近一个
sync_interval
窗口的数据。
- 快,但节点崩溃时可能丢失最近一个
重点区分:“写入 translog 文件” ≠ “fsync 到磁盘盘片”。前者可能仅到 OS page cache;后者才是不可丢的落盘。
1.4 refresh / flush / merge 的触发与作用
-
refresh
- 触发:周期(
index.refresh_interval
,默认 1s)/ 手动 / 写多时可临时关闭(设为-1
)再打开批量refresh以提速。 - 作用:生成新 segment,使数据可被搜索。
- 触发:周期(
-
flush
- 触发:定时(常见 30min 级别)、translog 过大、手动。
- 作用:把 segment 真正落盘并清空 translog,形成安全恢复点。注意,translog只是操作日志而不是实际数据segment的落盘。
-
merge
- 触发:Lucene 的合并策略自动决定(如分层合并,逐级把小段并大)。
- 作用:减少 segment 数/删除开销,提升搜索性能,但会吃 IO/CPU。
简要心智图:
写 → buffer + translog →(1s)refresh(可查但未持久化)→(周期/阈值)flush(持久化并清空 translog)→(后台)merge(优化查询)。
2. 从单机到集群:协调、路由与复制
2.1 写入复制流程(Primary-Replica)
- 客户端 → 任意节点(作为协调节点)。
- 协调节点计算目标 主分片,把请求转发过去。
- 主分片执行写入(buffer + translog),并把操作并行转发给所有副本分片。
- 副本分片执行同样的写入(buffer + translog)。
- 所有副本 ACK 后,主分片返回给协调节点 → 客户端。
注意:一条文档只会写入它所属的 一个主分片 + 其副本,不是所有分片。
2.2 协调节点(Coordinating Node)是怎么来的?
-
不是选举出来的固定角色:谁接到客户端请求,谁临时扮演协调节点。
-
为什么需要它:
- 屏蔽路由细节(客户端不必知道分片在哪个节点),如果没有他,客户端就必须保存每个内容的主分片在哪个节点,这肯定不合理。。
- 适应集群动态(分片会迁移/升副为主)。
- 压力均衡(请求可打到任意节点,由它路由)。
2.3 路由规则与分片定位
-
所有节点都知道这两个内容:
- 路由算法:
shard = hash(_id) % number_of_primary_shards
。 - 分片位置:通过 Cluster State(由 master 维护并分发)得知“某分片的主/副在谁那里”。
- 路由算法:
2.4 一致性与可用性参数
-
wait_for_active_shards
:控制写入前需要有多少份分片处于 active 才返回;1
(默认)仅等主分片;all
等主 + 所有副本。
-
副本数
number_of_replicas
:副本越多,读扩展强、容错高,写入成本也越高。 -
失败与恢复:主分片宕机会由某个副本提升为新的主分片;通过序列号/主 term 等元数据保证顺序与幂等。
3. Segment 细节:为什么会有重复 term?查询怎么处理?
- 每次 refresh 都会产出一个独立的小 segment;不同 segment 彼此独立,相同 term 会重复存在。
- 查询阶段:Lucene 会同时在多个 segment 里查同一 term 的 posting list,并把结果归并。
- merge 阶段:把多段合成大段,合并相同 term 的 posting,并物理清理删除标记(tombstones)。
这也是为什么 segment 过多会拖慢查询,merge 能提速但会消耗资源。
4. 字段与索引结构:不是所有字段都是倒排表
text
:分词,建立倒排索引(term→posting)。keyword
:不分词,整个值作为一个 term 建倒排(适合精确匹配、聚合)。- 数值/日期/地理:不是倒排,使用 BKD 树/空间索引 支持范围/地理查询。
index: false
:不建索引,只存储,无法被检索。- 多字段(multi-fields):一个字段既是
text
又是keyword
,兼顾全文与精确匹配。
5. 可观测与常用调优
5.1 可见性相关参数
index.refresh_interval
:默认1s
;批量写入建议临时设为-1
,完毕后再改回。index.translog.durability
:request
(默认,安全) /async
(快,可能丢最近一个窗口)。index.translog.sync_interval
:async
模式下 fsync 周期,默认5s
。
5.2 写入吞吐相关手段
-
Bulk 批量写:合并网络/解析开销;控制合理批大小(几 MB~几十 MB)。
-
副本与刷新策略:
- 导入期可把副本数临时设为
0
,导入完成再恢复。 - 刷新间隔设为
-1
,导入完成手动_refresh
。
- 导入期可把副本数临时设为
-
映射与字段控制:禁用不需要的索引/存储/来源字段(如关闭
_all
、合理控制doc_values
)。 -
Merge 影响:高写入期可以调节合并限速(避免打爆 IO),导入后允许合并充分进行以稳定查询性能。
5.3 故障与恢复
- 宕机恢复:依赖 translog 回放 + 上次 flush 的 commit point。
- 副本恢复:主分片把缺失的 segment/操作日志同步给副本,直到达到一致。
6. 协调节点到底值不值得?(设计权衡)
- 好处:隐藏路由、承接集群动态、均衡负载、简化客户端。
- 代价:多一跳转发(但通常可忽略),以及所有节点需要持有最新 Cluster State(大集群可能膨胀,需要控制索引/映射规模)。
7. 一些面试点
Q1:为什么 ES 写入快、搜索也快?
A:倒排索引/列存结构 + 分片并行 + OS cache + 针对数值/地理的 BKD/空间索引;查询时多段归并,merge 降段数。
Q2:写入成功是不是所有分片都写了?
A:不是。一条文档只落到一个主分片及其所有副本。
Q3:写入成功后为什么搜索不到?
A:还没 refresh(默认 1s)。可手动 _refresh
,或等下一次 refresh。GET-by-id
默认可见(realtime)。
Q4:translog 是不是“写盘”了?还会丢?
A:写入 translog 后若没 fsync,只在 OS page cache,机器掉电可能丢。durability=request
会在返回前 fsync,安全;async
依赖 sync_interval
,窗口内可能丢。
Q5:merge 什么时候发生?会影响性能吗?
A:后台自动按策略触发;会吃 IO/CPU,写入高峰要限速/合理配置,导入完成后让它合并以稳定查询时延。
Q6:为什么需要协调节点?
A:简化客户端、适应分片迁移/升主、均衡负载;谁接到请求谁协调。
8. 小结
单机:写 → buffer + translog → refresh(可搜) → flush(持久化) → merge(优化)。
集群:任一节点临时协调 → 定位主分片 → 写主并同步副本 → 返回。
可靠性:durability=request
安全;async
+ sync_interval
快但可能丢最近窗口。
设计哲学:用不可变 segment + 异步合并,换取简单、稳定、可伸缩。