Elasticsearch 核心架構與資料儲存大揭秘 — Node, Shard, Lucene
上一篇教你怎麼開車——docker compose up、打幾個 API、搜到第一筆文件。這篇要打開引擎蓋,看裡面的引擎、變速箱、底盤是怎麼配合的。
為了不讓這件事變成讀規格書,我用鼎泰豐來比喻。
鼎泰豐集團是一個 Cluster。信義店、永康店、101 店是不同的 Node。每間店的菜單是一個 Index。菜單被切成好幾個工作台,每個工作台是一個 Shard。每個工作台上有一台料理機器人,那就是 Lucene。備份工作台是 Replica——如果永康店的工作台壞了,101 店的備份可以立刻頂上。
1 | 鼎泰豐集團 (Cluster) |
接下來一個一個拆。
Cluster、Node、Index
Cluster 是一群 Node 透過相同的 cluster.name 組成的邏輯單位。它自動協調資料分布、故障轉移、負載均衡。注意:同名的 Node 會自動加入同一個 Cluster。正式環境千萬別用預設名稱 elasticsearch,不然隔壁同事的開發機可能偷偷加入你的叢集。
Node 就是一個 ES 實例 = 一個 JVM Process。每個 Node 可以身兼多職,但正式環境建議專職分工:
- Master Node(總經理):管叢集狀態——建立刪除索引、分片分配決策。不處理搜尋寫入。建議 3 個 master-eligible 節點做高可用,避免腦裂。
- Data Node(廚師):真正幹活的。儲存資料、執行 CRUD、搜尋、聚合。CPU 和記憶體吃最兇。
- Coordinating Node(外場服務生):接收客戶端請求,算出該去哪個 Shard,彙整各 Shard 的結果回傳。每個 Node 預設都有這個角色。
- Ingest Node(食材處理員):做資料前處理——GeoIP 解析、日期轉換、text enrichment。輕量 ETL。
ES 8.x 用 node.roles 設定角色,取代舊版的 node.master / node.data 布林值。
1 | GET _cat/nodes?v&h=name,node.role,heap.percent,ram.percent,cpu,master |
Index 是文件的邏輯容器。7.0 之後,一個 Index 大致等於 RDBMS 的一張 Table。7.0 之前 Index 像 Database、Type 像 Table,但 Type 已經被廢除了——因為同一個 Index 裡不同 Type 的同名欄位會在 Lucene 底層共用同一個倒排索引,搜尋結果會亂掉。
Shard 和 Replica——水平切分的核心
ES 把一個 Index 水平切成 N 份,每份就是一個 Primary Shard。這是水平擴展的關鍵——資料分散到多個分片,搜尋時平行處理。每個 Shard 底層就是一個完整的 Lucene Index。
1 | my-menu Index (3 Primary + 1 Replica) |
幾個必須記住的規則:
分片大小:單個 Shard 控制在 10-50 GB。太小浪費 overhead,太大搜尋慢而且故障恢復拉長。
每個 Data Node 的 Shard 上限:大約 20 shards/GB heap。32GB heap 的機器最多撐 640 個分片。
分片數不可變。number_of_shards 建完就鎖死,原因是路由公式 shard = hash(_id) % number_of_shards。改了分片數等於所有文件的歸屬全亂,只能 reindex。7.0 之前預設 5 個 primary shards,很多人建了幾千個小索引結果上萬分片,叢集 overhead 直接爆炸。7.0 之後預設改成 1。
Replica 是 Primary Shard 的完整副本。它做兩件事:高可用(Primary 掛了 Replica 秒級升級)和讀取擴展(搜尋請求可以分散到所有 Replica)。Replica 數量可以動態調整,不需要 reindex:
1 | PUT /my-menu/_settings |
大量寫入時可以暫時把 Replica 關掉提升效能,寫完再開。
Lucene——ES 的靈魂引擎
如果 ES 是一台跑車,Lucene 就是引擎。Doug Cutting 1999 年用 Java 寫的——對,就是後來又寫了 Hadoop 的那位。ES 在 Lucene 外面包了一層分散式外殼:RESTful API、叢集管理、分片、副本。但真正做搜尋的核心運算,全是 Lucene 在處理。
層級關係刻進腦子裡:
1 | 1 ES Index |
Segment 是 Lucene 最核心的概念。每次 Refresh 產生一個新 Segment,寫入後就不可變(immutable)。不可變是效能好的關鍵——不需要鎖,多執行緒可以安全併發讀取。
刪除一筆文件不會真的從 Segment 裡移除,而是在 .del 檔案標記為已刪除。搜尋時跳過它。更新也是一樣——標記舊版本刪除 + 新版本寫進新 Segment。ES 沒有真正的 in-place update。
那標記刪除的資料什麼時候才會真正消失?等 Merge。Lucene 背景程序定期把多個小 Segment 合併成大 Segment,同時物理刪除被標記的文件,真正釋放磁碟空間。所以「我刪了 100 萬筆為什麼磁碟空間沒變」——因為 Merge 還沒跑。
倒排索引——搜尋快的終極秘密
傳統資料庫的索引是「文件 → 包含哪些詞」。倒排索引反過來:「每個詞 → 出現在哪些文件」。
1 | 假設三份文件: |
SQL 的 LIKE '%小籠包%' 是逐行掃描。ES 是查一張已經建好的表。資料量越大,差距越明顯——100 萬筆文件,SQL 要掃 100 萬行,ES 還是查一張表。
除了 Inverted Index,Lucene 還有 Doc Values(列式儲存,用於排序和聚合)和 Stored Fields(存放 _source 原始 JSON)。三者分工明確:搜尋用倒排索引、排序聚合用 Doc Values、回傳原始文件用 Stored Fields。
一筆文件的完整旅程
從你打下 POST /index/_doc 到文件可以被搜尋到,中間經過五個階段。
① 請求進入 Coordinating Node——叢集裡任何一個 Node 都可以接請求。接到請求的那個自動成為本次的 Coordinating Node。
② 路由計算——用 shard = hash(routing) % number_of_primary_shards 算出該去哪個 Primary Shard。routing 預設就是文件的 _id。這也是分片數不能改的根本原因——改了公式結果就變了,同一個 _id 會被送到錯的分片。
③ Primary Shard 寫入——文件同時寫進 Memory Buffer(記憶體)和 Translog(磁碟上的 Write-Ahead Log)。Translog 就像 MySQL 的 redo log——機器掛了可以從這裡恢復。
④ Refresh(每 1 秒)——Memory Buffer 的內容被寫成一個新的 Segment,放在 OS Page Cache 裡。注意:此時 Segment 還在記憶體,尚未落地磁碟。但已經可以被搜尋了。這就是「近即時搜尋 (NRT)」的由來——寫入後最多等 1 秒。
⑤ Flush(定期)——把 Page Cache 裡的 Segments 用 fsync 寫到磁碟,然後清空 Translog。這步之後資料才真正持久化。
1 | Client POST /_doc |
大量匯入資料的時候,1 秒一次 Refresh 會產生大量小 Segment 拖慢速度。可以暫時關掉:
1 | PUT /my-menu/_settings |
匯入完再改回 "1s"。這個技巧可以提升 30-50% 的寫入效能。
Force Merge 可以手動合併 Segment:
1 | POST /my-menu/_forcemerge?max_num_segments=1 |
但這個操作吃大量 CPU 和 IO。鐵律:只對不再寫入的索引使用。曾經有人在 production 的活躍索引上跑 Force Merge,直接佔滿 IO 導致所有搜尋 timeout。
五個會在半夜叫你起床的坑
分片數設太多:7.0 之前預設 5 個 primary shards,一個叢集幾千個小索引加起來就是上萬分片。每個分片需要約 10MB heap 加上 Lucene 的記憶體結構。一算就知道這個數字有多誇張。
分片大小不均:custom routing 或資料分佈不均會讓某些分片巨大、某些很小,造成 hot node。用 GET _cat/shards?v 監控,發現不均就該重新規劃 routing 或 reindex。
Refresh 太頻繁:大量寫入場景,預設 1 秒 refresh 會產生大量小 Segment。改成 30 秒或 -1 效能差很多。記得匯入完改回來。
以為 delete 立刻釋放空間:刪除只是在 .del 標記。要等 Merge 才真正清除。不要看磁碟用量沒變就慌。
對活躍索引 Force Merge:想像你正在出餐的廚房裡做大掃除。結果就是搜尋全部 timeout。只對昨天的日誌索引、已經封存的歷史資料做。
回到鼎泰豐的比喻。
一間連鎖餐廳能同時服務上千桌客人,靠的不是一個超大的廚房,而是把廚房切成很多工作台(Shard),每個工作台有自己的設備(Lucene),再加上備份工作台(Replica)確保任何一台壞了不會影響出餐。
ES 的擴展邏輯也是這個——不是把一台機器變更強,是把資料切到更多台機器上。水平擴展而非垂直擴展。理解這一點,你就能回答面試裡 90% 關於 ES 架構的問題。
剩下的 10%?那是關於你踩過多少坑。下一篇我們看資料怎麼進出——CRUD、Bulk API、Alias,還有那些你以為很簡單但其實到處是陷阱的操作。





