你是小阿巴,剛入職的后端程序員。
這天,產品經理給你安排任務:阿巴阿巴,咱們網站要加一個文章搜索功能。
你心想:簡單,直接寫一句 SQL 查詢數據庫就搞定了~
SELECT * FROM article WHERE title LIKE '%關鍵詞%'
結果上線沒幾天,就收到了大量用戶的投訴!
怎么什么搜索結果都沒有啊?
搜索結果亂七八糟,我想找的那篇內容竟然排在最后面?
搜索一次竟然要等好幾秒才出結果?什么破系統!
你汗流浹背了:明明 SQL 寫對了啊,難道是 MySQL 數據庫不行?
![]()
這時,號稱 "后端之狗" 的魚皮路過。他瞄了一眼你的代碼,嘲笑道:肯定要用 Elasticsearch 來做搜索功能啊!
你一臉懵:Elasticsearch?那是啥?
![]()
第一階段:認識 Elasticsearch
魚皮:Elasticsearch 簡稱 ES,是一個專門為搜索而生的分布式數據庫,也叫 搜索引擎數據庫。
它能存儲和管理大量文本數據,提供快速、準確、靈活的全文檢索功能。你剛才用 LIKE 查詢搞不定的那些問題,用 ES 都能輕松解決。
你撓了撓頭:真有這么神?
魚皮:當然。打個比方,MySQL 就像圖書館的書架,書按照分類整整齊齊地擺放著,你想找某本書得自己一排一排去翻;而 ES 就像圖書館的電子檢索系統,你輸入關鍵詞,它立刻就能告訴你書在哪兒,還會把最相關內容的排在最前面。
![]()
像全文搜索、日志分析、數據統計這些需要搜索能力的場景,ES 都能輕松搞定。
![]()
你眼前一亮:聽起來有點兒夯啊,那我趕緊裝一個試試。
第二階段:實戰應用 安裝 Elasticsearch
機智如你,直接打開 ES 官網 下載了安裝包:
![]()
并且成功安裝運行:
![]()
你:安裝好之后,我怎么操作它呢?
魚皮:ES 本身提供了 RESTful API,默認在 9200 端口提供服務,你可以用 curl 命令或者 Postman 等接口測試工具直接發 HTTP 請求來操作它。
![]()
不過對新手來說,更推薦先安裝一個官方的可視化工具 Kibana。
有了它,你可以直觀地查看分析數據、對數據進行操作。
![]()
只需要到官網下載安裝包并運行,啟動之后訪問本機的 5601 端口,就能打開 Kibana 的管理界面了。在開發工具控制臺里,你可以直接輸入查詢語句,能夠立刻看到結果,非常方便。
![]()
基本操作
下面我來帶你實操一波 ES 的基本操作。
1)首先是 創建索引。ES 的 索引(Index) 相當于 MySQL 里的表,是存放數據的容器。
![]()
創建索引的時候,還要定義 Mapping(映射),類似 MySQL 的表結構,用來規定每個字段的類型、是否需要分詞、使用什么分詞器等等。
![]()
在 Kibana 開發工具中輸入這段代碼:
PUT /article
{
"mappings": {
"properties": {
"title": { "type": "text", "analyzer": "standard" },
"content": { "type": "text" },
"tags": { "type": "keyword" },
"viewCount": { "type": "long" },
"isPublished": { "type": "boolean" },
"createTime": { "type": "date" }
}
}
}
這段代碼創建了一個叫 article 的索引。其中 text 類型表示需要分詞的文本字段,適合做全文檢索;keyword 類型不會分詞,適合存標簽、狀態這種需要精確匹配的內容。其他的類型就比較好理解了,long 存數字,boolean 存 true 或者 false,date 存日期。設計索引的時候要根據業務需求合理選擇字段類型。
2)然后是 插入文檔。文檔(Document) 相當于 MySQL 里的一行數據。ES 的文檔是用 JSON 格式存儲的,不需要像 MySQL 那樣提前定義好所有字段,而是隨時可以加新字段,非常靈活。
POST /article/_doc/1
{
"title": "魚皮的 Elasticsearch 入門教程",
"content": "魚皮帶你學習 ES",
"cover": "封面圖地址",
"tags": ["ES", "搜索"],
"viewCount": 1000,
"isPublished": true,
"createTime": "2025-01-30"
}
3)有了數據之后,就可以體驗 ES 最核心的能力 搜索文檔。比如在 article 索引中搜索標題包含 "魚皮教程" 的文章:
GET /article/_search
{
"query": {
"match": { "title": "魚皮教程" }
}
}
你執行完這條查詢,驚喜地發現:搜 "魚皮教程" 居然能匹配到 "魚皮的 ES 入門教程" 這篇文章!
![]()
魚皮點點頭:雖然標題里并沒有 "魚皮教程" 這 4 個連著的字,但因為 ES 會自動分詞,把 "魚皮" 和 "教程" 拆開分別匹配,所以就搜到了。
![]()
你感嘆道:哇,這才是搜索該有的樣子啊!
查詢語法 DSL
魚皮:沒錯,ES 的搜索能力非常靈活強大。剛才你寫的那些操作語句,其實用的就是 ES 的 DSL(Domain Specific Language 領域特定語言)。就像學數據庫要學 SQL 一樣,學 ES 就得學 DSL。不管是創建索引、插入文檔,還是搜索查詢,都是用這套 JSON 格式的語法來描述的。
![]()
其中最常用的就是查詢語法,常見的查詢類型有這么幾種:
match是全文檢索,會對搜索詞分詞之后再匹配term是精確匹配,不分詞,適合查 id、狀態這種bool可以組合多個條件,用must(必須滿足)、should(最好滿足)、must_not(必須不滿足)來靈活控制range用來做范圍查詢,比如查某個時間段內的數據。
你不需要背這些語法,用到的時候問 AI 或者查文檔就行,多寫幾次就熟了。
用代碼操作 ES
你皺了皺眉:感覺寫這些 JSON 格式的 DSL 還是有點麻煩啊,我用 Java 代碼操作 ES 的時候,總不會也要手動拼這堆 JSON 吧?
魚皮:當然不用!ES 官方提供了各種語言的客戶端。比如你用 Java 語言,對應的是 Java API Client,支持鏈式調用和類型安全。
![]()
對于 Spring 項目來說,更推薦用 Spring Data Elasticsearch,它可以讓你像用 MyBatis-Plus 操作 MySQL 一樣操作 ES。只需要定義一個實體類,加上 @Document 注解指定要操作的索引,再寫個 Repository 接口繼承依賴包內置的 ES 操作接口。
@Document(indexName = "article")
public class Article {
@Id
private Long id;
private String title;
private String content;
}public interface ArticleRepository extends ElasticsearchRepository {
// 根據標題搜索
ListfindByTitle(String title);
}
框架會根據方法名自動生成查詢邏輯,基本的增刪改查方法就自動實現了。
// 使用示例
// 插入文檔
articleRepository.save(article);
// 根據 id 查詢
articleRepository.findById(1L);
// 根據標題搜索
articleRepository.findByTitle("魚皮");
// 刪除文檔
articleRepository.deleteById(1L);
你感嘆道:這才是人寫的代碼啊!優雅,真是優雅~ 我這就給 Java 代碼整上 ES!
魚皮:要注意,ES 版本更新很快,你用的客戶端版本要跟安裝的 ES 服務保持一致,不然會出各種奇奇怪怪的 Bug。
![]()
第三階段:實用特性
學會了基本操作之后,你興沖沖地把 MySQL 數據庫里的文章數據全部導入到了 ES,然后把網站的搜索功能改成從 ES 查詢。上線后效果立竿見影,搜索又快又準,用戶好評如潮。
![]()
你非常開心:阿巴,俺可真厲害!
![]()
魚皮:不錯不錯,你已經掌握了 ES 的基本操作,算是學會 80% 了。不過 ES 還有很多值得學習的實用特性,進一步優化你的搜索功能。
倒排索引
魚皮:先來考考你,你知道為什么 ES 能搜得又快又準么?
你撓撓頭:阿巴阿巴……
魚皮笑道:關鍵在于它使用了 倒排索引 來存儲數據,這是 ES 最核心的特性。
舉個例子,假設咱們要存 3 篇博客文檔,用 MySQL 數據庫的話,存儲結構是這樣的:
文檔 id
文檔內容
1
感謝關注魚皮
2
魚皮是一名程序員
3
感謝關注編程導航
這種結構下,如果用戶搜 “魚皮程序員”,MySQL 會傻乎乎地把它當成一整個詞去匹配,結果可能啥也搜不到。
![]()
而 ES 的做法不一樣。它會先把文檔內容按照單詞進行切分,這個過程叫 分詞。然后再構建 單詞到文檔 id 的映射關系,也就是 倒排索引。
![]()
有了上述的倒排索引,當用戶搜索 “魚皮程序員” 時,搜索引擎數據庫會先對搜索詞進行分詞,得到 “魚皮” 和 “程序員”,然后根據這兩個詞匯就能找到文檔 id 1、2 了。不用再一行一行遍歷表內所有的數據,實現了更靈活、快速的 模糊搜索 。
![]()
你兩眼放光:原來如此,牛啊牛啊!
但是 ES 怎么知道一句話該拆成哪些詞呢?
分詞器
魚皮:好問題,這就要靠 分詞器 了,它負責把一段文本拆成一個個詞。
ES 內置了標準分詞器,它基于 Unicode 文本分割算法設計,會按空格和標點符號等來切分文本。但這個規則只適合英文,對中文基本是一個字一個字地拆,效果很差。
![]()
所以如果你要做中文搜索,必須安裝 IK 分詞器。它是專門為中文設計的,能夠智能識別中文詞匯的邊界,把句子正確地拆分成有意義的詞語。
IK 提供了兩種分詞模式:
ik_smart是智能分詞,盡量把詞分得少一點,比如 "好學生" 就只會拆成 "好學生" 一個詞ik_max_word是最大化分詞,能拆的都拆,"好學生" 會被拆成 "好學生"、"好學"、"學生" 三個詞。
一般建議索引的時候用 ik_max_word 盡可能多分詞,搜索的時候用 ik_smart 提高精確度。
此外,IK 還支持自定義詞典。比如你想讓 “程序員魚皮” 作為一個完整的詞不被拆開,加到詞典里就行了。
![]()
高亮顯示
你好奇道:既然 ES 能分詞,那能不能在搜索結果中把命中的關鍵詞標紅啊?
![]()
魚皮:當然可以,ES 支持 高亮顯示 功能。只需要在查詢里加個 highlight 參數,指定要高亮的字段就行:
GET /article/_search
{
"query": {
"match": { "title": "魚皮教程" }
},
"highlight": {
"fields": { "title": {} }
}
}
返回結果里,命中的關鍵詞會自動被 標簽包起來,前端拿到之后加個顏色樣式就搞定了。
![]()
你兩眼放光:這也太方便了吧!
![]()
不過還有個問題,現在雖然能夠搜索到內容了,但怎么把最相關的結果排到前面呢?
相關性評分
魚皮:好問題。ES 會給每個搜索結果計算一個分數,放到 _score 字段中,分數高的排在前面。
![]()
你好奇道:這個分數是怎么算的呢?
魚皮:ES 默認用的是 BM25 算法,主要考慮三個因素:
詞頻 ,關鍵詞在文檔里出現的次數越多,分數越高。這很好理解,一篇文章里反復提到 "魚皮",說明它很可能就是在講魚皮相關的內容。
文檔長度 ,同樣出現一次關鍵詞,在短文檔里占的比例更大,所以短文檔的分數會更高一點。
稀有度 ,如果一個詞在所有文檔里都很常見,比如 "的"、"是",那它對搜索結果的區分度就不大。反過來,如果一個詞很少見,只在少數文檔里出現,那命中這個詞的文檔就更有價值,分數也更高。
魚皮:除了搜索,ES 還有個很實用的功能叫 聚合分析,有點像 MySQL 的 GROUP BY 分組查詢。
比如你想統計每個標簽下有多少篇文章,寫個聚合查詢就行:
GET /article/_search
{
"size": 0,
"aggs": {
"tag_count": {
"terms": { "field": "tags" }
}
}
}
除了分組統計數量,ES 的聚合還能做求和、求平均值、找最大最小值、甚至多層嵌套聚合,能夠滿足開發各類數據報表的需求。
![]()
第四階段:生產環境實踐
用了一段時間 ES 后,你開始有點兒飄了。
沒事兒就對著新來的實習生阿坤吹牛皮:ES 我閉著眼睛都能寫!什么分詞、高亮、聚合,我都玩得賊溜兒~
![]()
結果沒多久,老板黑著臉找到你:有用戶投訴,說明明改了自己文章的標題,但是搜索出來還是舊的,怎么回事?
![]()
你排查后發現:原來是 ES 里的數據和 MySQL 數據庫里的不一樣!當初俺只是把數據一次性導入 ES,后來文章在數據庫里更新了,但 ES 里還是舊數據。
你有些頭大:唉,ES 和 MySQL 是兩套獨立的系統,數據不會自動同步啊,咋辦啊?
![]()
這時,旁邊的阿坤突然雞叫起來:我來!
數據同步方案
阿坤一邊打籃球一邊說:MySQL 和 ES 的數據同步,一般有這么幾種方案。
1)定時任務
每隔幾分鐘掃一遍數據庫,把最近更新的數據同步到 ES。優點是實現簡單,缺點是有一定延遲。適合數據更新不頻繁、對實時性要求不高的場景。
![]()
2)雙寫
每次把數據寫入 MySQL 的時候順便也寫一份到 ES。優點是能做到實時同步,缺點是會影響寫入性能,而且如果 ES 寫失敗了還得處理數據不一致的問題。適合數據寫入量不大的場景。
![]()
3)用 Logstash
它是 ES 官方提供的數據收集工具,可以配置從 MySQL 定時拉取數據同步到 ES。優點是不用寫代碼,全靠配置驅動,缺點是需要額外部署組件,靈活性也有限。
![]()
4)用 Canal 監聽數據庫
Canal 是阿里開源的一個工具,它會偽裝成 MySQL 的從庫,實時監聽數據庫的變更日志。數據庫一有改動,Canal 立刻就能感知到,然后同步到 ES。優點是能做到實時同步,缺點是部署和運維相對麻煩一點。
![]()
像咱們這個文章系統,更新又不頻繁,用戶也能接受幾分鐘的延遲,用定時任務就完全夠了。如果以后做電商那種對實時性要求高的系統,再考慮上 Canal。
集群部署
魚皮走過來拍了拍阿坤的肩膀:不錯不錯,我再考考你們,如果 ES 服務器掛了怎么辦?
你支支吾吾:重…… 重啟?
![]()
魚皮搖頭:用戶等得起嗎?
阿坤:生產環境肯定不能只部署一臺 ES 啊,得搭建 集群。
ES 集群中有幾種角色的節點。主節點 負責管理集群的狀態,比如哪些節點在線、索引的元數據等等。數據節點 負責存儲實際的數據,處理讀寫請求。一般生產環境至少部署 3 個節點,保證高可用。
![]()
魚皮追問:那如果數據量特別大,一個節點存不下怎么辦?
你眼前一亮,終于等到自己會的問題了,搶答道:刪除數據!
阿坤用看流浪狗的眼神看了你一眼,回答道:這就要說到 分片 了。分片就是把一個索引的數據拆成多份,分別存到不同的節點上。這樣單個節點存不下的海量數據,也能通過多節點分擔。而且多個節點可以并行處理查詢請求,性能也更好。
![]()
你有些不服氣:那萬一某個節點掛了,上面的數據不就丟了?
阿坤:所以還需要 副本。副本就是分片的備份。每個分片可以配置若干個副本,存在其他節點上。萬一某個節點掛了,副本可以頂上,這樣數據就不會丟失,服務也不會中斷。
![]()
其他生產實踐
魚皮拍了拍阿坤的肩膀:小伙子年輕有為啊!
這些都是 ES 在生產環境必須考慮的問題,此外還要學習:
深度分頁問題:ES 默認只允許查詢前 10000 條數據,再往后翻就會報錯。這是為了保護集群性能。如果確實需要給用戶深度翻頁,推薦使用更高效的 search_after。如果需要導出全量數據,可以結合 Point-in-Time API 使用。
性能調優技巧:合理設計 Mapping,該用 keyword 的別用 text;查詢的時候多用 filter 少用 query,因為 filter 會緩存結果;還有控制返回字段的數量,別動不動就查全部字段。
ELK 日志方案:ES 最經典的應用場景之一就是做日志系統。ELK 是三個組件的縮寫,E 是 Elasticsearch 負責存儲和搜索日志,L 是 Logstash 負責收集和處理日志,K 是 Kibana 負責可視化展示。大廠排查線上問題,基本都靠這一套。
你羞愧地抬不起頭:我以為自己已經掌握了 ES,原來只是學了個皮毛……
魚皮:小阿巴,你還要好好跟阿坤學習啊。
![]()
第五階段:深入原理
被連環拷問后,你主動找到阿坤:坤哥,我想深入學習 ES 的底層原理,你是怎么學的?
阿坤有些驚訝:咦?你不背八股文的么?去 面試刷題網站 - 面試鴨 刷刷題就好了呀!
![]()
你震驚了:現在的實習生,竟然恐怖如斯!
魚皮笑了笑:阿坤你別逗他了。其實可以帶著問題去學習,比如 ES 為什么這么快?
你搶答道:因為倒排索引!
魚皮:沒錯,但這只是一方面。ES 底層是基于 Lucene 搜索引擎庫的,它的倒排索引結構經過了高度優化。另外 ES 會把常用的數據緩存在內存里,查詢時優先從內存讀取,速度自然快。再加上 ES 是分布式的,可以把數據分片存儲到多個節點,并行處理查詢請求,幾方面加起來,性能就上去了。
![]()
再比如數據是怎么寫入的、查詢請求是怎么執行的?
從這些問題出發,去閱讀相關的文章,或者像阿坤說的刷一刷 ES 高頻面試題,就能快速學會很多核心知識點。
![]()
如果想系統學習,推薦看 ES 官方文檔,因為 ES 的更新太快了,很多書籍可能已經跟不上節奏了。
![]()
結尾
若干年后,你已經成為了公司的 ES 搜索專家。不僅能熟練使用 ES 解決各種搜索問題,搭個集群架構也是手拿把掐的。
你也像魚皮當時一樣,耐心地給新人分享學習 ES 的經驗,讓他們謹記一句話:ES 是實戰型技術,一定要多動手實踐!
![]()
再次遇到魚皮是在一條昏暗的小巷,此時的他年過 35,灰頭土臉。你什么都沒說,只是給他點了個贊,投了 2 個幣。
![]()
不打擾,是你的溫柔~
一些對大家有用的資源:
100+ 編程學習路線 / 實戰項目 / 求職指導
100+ 簡歷模板
300+ 企業面試題庫 mianshiya.com
500+ AI 資源大全
1 對 1 模擬面試
動畫學算法教程
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.