Elasticsearch 深度解析:從原理到實踐
一、為什么選擇 Elasticsearch?
數據模型
Elasticsearch 是基于文檔的搜索引擎,它使用 JSON 文檔來存儲數據。在 Elasticsearch 中,相關的數據通常存儲在同一個文檔中,而不是分散在多個表中。
MySQL 是一個關系型數據庫管理系統,它使用表、行和列的結構來組織數據。數據通過外鍵關系分散在多個表中。
查詢語言
Elasticsearch 使用 Query DSL(Domain Specific Language),這是一種非常靈活的查詢語言,基于 JSON,支持全文搜索、復合查詢、過濾以及聚合等。
MySQL 使用 SQL(Structured Query Language),這是一種強類型和非常成熟的語言,專門用于查詢和管理關系數據庫。
全文搜索
Elasticsearch 的核心功能是全文搜索。它對數據進行索引時會自動建立全文搜索索引,使其在搜索大量文本數據時表現優異。
MySQL 雖然也提供了基本的全文搜索功能,但其主要設計目標是處理結構化數據的存儲和查詢,對全文搜索的支持不如 Elasticsearch 那樣強大。
事務支持
Elasticsearch 不支持傳統的 ACID(原子性、一致性、隔離性、持久性)事務。雖然它確保了單個文檔操作的原子性,但不適用于跨多個文檔的復雜事務。雖然 Elasticsearch 不支持傳統的事務,但是他是可以確保單個文檔的更改(如創建、更新、刪除)是原子性的。這意味著任何文檔級的操作都完整地成功或完整地失敗,但不保證跨多個文檔或操作的一致性。
MySQL 支持 ACID 事務,這使得它非常適合需要嚴格數據一致性的應用,如金融服務和其他商業數據處理。
支持樂觀鎖:
Elasticsearch 支持通過使用文檔版本控制來實現樂觀鎖。
6.7之前。在 ES 中,每個文檔存儲時都有一個 _version 字段,這個版本號在每次文檔更新時自動增加。當我們執行更新、刪除或者使用腳本處理文檔時,可以指定這個版本號來確保正在操作的文檔是預期中的版本。如果操作中的版本號與存儲在索引中的文檔版本號不一致,說明文檔已被其他操作更改,當前操作將會失敗。(CAS)
為什么_version在后續版本被拋棄
_version 機制是基于單一遞增整數,它主要適用于簡單的沖突檢測,但在復雜的分布式系統中,僅依靠版本號可能無法準確地反映數據的歷史和復制狀態。尤其是在發生網絡分區或節點故障時,僅憑 _version 可能導致數據丟失或過時的數據被錯誤地寫入。
主要還是在高并發和高可用的環境下,單一的-version不足以處理因節點故障或網絡問題導致的多個副本之間的數據不一致問題。
然而if_seq_no 和 if_primary_term這兩個參數可以更準確地確定操作是否應當被執行,可以跟蹤每個文檔變更的序列號和主分片的期限。這種機制幫助確保即使在集群狀態發生變化(如分片重新分配到新的節點)的情況下,也不會應用基于過時副本的更新。它有效地防止了腦裂問題。
6.7之后,使用 _version 關鍵字進行樂觀鎖已經被廢棄了,替代方法是使用
if_seq_no (是一個遞增的序列號,表示文檔的每次修改)和
if_primary_term(表示主分片的當前任期,每當主分片發生變化時,這個值會增加。)?
來指定版本。
1. 核心優勢()
維度 | Elasticsearch | MySQL |
---|---|---|
數據模型 | 文檔型(JSON),動態映射 | 關系型(嚴格Schema) |
搜索能力 | 全文檢索、模糊匹配、語義分析 | 僅支持精確查詢和簡單LIKE |
擴展性 | 分布式架構,自動分片/副本 | 分庫分表復雜 |
吞吐量 | 單集群支持每秒10萬+查詢 | 高并發寫入更優 |
一致性 | 最終一致性(近實時) | 強一致性(ACID) |
Elasticsearch是一個開源的分布式搜索和分析引擎,主要適用于以下場景:
1:搜索引擎:用于快速檢索文檔、商品、新聞等。
2:日志分析:通過分析日志數據,幫助企業了解其業務的性能情況。
3:數據分析:幫助數據科學家和數據分析師進行數據分析,以獲取有價值的信息。
4:商業智能:幫助企業制定數據驅動的決策,以實現商業上的成功。
5:實時監控:幫助企業實時監測系統性能、監控數據變化,以保證系統正常運行。
6:安全性:幫助企業保證數據的安全性,保證數據不被非法竊取。
7:應用程序開發:幫助開發人員開發基于搜索的應用程序,以增加用戶體驗。
Elasticsearch具有以下幾個優勢:
1:高性能:Elasticsearch具有高性能的搜索和分析能力,其中涵蓋了多種查詢語言和數據結構。
2:可擴展性:Elasticsearch是分布式的,可以通過增加節點數量擴展搜索和分析能力。
3:靈活性:Elasticsearch支持多種數據類型,支持多種語言,支持動態映射,允許快速地調整模型以適應不同的需求。
4:實時分析:Elasticsearch支持實時分析,可以對數據進行實時查詢,這對于快速檢索數據非常有用。
5:可靠性:Elasticsearch具有可靠性和高可用性,支持數據備份和恢復。
ES為什么塊:
1:分布式存儲:Elasticsearch使用分布式存儲技術,將數據存儲在多個節點上,從而減少單個節點的壓力,提高整體性能。
2:索引分片:Elasticsearch把每個索引劃分成多個分片,這樣可以讓查詢操作并行化,從而提高查詢速度。
3:全文索引:Elasticsearch使用了高效的全文索引技術,把文檔轉化成可搜索的結構化數據,使得搜索操作快速高效。
4:倒排索引:Elasticsearch支持倒排索引這種數據結構,倒排索引將文檔中的每個詞與該詞出現在哪些文檔中進行映射,并存儲這些信息。當搜索請求發生時,ES可以快速查找包含所有搜索詞的文檔,從而返回結果。
5:索引優化:Elasticsearch通過索引優化技術,可以使查詢速度更快。例如,它支持索引覆蓋、索引下推等優化技術,使得查詢速度更快。
6:預存儲結果:Elasticsearch在插入數據時,對數據進行預處理,把結果預存儲到索引中,從而在查詢時不需要再重新計算,提高查詢速度。
7:高效的查詢引擎:Elasticsearch使用了高效的查詢引擎,支持各種類型的查詢,并對復雜查詢提供了優化策略,從而提高查詢速度。
8:異步請求處理:ES使用了異步請求處理機制,能夠在請求到達時立即返回,避免長時間的等待,提高用戶體驗。
9:內存存儲:ES使用了內存存儲技術,能夠在讀寫數據時大大減少磁盤訪問次數,提高數據存儲和查詢效率。
我覺得最重要就是倒排索引了:它?用于快速搜索文檔中的某個詞匯。
假設我們有一個原始文檔:
文檔ID | 文檔內容 |
---|---|
Doc1 | "Elasticsearch is fast" |
Doc2 | "Redis is fast too" |
Doc3 | "Elasticsearch and Redis" |
主要分為倆個過程:
-
分詞(Tokenization):
-
Doc1 → ["elasticsearch", "is", "fast"]
-
Doc2 → ["redis", "is", "fast", "too"]
-
Doc3 → ["elasticsearch", "and", "redis"]
-
-
生成詞項-文檔映射:
Term DocIDs (Postings List) ───────────────────────────────────── "and" → [Doc3] "elasticsearch" → [Doc1, Doc3] "fast" → [Doc1, Doc2] "is" → [Doc1, Doc2] "redis" → [Doc2, Doc3] "too" → [Doc2]
3. 倒排索引的核心組件
組件 | 作用 | 示例 |
---|---|---|
詞項字典(Term Dictionary) | 存儲所有唯一詞項,通常用?FST(有限狀態轉換器)?壓縮存儲 | "elasticsearch", "redis" |
倒排列表(Postings List) | 記錄包含該詞項的文檔ID及位置信息(支持快速跳表SkipList優化遍歷) | Doc1:[pos1,pos2], Doc3:[pos1] |
詞頻(TF) | 詞項在文檔中的出現次數(用于相關性評分) | "fast"在Doc1中TF=1 |
文檔頻率(DF) | 包含該詞項的文檔數(用于IDF計算) | "elasti |
?倒排索引的優化技術
-
壓縮存儲:
-
FST:壓縮詞項字典,減少內存占用。
-
Delta Encoding:對文檔ID差值編碼(如[100, 102, 105]→[100, +2, +3])。
-
-
跳表(SkipList):
-
加速倒排列表的合并操作(如
AND
查詢)。
-
-
多級索引:
-
內存中保留熱點詞項,磁盤存全量數據。
-
二、Elasticsearch 核心技術細節
1. 分詞器(Analyzer)
分詞器類型 | 功能說明 | 示例 |
---|---|---|
Standard | 默認分詞器,按空格/標點切分 | "Elasticsearch" → ["elasticsearch"] |
IK Analyzer | 中文分詞(社區版+擴展詞典) IK分詞器有幾種模式?
IK分詞器如何拓展詞條?如何停用詞條?
| "華為手機" → ["華為", "手機"] |
Pinyin | 中文轉拼音搜索 | "北京" → ["beijing", "bj"] |
Whitespace | 僅按空格切分 | "hello world" → ["hello", "world"] |
Keyword | 不分詞,整體作為Term | "2023-08-15" → ["2023-08-15"] |
自定義分詞器:
PUT /my_index {"settings": {"analysis": {"analyzer": {"my_ik": {"type": "custom","tokenizer": "ik_max_word","filter": ["lowercase"]}}}} }
2. 底層原理
-
倒排索引(Inverted Index):
-
Term → Document ID?的映射(如“手機” → [Doc1, Doc3])。
-
通過?FST(Finite State Transducer)?壓縮存儲,減少內存占用。
-
-
分片(Shard)機制:
-
索引自動拆分為多個分片(默認5個),分散到不同節點。
-
每個分片有1個主副本和N個從副本(通過
_settings
調整)。
-
-
近實時(NRT):
-
數據寫入后默認1秒(
refresh_interval
)可被搜索,通過?translog?保證持久化。
-
3. 查詢高效的原因
-
分布式計算:
-
查詢并行發送到所有分片,結果聚合(Scatter-Gather模式)。
-
-
緩存優化:
-
Query Cache:緩存過濾條件結果。
-
Fielddata:文本字段啟用內存緩存(慎用,易OOM)。
-
-
列式存儲(Doc Values):
-
對排序、聚合字段預先構建磁盤數據結構,避免實時計算。
-
4. 增刪改操作
-
寫入流程:
-
請求發送到協調節點。
-
路由到對應分片的主副本。
-
寫入Lucene內存Buffer和translog。
-
定期刷新(Refresh)生成新Segment(可搜索)。
-
后臺合并(Merge)Segment優化存儲。
-
-
刪除:
-
標記文檔為
deleted
,Merge時物理刪除。
-
-
更新:
-
先刪除舊文檔,再寫入新文檔(版本號
_version
遞增)。
-
三、關鍵問題解決方案
1. 深度分頁優化
-
問題:
from 10000, size 10
?需遍歷所有分片的10010條記錄。在Elasticsearch中進行分頁查詢通常使用from和size參數。當我們對Elasticsearch發起一個帶有分頁參數的查詢(如使用from和size參數)時,ES需要遍歷所有匹配的文檔直到達到指定的起始點(from),然后返回從這一點開始的size個文檔。跟MySQL的深度分頁原因差不多, -
方案:
-
Search After:search_after 是 Elasticsearch 中用于實現深度分頁的一種機制。與傳統的分頁方法(使用 from 和 size 參數)不同,search_after 允許你基于上一次查詢的結果來獲取下一批數據,這在處理大量數據時特別有效。
在第一次查詢時,你需要定義一個排序規則。不需要指定 search_after 參數:{"query": { "match_all": {} },"size": 10,"sort": [{"_id": "asc"}],//在第一次查詢時,你需要定義一個排序規則。"search_after": [last_id] //第一次查詢不要定義,在后續的查詢中,使用上一次查詢結果中最后一條記錄的排序值 //search_after包含timestamp和last_id 的值,對應上一次查詢結果的最后一條記錄。}
search_after 可以有效解決深度分頁問題,原因如下:
避免重復處理數據:與傳統的分頁方式不同,search_after 不需要處理每個分頁請求中所有先前頁面上的數據。這大大減少了處理數據的工作量。
提高查詢效率:由于不需要重復計算和跳過大量先前頁面上的數據,search_after 方法能顯著提高查詢效率,尤其是在訪問數據集靠后部分的數據時。
但是這個方案有一些局限,一方面需要有一個全局唯一的字段用來排序,另外雖然一次分頁查詢時不需要處理先前頁面中的數據,但實際需要依賴上一個頁面中的查詢結果。 -
適用于深度分頁和大數據集的遍歷。
-
-
-
Scroll API:Scroll API在Elasticsearch中的主要目的是為了能夠遍歷大量的數據,它通常用于數據導出或者進行大規模的數據分析。可以用于處理大量數據的深度分頁問題。
-
POST /_search/scroll { "scroll": "1m", "scroll_id": "DXF1ZXJ5QW..." }
//如上方式初始化一個帶有scroll參數的搜索請求。這個請求返回一個scroll ID,用于后續的滾動。Scroll參數指定了scroll的有效期,例如1m表示一分鐘。
//接下來就可以使用返回的scroll ID來獲取下一批數據。每次請求也會更新scroll ID的有效期。 -
業務妥協:限制最大頁碼(如只展示前100頁)。
-
避免重復排序:在傳統的分頁方式中,每次分頁請求都需要對所有匹配的數據進行排序,以確定分頁的起點。Scroll避免了這種重復排序,因為它保持了一個游標。
-
穩定視圖:Scroll提供了對數據的“穩定視圖”。當你開始一個scroll時,Elasticsearch會保持搜索時刻的數據快照,這意味著即使數據隨后被修改,返回的結果仍然是一致的。
-
減少資源消耗:
由于不需要重復排序,Scroll減少了對CPU和內存的消耗,特別是對于大數據集。 -
Scroll非常適合于處理需要訪問大量數據但不需要快速響應的場景,如數據導出、備份或大規模數據分析。
但是,需要知道,使用Scroll API進行分頁并不高效,因為你需要先獲取所有前面頁的數據。Scroll API主要用于遍歷整個索引或大量數據,而不是用于快速訪問特定頁數的數據。
-
2. 數據一致性
第一種方法:雙寫一致性
對MySQL和ES進行雙寫,先更新數據庫,再更新ES,放在一個事務里面,需要保證事務的一致性。有數據不一致的風險,比如MySQL寫入成功,但是ES發生了寫入成功但因為超時拋異常了就會出現數據不一致的情況。
第二種方法:使用消息隊列MQ進行處理
在更新數據庫時,發送一個消息到MQ中,然后數據庫和ES各設置一個監聽者,監聽消息之后各自去做數據變更,如果失敗了就基于消息的重試在重新執行。
好處是進行了異步解耦,性能稍后,但是有延遲
第三種方法:定時掃描MySQL
如果ES中的數據變更的實時性要求不高,可以考慮定時任務掃表來批量更新ES。
這個方案優點是沒有侵入性,數據庫的寫操作處不需要改代碼。
缺點是實時性很差,并且輪詢可能存在性能問題、效率問題以及給數據庫帶來壓力。
第四種方法:監聽binlog日志(基于canal做數據同步的方案)
對業務代碼完全沒有侵入性,業務也非常解耦,不需要關心這個ES的更新操作。
缺點就是需要基于binlog監聽,需要引入第三方框架。存在一定的延遲。
一致性級別 | 實現方案 | 優缺點 |
---|---|---|
強一致性 | 寫入后立即refresh (性能差) | 數據最新,吞吐量下降 |
最終一致性 | 默認1秒刷新 + 手動_refresh (平衡選擇) | 性能高,短暫延遲 |
與Canal集成:主要的過程
-
MySQL Binlog?→?Canal Server?解析變更。
-
Canal Client?→?Kafka?發送變更事件。
-
Logstash/自定義Consumer?→?寫入ES。
關于Logstash和自定義Consumer
. Logstash 是什么?
-
定位:開源的數據處理管道工具,屬于 Elastic Stack(ELK)的一部分。
-
核心功能:
-
數據輸入(Input):從 Kafka、MySQL、文件等讀取數據。
-
數據過濾(Filter):清洗、轉換、豐富數據(如字段重命名、類型轉換)。
-
數據輸出(Output):將處理后的數據寫入 ES、MySQL、文件等。
-
-
特點:
-
配置驅動:通過配置文件定義數據處理流程,無需編碼。
-
插件化:支持 200+ 官方/社區插件(如?
kafka
、elasticsearch
、grok
)。
-
Canal → ES 場景中的用途
將 Canal 發送到 Kafka 的 MySQL Binlog 數據轉換為 ES 所需的格式,并寫入 ES。
2. 自定義 Consumer 是什么?
-
定位:用戶自行編寫的程序(通常用 Java/Python/Go),用于消費 Kafka 消息并處理。
-
核心功能:
-
從 Kafka 拉取 Canal 生成的 Binlog 消息。
-
解析消息并轉換為 ES 支持的格式(如 JSON)。
-
調用 ES 的 API 寫入數據。
-
-
特點:
-
靈活可控:可自定義業務邏輯(如特殊字段處理、錯誤重試)。
-
高性能:通過批量寫入、異步請求等優化吞吐量。
-
整體架構流程:
3. ES支持的數據結構
-
核心類型:
-
Text:用于存儲全文文本數據。
-
Keyword:用于存儲文本值,通常用于索引結構化內容,如郵件地址、標簽或任何需要精確匹配的內容。
-
Nested:類似于 Object 類型,但用于存儲數組列表,其中列表中的每個元素都是完全獨立且可搜索的。
-
Long, Integer, Short, Byte, Double, Float: 這些是數值類型,用于存儲各種形式的數字。
-
Date: 存儲日期或日期和時間。
-
Boolean: 存儲 true 或 false 值。
-
Binary: 用于存儲二進制數據。
-
Object: 用于嵌套文檔,即文檔內部可以包含文檔。
-
-
特殊類型:
-
Flattened:避免深層JSON字段爆炸。
-
Join:父子文檔(性能較差,慎用)。
-
-
text 和 keyword 有啥區別?
text 類型被設計用于全文搜索。這意味著當文本被存儲為 text 類型時,Elasticsearch 會對其進行分詞,把文本分解成單獨的詞或短語,便于搜索引擎進行全文搜索。因為 text 字段經過分詞,它不適合用于排序或聚合查詢。
text適用于存儲需要進行全文搜索的內容,比如新聞文章、產品描述等。
keyword 類型用于精確值匹配,不進行分詞處理。這意味著存儲在 keyword 字段的文本會被當作一個完整不可分割的單元進行處理。因為 keyword 類型字段是作為整體存儲,它們非常適合用于聚合(如計數、求和、過濾唯一值等)和排序操作。由于不進行分詞,keyword 類型字段不支持全文搜索,但可以進行精確匹配查詢。
keyword適用于需要進行精確搜索的場景,比如標簽、ID 編號、郵箱地址等。 -
ES 不支持 decimal,如何避免丟失精度?
? ? ? ? 五種方法:
? ? ? ?一:使用字符串類型(比較好用)
? ? ? ? ? ? ? ? ?完全保留數字的精度。簡單易于實現,數據遷移時不需特別處理。但是作為字符串存儲的數字不能直接用于數值比較或數學運算,需要在應用層處理轉換。但也不是什么大毛病。
? ? ? 二:擴大浮點類型的精度
? ? 就是直接使用 double 類型,在理論上可能會有精度損失,但實際上 double 類型提供的精度對于許多業務需求已經足夠使用。需要接受減小精度損失。
??????三:使用scaled_float(推薦)
????????Elasticsearch 的 scaled_float 類型是一種數值數據類型,專門用于存儲浮點數。其特點是通過一個縮放因子(scaling factor)將浮點數轉換為整數來存儲,從而在一定范圍內提高存儲和計算的效率。他使用一個縮放因子將浮點數轉換為整數存儲。例如,如果縮放因子是 100,那么值 123.45 會存儲為 12345。這樣可以避免浮點數存儲和計算中的精度問題。
? ? ? 四:使用多個字段
????????在某些情況下,可以將 decimal 數值拆分為兩個字段存儲:一個為整數部分,另一個為小數部分。這樣做可以在不丟失精度的情況下,將數值分開處理。可以保持數值精確,同時可進行部分數學運算。但是增加了數據處理的復雜性,需要在應用層重建數值。
? ? ? 五:使用自定義腳本
????????用 Elasticsearch 的腳本功能(如 Painless 腳本)來處理數值計算,確保在處理過程中控制精度。優點:靈活控制數據處理邏輯。缺點:可能影響查詢性能,增加系統復雜性。
四、實際應用場景
1. 電商搜索
-
需求:支持顏色、品牌、價格區間等多維度過濾。
-
實現:
{"query": {"bool": {"must": [{ "term": { "brand": "華為" } },{ "range": { "price": { "gte": 1000, "lte": 2000 } } }],"filter": { "term": { "color": "紅色" } }}},"aggs": {"price_stats": { "stats": { "field": "price" } }} }
2. 日志告警
-
需求:實時檢測ERROR日志并觸發告警。
-
實現:
-
Watcher?插件定時查詢:
{"query": { "match": { "level": "ERROR" } },"condition": { "compare": { "ctx.hits.total": { "gt": 0 } } },"actions": { "email": { "to": "admin@example.com" } } }
-
3. 數據同步容災
-
雙寫方案:
-
應用同時寫MySQL和ES,通過本地事務表記錄同步狀態。
-
-
補償機制:
-
定時任務掃描MySQL與ES差異數據,修復不一致。
-
五、性能調優
集群和硬件優化
????????負載均衡: 確保查詢負載在集群中均衡分配。
????????硬件資源: 根據需要增加 CPU、內存或改善 I/O 性能(例如使用 SSD)。
????????配置 JVM: 優化 JVM 設置,如堆大小,以提高性能。
-
硬件:SSD磁盤、32GB+內存(堆內存不超過31GB)。
-
索引設計:
-
-
選擇合適的分詞器
冷熱分離:hot-warm
架構(熱數據用SSD,冷數據用HDD)。 -
生命周期管理(ILM):自動滾動到新索引。
-
-
查詢優化:
-
避免
wildcard、
regexp查詢(如*test*
)。 -
使用
constant_score
過濾不相關文檔。 -
使用過濾器: 對于不需要評分的查詢條件,使用 filter 而不是 query,因為 filter 可以被緩存以加快后續相同查詢的速度。
-
合理使用聚合:聚合可以用于高效地進行數據分析,但復雜的聚合也可能非常消耗資源。優化聚合查詢,如通過限制桶的數量,避免過度復雜的嵌套聚合。
-
查詢盡可能少的字段: 只返回查詢中需要的字段,減少數據傳輸和處理時間。
-
避免深度分頁: 避免深度分頁,對于需要處理大量數據的情況,考慮使用 search_after。
-
總結
-
選型:ES適合搜索/分析,MySQL適合事務/精準查詢。
-
核心能力:分詞器、倒排索引、分布式計算支撐高效查詢。
-
一致性:通過
refresh
、Canal同步等方案平衡實時性與性能。 -
實踐:結合業務場景選擇分頁策略、數據結構、同步機制。