淺談ElasticSearch的那些事兒

Part1:獲取信息的訴求
我們正處于一個信息過載的時代,有用的信息、無用的信息夾雜在一起。
我們有從海量信息中獲取數(shù)據(jù)的訴求,搜索和推薦就是兩個有效的工具。
- 搜索:想好要什么,然后去查詢、再從眾多結(jié)果中獲取對你有用的。
 - 推薦:根據(jù)你的用戶畫像等信息,來主動推送你可能感興趣的信息。
 
ChatGPT本質(zhì)上說是一種更加高效準確的獲取信息的渠道,至少對于我來說,有效減少了從傳統(tǒng)搜索引擎眾多結(jié)果中過濾有效信息的成本。
Part2:讓搜索飛入尋常百姓家
曾幾何時,搜索引擎也是技術(shù)壁壘較深的一個領(lǐng)域,像谷歌、百度等老牌網(wǎng)頁搜索引擎公司,就靠著搜索和廣告賺得盆滿缽滿。
時間拉到現(xiàn)在,各類app層出不窮,搜索不再均限于網(wǎng)頁,更多的是app內(nèi)的垂直領(lǐng)域檢索,像小紅書、知乎、淘寶、拼多多都是如此。
再具體到一些我們?nèi)粘5臉I(yè)務(wù)場景,也同樣有檢索訴求:篩選檢索和模糊檢索。

以常用的找房app為例,可以輸入商圈名稱、小區(qū)名稱,當然這些都不需要非常具體,檢索框就可以進行提示,同時還可以根據(jù)具體的條件做篩選,比如面積、朝向、總價等。
這些都可以用es來實現(xiàn),再有上層的排序邏輯,基本上就可以實現(xiàn)面向用戶的檢索功能了。
Part3Lucene和ElasticSearch
在聊es之前,就必須要提lucene,如果把es看做是豪車,lucene則是這輛豪車的發(fā)動機,真可謂是es的核心部件。
1、Lucene的誕生
Luene是一款高性能、可擴展的信息檢索庫,用于完成文檔元信息、文檔內(nèi)容等搜索功能。
用戶可以使用Lucene來快速構(gòu)建搜索服務(wù),如文件搜索、網(wǎng)頁搜索等,它是一個索引和搜索庫,不包含爬取和HTML解析功能。
1985年 Doug Cutting畢業(yè)于美國斯坦福大學,在1999年編寫了Lucene,他是一位資深的全文索引及檢索專家,曾經(jīng)是V-Twin搜索引擎的主要開發(fā)者,后來在Excite擔任高級系統(tǒng)架構(gòu)設(shè)計師,目前從事于一些互聯(lián)網(wǎng)底層架構(gòu)的研究。
值得一提的是,Doug Cutting還是大名鼎鼎的Hadoop之父,大牛果然是高產(chǎn)。
2、elasticsearch的誕生
有了Lucene之后,那么es又是怎么誕生的呢?這就要提到一個曾經(jīng)的待業(yè)青年 Shay Banon。
當年他還是一個待業(yè)工程師,跟隨自己的新婚妻子來到倫敦,妻子想在倫敦學習做一名廚師,而自己則想為妻子開發(fā)一個方便搜索菜譜的應(yīng)用,所以才接觸到 Lucene。
直接使用 Lucene 構(gòu)建搜索有很多問題,包含大量重復性的工作,所以 Shay Banon 便在 Lucene 的基礎(chǔ)上不斷地進行抽象,讓 Java 程序嵌入搜索變得更容易,經(jīng)過一段時間的打磨便誕生了他的第一個開源作品Compass。
之后,他找到了一份面對高性能分布式開發(fā)環(huán)境的新工作,在工作中他漸漸發(fā)現(xiàn)越來越需要一個易用的、高性能、實時、分布式搜索服務(wù),于是決定重寫 Compass,將它從一個庫打造成了一個獨立的 server,并創(chuàng)建了開源項目。
第一個公開版本出現(xiàn)在 2010 年 2 月,在那之后 Elasticsearch 已經(jīng)成為 Github 上最受歡迎的項目之一。
后來和幾個志同道合的技術(shù)狂人一起把es做大做強,最后敲鐘上市,從待業(yè)青年成為了億萬富翁,還真是勵志!
Part4:ElasticSearch架構(gòu)原理
我們前面提到,es是基于Lucene打造的開源檢索組件,Lucene只是一個裸信息檢索庫,而es要做的就是解決Lucene到業(yè)務(wù)場景的最后一公里問題。
當我們嘗試去學習一個組件時,不妨把我們自己當做組件的研發(fā)者,抱著去做一款產(chǎn)品的思維來看,或許可以更清晰。
在聊es的架構(gòu)和原理之前,我們也反客為主去思考下,es的目標有哪些:
- 簡單的交互模式、支持多種語言
 - 支持海量數(shù)據(jù)、高效檢索效率
 - 支持實時/準實時地低時延檢索
 - 支持高并發(fā)&高可用場景
 
3、分布式系統(tǒng)的引入
要解決海量數(shù)據(jù)檢索、高并發(fā)、高可用等問題,就必須要引入分布式系統(tǒng),集群模式下吞吐量和穩(wěn)定性都能有保證。
在es集群中每臺機器從不同角度看有不同的角色,其中重要的幾個包括:
- Master Node 負責監(jiān)控和協(xié)調(diào)工作,保證整個集群的穩(wěn)定性,管事的節(jié)點
 - Work Node 負責數(shù)據(jù)的寫入和讀取,也就是干活的節(jié)點
 - Coordinating Node 協(xié)調(diào)節(jié)點,負責請求的路由分發(fā)等,每個節(jié)點都可以是協(xié)調(diào)節(jié)點,同時也可以只負責協(xié)調(diào),不做具體的數(shù)據(jù)處理工作
 - Master-eligible node 候選主節(jié)點,作為Master節(jié)點的接班人之一,可以參與投票和競選新的Master節(jié)點
 
再細分的角色還有很多,在此不展開了,實際上分布式系統(tǒng)中各個節(jié)點的角色和要做的事情,基本都差不多,和人類社會運行中的各個角色都非常相似。
引入分布式系統(tǒng)之后,就會面臨很多新的問題:網(wǎng)絡(luò)延遲、消息丟失、集群腦裂、故障容錯和恢復、一致性、共識問題、選舉問題、等。
分布式系統(tǒng)也是一把雙刃劍,但是其帶來的好處遇大于問題,在分布式基礎(chǔ)理論和基礎(chǔ)算法的加持下,讓分布式系統(tǒng)應(yīng)用于生產(chǎn)實踐成為了現(xiàn)實。
4、高可用和高并發(fā)
基于分布式系統(tǒng),es存儲的數(shù)據(jù)會進行分割和備份,也就是我們常說的分片和副本。
- 分片是為了提高并發(fā)能力,化整為零,并行工作
 - 副本是為了提高可用能力,防止某臺機器掛掉,數(shù)據(jù)丟失
 
如圖所示,Data-A和Data-B分割為兩個分片shard,每個shard有1個主分片2個副本分片,這12塊數(shù)據(jù)被交錯無重復地分配到4臺機器上:

這種分配模式可以有效降低機器故障帶來的數(shù)據(jù)丟失風險,副本數(shù)增加也提升了讀的并發(fā)量。
5、數(shù)據(jù)寫入
路由過程
ES的任意節(jié)點都可以作為協(xié)調(diào)節(jié)點(coordinating node)接受請求,當協(xié)調(diào)節(jié)點接受到請求后進行一系列處理,然后通過_routing字段找到對應(yīng)的主分片primary shard,并將請求轉(zhuǎn)發(fā)給primary shard。
一種常用的路由算法是:
primary shard完成寫入后,將寫入并發(fā)發(fā)送給各replica, raplica執(zhí)行寫入操作后返回給primary shard, primary shard再將請求返回給協(xié)調(diào)節(jié)點。

主分片primary shard與副本分片replica之間的同步,有兩種模式:
- 同步復制,需要所有副本分片全部寫入才可以
 - 異步復制,只有一半以上的副本完成寫入即可
 
倒排索引
倒排索引(Inverted Index)是通過value找key,這是全文檢索的關(guān)鍵,但是大文本數(shù)據(jù)使用B+樹作為底層存儲容易造成樹深度增加,IO次數(shù)增加等問題,因此es的倒排索引采用了另外一種結(jié)構(gòu):
- Term(單詞):?段?本經(jīng)過分析器分析以后就會輸出?串單詞,這?個?個的就叫做Term
 - Term Dictionary(單詞字典):??維護的是Term,可以理解為Term的集合
 - Term Index(單詞索引):為了更快的找到某個單詞,為單詞建?索引,如果term太多,term dictionary也會很?,放內(nèi)存不現(xiàn)實。
 - Posting List(倒排列表):倒排列表記錄了出現(xiàn)過某個單詞的所有?檔的?檔列表及單詞在該?檔中出現(xiàn)的位置信息,每條記錄稱為?個倒排項(Posting)。根據(jù)倒排列表,即可獲知哪些?檔包含某個單詞。
 

寫入細節(jié)
說明寫入細節(jié)之前,有幾個概念需要對齊:
- 兩種介質(zhì):內(nèi)存和磁盤
 
es寫入的數(shù)據(jù)最先放到內(nèi)存中,再做一系列的操作寫到磁盤中
- 兩個內(nèi)存區(qū)域:buffer和cache
 
內(nèi)存緩沖區(qū)(memory buffer)和文件系統(tǒng)緩存區(qū)(file system cache),這是兩種的內(nèi)存區(qū)域,目的是為了提高寫入的速度,作為寫入磁盤前的緩沖地帶,但是buffer和cache并不是同一個東西。
- 兩個動作:refresh和flush
 
refresh就是將buffer中的數(shù)據(jù)寫入cache的過程,flush就是將內(nèi)存中的數(shù)據(jù)刷到磁盤的過程。
接下來,我們來看下寫入的詳細過程:
- 寫入數(shù)據(jù)時,會先寫進內(nèi)存緩沖區(qū)memeory buffer中,此時數(shù)據(jù)還不能被檢索。
 - 為了防止宕機造成數(shù)據(jù)丟失保證可靠存儲,在每次寫入數(shù)據(jù)成功后,將此操作寫到translog事務(wù)日志中,translog也位于內(nèi)存中。
 - 寫?translog的數(shù)據(jù)是要持續(xù)去落盤的,如果對可靠性要求不是很?,也可以設(shè)置異步落盤提?性能,可由配置 index.translog.durability 和 index.translog.sync_interval 控制。
 

- 在buffer中的數(shù)據(jù)不斷增長,es提供了?個refresh操作,會定時地調(diào)?lucene的api,將位于buffer中的數(shù)據(jù)生成segment文件,segment文件仍然位于內(nèi)存中,只不過從內(nèi)存buffer換到了文件系統(tǒng)緩存cache中。
 - refresh操作的時間間隔由 refresh_interval 參數(shù)控制,默認為1s, 還可以在寫?請求中帶上refresh表示寫?后?即refresh,refresh完成之后就會清空buffer中的數(shù)據(jù),但是translog在segment沒有刷入磁盤前是不會被清空的。
 - refresh期間可能會產(chǎn)??量的?segment,es會運??個任務(wù)檢測當前磁盤中的segment,對符合條件的segment進?合并操作,減少lucene中的segment個數(shù),提?查詢速度,降低負載。
 

- es持續(xù)運行過程中會有更多的doc被添加到內(nèi)存緩沖區(qū)和追加到事務(wù)日志,期間buffer到cache的fresh動作持續(xù)進行,同時translog也逐漸變大直至到了觸發(fā)translog提交的點,也就是commit point。
 - 執(zhí)行一個提交的行為在 es 被稱作一次 flush,每隔設(shè)置的時間自動刷新flush或者在 translog 太大的時候也會刷入磁盤。
 

- 在 flush 之后,文件緩沖區(qū)cache中的segment文件被全量提交,并且translog事務(wù)日志被清空,本輪的工作基本結(jié)束,創(chuàng)建新的translog迎接下一輪的新數(shù)據(jù)。
 

再對整個過程做下總結(jié):
- 數(shù)據(jù)被寫入buffer是不可被搜索的,期間不斷的執(zhí)行refresh操作將buffer中的數(shù)據(jù)生成segment文件寫入文件系統(tǒng)緩存cache中,此時就可以被檢索了。
 - 但是此時的數(shù)據(jù)仍然駐留在內(nèi)存中,有丟失風險,為此es設(shè)置了translog來記錄數(shù)據(jù)執(zhí)行操作日志,發(fā)生故障時做數(shù)據(jù)恢復用
 - translog的數(shù)據(jù)也是在內(nèi)存中,但是默認每5秒會刷入磁盤,也就是最多丟5秒的數(shù)據(jù),在translog到達設(shè)定時間或者大小,就會執(zhí)行commit操作,此時將駐留在內(nèi)存buffer和cache的數(shù)據(jù)全部flush到磁盤,從而完成數(shù)據(jù)的持久化。
 


在網(wǎng)上看到了另外一張圖,更清晰一些:
6、數(shù)據(jù)檢索
es的Search操作分為兩個階段:query then fetch。
需要兩階段完成搜索的原因是:在查詢時不知道文檔位于哪個分片,因此索引的所有分片都要參與搜索,然后協(xié)調(diào)節(jié)點將結(jié)果合并,在根據(jù)文檔ID獲取文檔內(nèi)容。?
Query階段
- 客戶端向集群中的某個節(jié)點發(fā)送Search請求,該節(jié)點就作為本次請求的協(xié)調(diào)節(jié)點;
 - 協(xié)調(diào)節(jié)點將查詢請求轉(zhuǎn)發(fā)到索引的每個主分片或者副分片中
 - 每個分片在本地執(zhí)行查詢,并使用本地的Term/Document Frequency信息進行打分,添加結(jié)果到大小為from+size的本地有序優(yōu)先隊列中
 - 每個分片返回各自優(yōu)先隊列中所有文檔的ID和排序值給協(xié)調(diào)節(jié)點,協(xié)調(diào)節(jié)點合并這些值到自己的優(yōu)先隊列中,產(chǎn)生一個全局排序后的列表
 
Fetch階段
- 協(xié)調(diào)節(jié)點向相關(guān)的節(jié)點發(fā)送GET請求。
 - 分片所在節(jié)點向協(xié)調(diào)節(jié)點返回數(shù)據(jù)。
 - 協(xié)調(diào)階段等待所有的文檔被取得,然后返回給客戶端。
 
Part5參考資料
- https://blog.liu-kevin.com/2020/08/04/es-forcemerge/。
 - https://www.elastic.co/guide/cn/elasticsearch/guide/current/translog.html。
 - https://cloud.tencent.com/developer/article/1765827。
 















 
 
 






 
 
 
 