你去過 Costco 嗎?100 萬件商品堆在倉庫裡,你要找一瓶特定的醬油。

用 MySQL 的 LIKE '%醬油%' 來找,等於從第一排貨架開始一排一排翻。Costco 那個面積,翻完天都黑了。但如果門口站了一個超級管理員,他記住了每個字出現在哪些商品上——你說「醬油」,他毫秒級回你:「C7 排第三層、D2 排第一層。」

Elasticsearch 就是那個超級管理員。底層用 Apache Lucene 的倒排索引(Inverted Index),把「每個詞 → 出現在哪些文件」這件事預先算好。搜尋的時候不是遍歷,是查表。

這是為什麼它快。快不是魔法,是資料結構。


從搜食譜到紐約上市

ES 的起源是一段聽起來不太正經的故事。2004 年,Shay Banon 的老婆在學做菜,他想幫她建一個食譜搜尋工具。於是寫了 Compass——一個 Lucene 的 Java 封裝。

六年後他把 Compass 整個重寫,2010 年以 Elasticsearch 的名字開源。2012 年成立公司,2018 年在紐約證交所上市,股票代碼 ESTC。

中間幾個關鍵版本轉折:

  • 7.0(2019):廢除 Mapping Type。這是面試必考題,後面會講為什麼。
  • 8.0(2021):預設啟用安全性 + 原生向量搜尋 kNN。很多人從 7.x 升上來打 API 結果 401,就是因為 8.x 預設要帳密和 HTTPS。
  • 2023-2025:ES|QL 查詢語言、Serverless 架構、向量 + 傳統混合檢索。RAG 整合大爆發的時期。

從食譜到上市公司,14 年。有時候好東西的起點就是「我老婆需要這個」。


ELK Stack 家族——不是只有 E

面試被問「ELK Stack 是什麼」,只回答 Elasticsearch 會被扣分。Elastic Stack 是一整個家族,每個成員各司其職。

Elasticsearch 是核心大腦。儲存、索引、搜尋,全文檢索加結構化查詢加聚合分析加向量搜尋。它是「資料庫 + 搜尋引擎」的合體。

Logstash 是 ETL 工具。從各種來源收集資料——檔案、Kafka、DB、HTTP——過濾轉換後送進 ES。但它是 JVM 寫的,吃記憶體。小場景不需要動到它。

Kibana 是 ES 的眼睛。Dashboard、Dev Tools、Maps、Machine Learning、Alerting,所有視覺化和管理操作都在這裡。等一下的 Hello World 就在 Kibana 的 Dev Tools 裡面打。

Beats 是用 Go 寫的輕量 agent,部署在每台主機上搬資料。Filebeat 搬 log(最常用)、Metricbeat 搬系統指標、Packetbeat 搬網路封包、Heartbeat 搬健康檢查。比 Logstash 輕量得多,吃不到它十分之一的記憶體。

還有一個新角色:Elastic Agent。它是 Beats 的下一代整合版,一個 Agent 搞定所有資料收集,透過 Fleet Server 統一管理。Elastic 官方正在推動從 Beats 遷移過去。

整個資料流向長這樣:

1
2
3
4
5
6
[資料來源]
├── Filebeat ──────┐
├── Metricbeat ────┤
├── Elastic Agent ──┤──▶ Elasticsearch ──▶ Kibana
└── Logstash ──────┘
(ETL) (儲存+搜尋) (視覺化)

每個元件都可以獨立部署。你不一定需要全家桶——很多團隊只用 ES + Kibana + Filebeat 就搞定了 80% 的場景。


Docker Compose 實戰:一行指令搞定

以下是一份 ES 8.17 + Kibana 的單節點 Docker Compose 設定。我加了 healthcheck,讓 Kibana 等 ES 真正啟動後才連線。這步不加的話,業界最常踩的坑就是 Kibana 先起來但 ES 還沒準備好,紅字一片。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
services:
es01:
image: docker.elastic.co/elasticsearch/elasticsearch:8.17.0
container_name: es01
environment:
- node.name=es01
- cluster.name=es-docker-cluster
- discovery.type=single-node
- ELASTIC_PASSWORD=changeme
- xpack.security.enabled=true
- xpack.security.http.ssl.enabled=false
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms1g -Xmx1g"
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- esdata01:/usr/share/elasticsearch/data
ports:
- "9200:9200"
networks:
- elastic
healthcheck:
test: curl -s -u elastic:changeme http://localhost:9200 || exit 1
interval: 10s
timeout: 5s
retries: 10

kibana:
image: docker.elastic.co/kibana/kibana:8.17.0
container_name: kibana
environment:
- ELASTICSEARCH_HOSTS=http://es01:9200
- ELASTICSEARCH_USERNAME=kibana_system
- ELASTICSEARCH_PASSWORD=changeme
- xpack.security.enabled=true
ports:
- "5601:5601"
depends_on:
es01:
condition: service_healthy
networks:
- elastic

volumes:
esdata01:
driver: local

networks:
elastic:
driver: bridge

儲存為 docker-compose.yml,然後:

1
docker compose up -d

等 30-60 秒讓叢集啟動。ES 8.x 安全性預設開啟,所以還需要設定 kibana_system 的密碼:

1
2
3
4
curl -s -u elastic:changeme -X POST \
'http://localhost:9200/_security/user/kibana_system/_password' \
-H 'Content-Type: application/json' \
-d '{"password":"changeme"}'

驗證一下叢集狀態:

1
curl -u elastic:changeme http://localhost:9200/_cluster/health?pretty

你會看到 status: yellow。別慌——單節點叢集的 health 永遠是 yellow,因為 Replica 找不到另一台機器可以放。正式環境至少 3 個節點才會看到 green。


Kibana Dev Tools——第一支 API

打開瀏覽器到 http://localhost:5601,用 elastic / changeme 登入。左邊選單找到 Dev Tools(或直接 Ctrl/Cmd + / 快速開啟)。

先看叢集健康:

1
GET _cluster/health

建立第一個 Index:

1
2
3
4
5
6
7
PUT /my-first-index
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 0
}
}

number_of_replicas 設 0 是因為單節點沒地方放副本。正式環境至少設 1。

寫入第一筆文件:

1
2
3
4
5
6
7
POST /my-first-index/_doc/1
{
"title": "Hello Elasticsearch",
"message": "我的第一筆文件",
"timestamp": "2026-04-29T10:00:00",
"tags": ["tutorial", "hello-world"]
}

搜尋看看:

1
2
3
4
5
6
7
8
GET /my-first-index/_search
{
"query": {
"match": {
"message": "第一筆"
}
}
}

match 查詢會做全文搜尋——先分詞,再用倒排索引比對。不是字串精確匹配。這是 ES 最核心的搜尋方式。

查看 Mapping:

1
GET /my-first-index/_mapping

你會看到 ES 自動推斷出的欄位型別。timestamp 會被推斷為 datetagstext + keyword。這叫 dynamic mapping——方便,但有坑。型別一旦推斷就鎖死了,設錯只能 reindex 重建。正式環境務必先手動定義 mapping。


五個會讓你加班的坑

JVM 記憶體設太大ES_JAVA_OPTS 別超過實體記憶體的 50%。另外 50% 要留給 Lucene 的 filesystem cache。超過 32GB 還會失去 Compressed OOPs 優化,效能反而更差。

Production 跑 single-nodediscovery.type=single-node 只適合開發環境。正式環境至少 3 個 master-eligible 節點,一掛全掛不是開玩笑。

Linux 忘記設 vm.max_map_count:ES 需要 sysctl -w vm.max_map_count=262144,不然啟動直接報錯。Docker Desktop 內建處理了這個,但 Linux 伺服器上要自己來。

8.x 安全性預設開啟:不少人照 7.x 教學打 API 結果 401,就是因為 8.x 預設需要帳密。踩這個坑的人多到 Stack Overflow 上相關問題的瀏覽量破百萬。

Mapping 建完不能改:欄位型別一旦確定就鎖死。要改只能建新 Index 然後 reindex。聽起來很麻煩——但這是倒排索引的物理限制,不是 ES 故意刁難你。


搜尋引擎的世界有兩條線在拉扯:寫入的方便查詢的速度

傳統資料庫選了寫入方便——資料進來就存,查的時候再慢慢翻。ES 選了查詢速度——資料進來的時候就先建好索引,查的時候查表就好。代價是寫入時要多做一步(建倒排索引),而且 schema 一旦定了就很難改。

這不是好壞的問題,是取捨。知道自己選了什麼、放棄了什麼,就不會在半夜被 Mapping 炸到的時候覺得 ES 在整你。

下一篇我們打開引擎蓋——Node、Shard、Lucene、Segment,搞懂 ES 底層是怎麼把資料切開、存起來、搜出來的。

參考來源:Elasticsearch 官方文件
參考來源:Elastic Stack 概觀