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 + 異步合并,換取簡單、穩定、可伸縮。