淺談ElasticSearch的那些事兒
Part1:獲取信息的訴求
我們正處于一個(gè)信息過(guò)載的時(shí)代,有用的信息、無(wú)用的信息夾雜在一起。
我們有從海量信息中獲取數(shù)據(jù)的訴求,搜索和推薦就是兩個(gè)有效的工具。
- 搜索:想好要什么,然后去查詢、再?gòu)谋姸嘟Y(jié)果中獲取對(duì)你有用的。
- 推薦:根據(jù)你的用戶畫(huà)像等信息,來(lái)主動(dòng)推送你可能感興趣的信息。
ChatGPT本質(zhì)上說(shuō)是一種更加高效準(zhǔn)確的獲取信息的渠道,至少對(duì)于我來(lái)說(shuō),有效減少了從傳統(tǒng)搜索引擎眾多結(jié)果中過(guò)濾有效信息的成本。
Part2:讓搜索飛入尋常百姓家
曾幾何時(shí),搜索引擎也是技術(shù)壁壘較深的一個(gè)領(lǐng)域,像谷歌、百度等老牌網(wǎng)頁(yè)搜索引擎公司,就靠著搜索和廣告賺得盆滿缽滿。
時(shí)間拉到現(xiàn)在,各類app層出不窮,搜索不再均限于網(wǎng)頁(yè),更多的是app內(nèi)的垂直領(lǐng)域檢索,像小紅書(shū)、知乎、淘寶、拼多多都是如此。
再具體到一些我們?nèi)粘5臉I(yè)務(wù)場(chǎng)景,也同樣有檢索訴求:篩選檢索和模糊檢索。
以常用的找房app為例,可以輸入商圈名稱、小區(qū)名稱,當(dāng)然這些都不需要非常具體,檢索框就可以進(jìn)行提示,同時(shí)還可以根據(jù)具體的條件做篩選,比如面積、朝向、總價(jià)等。
這些都可以用es來(lái)實(shí)現(xiàn),再有上層的排序邏輯,基本上就可以實(shí)現(xiàn)面向用戶的檢索功能了。
Part3Lucene和ElasticSearch
在聊es之前,就必須要提lucene,如果把es看做是豪車,lucene則是這輛豪車的發(fā)動(dòng)機(jī),真可謂是es的核心部件。
1、Lucene的誕生
Luene是一款高性能、可擴(kuò)展的信息檢索庫(kù),用于完成文檔元信息、文檔內(nèi)容等搜索功能。
用戶可以使用Lucene來(lái)快速構(gòu)建搜索服務(wù),如文件搜索、網(wǎng)頁(yè)搜索等,它是一個(gè)索引和搜索庫(kù),不包含爬取和HTML解析功能。
1985年 Doug Cutting畢業(yè)于美國(guó)斯坦福大學(xué),在1999年編寫了Lucene,他是一位資深的全文索引及檢索專家,曾經(jīng)是V-Twin搜索引擎的主要開(kāi)發(fā)者,后來(lái)在Excite擔(dān)任高級(jí)系統(tǒng)架構(gòu)設(shè)計(jì)師,目前從事于一些互聯(lián)網(wǎng)底層架構(gòu)的研究。
值得一提的是,Doug Cutting還是大名鼎鼎的Hadoop之父,大牛果然是高產(chǎn)。
2、elasticsearch的誕生
有了Lucene之后,那么es又是怎么誕生的呢?這就要提到一個(gè)曾經(jīng)的待業(yè)青年 Shay Banon。
當(dāng)年他還是一個(gè)待業(yè)工程師,跟隨自己的新婚妻子來(lái)到倫敦,妻子想在倫敦學(xué)習(xí)做一名廚師,而自己則想為妻子開(kāi)發(fā)一個(gè)方便搜索菜譜的應(yīng)用,所以才接觸到 Lucene。
直接使用 Lucene 構(gòu)建搜索有很多問(wèn)題,包含大量重復(fù)性的工作,所以 Shay Banon 便在 Lucene 的基礎(chǔ)上不斷地進(jìn)行抽象,讓 Java 程序嵌入搜索變得更容易,經(jīng)過(guò)一段時(shí)間的打磨便誕生了他的第一個(gè)開(kāi)源作品Compass。
之后,他找到了一份面對(duì)高性能分布式開(kāi)發(fā)環(huán)境的新工作,在工作中他漸漸發(fā)現(xiàn)越來(lái)越需要一個(gè)易用的、高性能、實(shí)時(shí)、分布式搜索服務(wù),于是決定重寫 Compass,將它從一個(gè)庫(kù)打造成了一個(gè)獨(dú)立的 server,并創(chuàng)建了開(kāi)源項(xiàng)目。
第一個(gè)公開(kāi)版本出現(xiàn)在 2010 年 2 月,在那之后 Elasticsearch 已經(jīng)成為 Github 上最受歡迎的項(xiàng)目之一。
后來(lái)和幾個(gè)志同道合的技術(shù)狂人一起把es做大做強(qiáng),最后敲鐘上市,從待業(yè)青年成為了億萬(wàn)富翁,還真是勵(lì)志!
Part4:ElasticSearch架構(gòu)原理
我們前面提到,es是基于Lucene打造的開(kāi)源檢索組件,Lucene只是一個(gè)裸信息檢索庫(kù),而es要做的就是解決Lucene到業(yè)務(wù)場(chǎng)景的最后一公里問(wèn)題。
當(dāng)我們嘗試去學(xué)習(xí)一個(gè)組件時(shí),不妨把我們自己當(dāng)做組件的研發(fā)者,抱著去做一款產(chǎn)品的思維來(lái)看,或許可以更清晰。
在聊es的架構(gòu)和原理之前,我們也反客為主去思考下,es的目標(biāo)有哪些:
- 簡(jiǎn)單的交互模式、支持多種語(yǔ)言
- 支持海量數(shù)據(jù)、高效檢索效率
- 支持實(shí)時(shí)/準(zhǔn)實(shí)時(shí)地低時(shí)延檢索
- 支持高并發(fā)&高可用場(chǎng)景
3、分布式系統(tǒng)的引入
要解決海量數(shù)據(jù)檢索、高并發(fā)、高可用等問(wèn)題,就必須要引入分布式系統(tǒng),集群模式下吞吐量和穩(wěn)定性都能有保證。
在es集群中每臺(tái)機(jī)器從不同角度看有不同的角色,其中重要的幾個(gè)包括:
- Master Node 負(fù)責(zé)監(jiān)控和協(xié)調(diào)工作,保證整個(gè)集群的穩(wěn)定性,管事的節(jié)點(diǎn)
- Work Node 負(fù)責(zé)數(shù)據(jù)的寫入和讀取,也就是干活的節(jié)點(diǎn)
- Coordinating Node 協(xié)調(diào)節(jié)點(diǎn),負(fù)責(zé)請(qǐng)求的路由分發(fā)等,每個(gè)節(jié)點(diǎn)都可以是協(xié)調(diào)節(jié)點(diǎn),同時(shí)也可以只負(fù)責(zé)協(xié)調(diào),不做具體的數(shù)據(jù)處理工作
- Master-eligible node 候選主節(jié)點(diǎn),作為Master節(jié)點(diǎn)的接班人之一,可以參與投票和競(jìng)選新的Master節(jié)點(diǎn)
再細(xì)分的角色還有很多,在此不展開(kāi)了,實(shí)際上分布式系統(tǒng)中各個(gè)節(jié)點(diǎn)的角色和要做的事情,基本都差不多,和人類社會(huì)運(yùn)行中的各個(gè)角色都非常相似。
引入分布式系統(tǒng)之后,就會(huì)面臨很多新的問(wèn)題:網(wǎng)絡(luò)延遲、消息丟失、集群腦裂、故障容錯(cuò)和恢復(fù)、一致性、共識(shí)問(wèn)題、選舉問(wèn)題、等。
分布式系統(tǒng)也是一把雙刃劍,但是其帶來(lái)的好處遇大于問(wèn)題,在分布式基礎(chǔ)理論和基礎(chǔ)算法的加持下,讓分布式系統(tǒng)應(yīng)用于生產(chǎn)實(shí)踐成為了現(xiàn)實(shí)。
4、高可用和高并發(fā)
基于分布式系統(tǒng),es存儲(chǔ)的數(shù)據(jù)會(huì)進(jìn)行分割和備份,也就是我們常說(shuō)的分片和副本。
- 分片是為了提高并發(fā)能力,化整為零,并行工作
- 副本是為了提高可用能力,防止某臺(tái)機(jī)器掛掉,數(shù)據(jù)丟失
如圖所示,Data-A和Data-B分割為兩個(gè)分片shard,每個(gè)shard有1個(gè)主分片2個(gè)副本分片,這12塊數(shù)據(jù)被交錯(cuò)無(wú)重復(fù)地分配到4臺(tái)機(jī)器上:
這種分配模式可以有效降低機(jī)器故障帶來(lái)的數(shù)據(jù)丟失風(fēng)險(xiǎn),副本數(shù)增加也提升了讀的并發(fā)量。
5、數(shù)據(jù)寫入
路由過(guò)程
ES的任意節(jié)點(diǎn)都可以作為協(xié)調(diào)節(jié)點(diǎn)(coordinating node)接受請(qǐng)求,當(dāng)協(xié)調(diào)節(jié)點(diǎn)接受到請(qǐng)求后進(jìn)行一系列處理,然后通過(guò)_routing字段找到對(duì)應(yīng)的主分片primary shard,并將請(qǐng)求轉(zhuǎn)發(fā)給primary shard。
一種常用的路由算法是:
primary shard完成寫入后,將寫入并發(fā)發(fā)送給各replica, raplica執(zhí)行寫入操作后返回給primary shard, primary shard再將請(qǐng)求返回給協(xié)調(diào)節(jié)點(diǎn)。
主分片primary shard與副本分片replica之間的同步,有兩種模式:
- 同步復(fù)制,需要所有副本分片全部寫入才可以
- 異步復(fù)制,只有一半以上的副本完成寫入即可
倒排索引
倒排索引(Inverted Index)是通過(guò)value找key,這是全文檢索的關(guān)鍵,但是大文本數(shù)據(jù)使用B+樹(shù)作為底層存儲(chǔ)容易造成樹(shù)深度增加,IO次數(shù)增加等問(wèn)題,因此es的倒排索引采用了另外一種結(jié)構(gòu):
- Term(單詞):?段?本經(jīng)過(guò)分析器分析以后就會(huì)輸出?串單詞,這?個(gè)?個(gè)的就叫做Term
- Term Dictionary(單詞字典):??維護(hù)的是Term,可以理解為Term的集合
- Term Index(單詞索引):為了更快的找到某個(gè)單詞,為單詞建?索引,如果term太多,term dictionary也會(huì)很?,放內(nèi)存不現(xiàn)實(shí)。
- Posting List(倒排列表):倒排列表記錄了出現(xiàn)過(guò)某個(gè)單詞的所有?檔的?檔列表及單詞在該?檔中出現(xiàn)的位置信息,每條記錄稱為?個(gè)倒排項(xiàng)(Posting)。根據(jù)倒排列表,即可獲知哪些?檔包含某個(gè)單詞。
寫入細(xì)節(jié)
說(shuō)明寫入細(xì)節(jié)之前,有幾個(gè)概念需要對(duì)齊:
- 兩種介質(zhì):內(nèi)存和磁盤
es寫入的數(shù)據(jù)最先放到內(nèi)存中,再做一系列的操作寫到磁盤中
- 兩個(gè)內(nèi)存區(qū)域:buffer和cache
內(nèi)存緩沖區(qū)(memory buffer)和文件系統(tǒng)緩存區(qū)(file system cache),這是兩種的內(nèi)存區(qū)域,目的是為了提高寫入的速度,作為寫入磁盤前的緩沖地帶,但是buffer和cache并不是同一個(gè)東西。
- 兩個(gè)動(dòng)作:refresh和flush
refresh就是將buffer中的數(shù)據(jù)寫入cache的過(guò)程,flush就是將內(nèi)存中的數(shù)據(jù)刷到磁盤的過(guò)程。
接下來(lái),我們來(lái)看下寫入的詳細(xì)過(guò)程:
- 寫入數(shù)據(jù)時(shí),會(huì)先寫進(jìn)內(nèi)存緩沖區(qū)memeory buffer中,此時(shí)數(shù)據(jù)還不能被檢索。
- 為了防止宕機(jī)造成數(shù)據(jù)丟失保證可靠存儲(chǔ),在每次寫入數(shù)據(jù)成功后,將此操作寫到translog事務(wù)日志中,translog也位于內(nèi)存中。
- 寫?translog的數(shù)據(jù)是要持續(xù)去落盤的,如果對(duì)可靠性要求不是很?,也可以設(shè)置異步落盤提?性能,可由配置 index.translog.durability 和 index.translog.sync_interval 控制。
- 在buffer中的數(shù)據(jù)不斷增長(zhǎng),es提供了?個(gè)refresh操作,會(huì)定時(shí)地調(diào)?lucene的api,將位于buffer中的數(shù)據(jù)生成segment文件,segment文件仍然位于內(nèi)存中,只不過(guò)從內(nèi)存buffer換到了文件系統(tǒng)緩存cache中。
- refresh操作的時(shí)間間隔由 refresh_interval 參數(shù)控制,默認(rèn)為1s, 還可以在寫?請(qǐng)求中帶上refresh表示寫?后?即refresh,refresh完成之后就會(huì)清空buffer中的數(shù)據(jù),但是translog在segment沒(méi)有刷入磁盤前是不會(huì)被清空的。
- refresh期間可能會(huì)產(chǎn)??量的?segment,es會(huì)運(yùn)??個(gè)任務(wù)檢測(cè)當(dāng)前磁盤中的segment,對(duì)符合條件的segment進(jìn)?合并操作,減少lucene中的segment個(gè)數(shù),提?查詢速度,降低負(fù)載。
- es持續(xù)運(yùn)行過(guò)程中會(huì)有更多的doc被添加到內(nèi)存緩沖區(qū)和追加到事務(wù)日志,期間buffer到cache的fresh動(dòng)作持續(xù)進(jìn)行,同時(shí)translog也逐漸變大直至到了觸發(fā)translog提交的點(diǎn),也就是commit point。
- 執(zhí)行一個(gè)提交的行為在 es 被稱作一次 flush,每隔設(shè)置的時(shí)間自動(dòng)刷新flush或者在 translog 太大的時(shí)候也會(huì)刷入磁盤。
- 在 flush 之后,文件緩沖區(qū)cache中的segment文件被全量提交,并且translog事務(wù)日志被清空,本輪的工作基本結(jié)束,創(chuàng)建新的translog迎接下一輪的新數(shù)據(jù)。
再對(duì)整個(gè)過(guò)程做下總結(jié):
- 數(shù)據(jù)被寫入buffer是不可被搜索的,期間不斷的執(zhí)行refresh操作將buffer中的數(shù)據(jù)生成segment文件寫入文件系統(tǒng)緩存cache中,此時(shí)就可以被檢索了。
- 但是此時(shí)的數(shù)據(jù)仍然駐留在內(nèi)存中,有丟失風(fēng)險(xiǎn),為此es設(shè)置了translog來(lái)記錄數(shù)據(jù)執(zhí)行操作日志,發(fā)生故障時(shí)做數(shù)據(jù)恢復(fù)用
- translog的數(shù)據(jù)也是在內(nèi)存中,但是默認(rèn)每5秒會(huì)刷入磁盤,也就是最多丟5秒的數(shù)據(jù),在translog到達(dá)設(shè)定時(shí)間或者大小,就會(huì)執(zhí)行commit操作,此時(shí)將駐留在內(nèi)存buffer和cache的數(shù)據(jù)全部flush到磁盤,從而完成數(shù)據(jù)的持久化。
在網(wǎng)上看到了另外一張圖,更清晰一些:
6、數(shù)據(jù)檢索
es的Search操作分為兩個(gè)階段:query then fetch。
需要兩階段完成搜索的原因是:在查詢時(shí)不知道文檔位于哪個(gè)分片,因此索引的所有分片都要參與搜索,然后協(xié)調(diào)節(jié)點(diǎn)將結(jié)果合并,在根據(jù)文檔ID獲取文檔內(nèi)容。?
Query階段
- 客戶端向集群中的某個(gè)節(jié)點(diǎn)發(fā)送Search請(qǐng)求,該節(jié)點(diǎn)就作為本次請(qǐng)求的協(xié)調(diào)節(jié)點(diǎn);
- 協(xié)調(diào)節(jié)點(diǎn)將查詢請(qǐng)求轉(zhuǎn)發(fā)到索引的每個(gè)主分片或者副分片中
- 每個(gè)分片在本地執(zhí)行查詢,并使用本地的Term/Document Frequency信息進(jìn)行打分,添加結(jié)果到大小為from+size的本地有序優(yōu)先隊(duì)列中
- 每個(gè)分片返回各自優(yōu)先隊(duì)列中所有文檔的ID和排序值給協(xié)調(diào)節(jié)點(diǎn),協(xié)調(diào)節(jié)點(diǎn)合并這些值到自己的優(yōu)先隊(duì)列中,產(chǎn)生一個(gè)全局排序后的列表
Fetch階段
- 協(xié)調(diào)節(jié)點(diǎn)向相關(guān)的節(jié)點(diǎn)發(fā)送GET請(qǐng)求。
- 分片所在節(jié)點(diǎn)向協(xié)調(diào)節(jié)點(diǎn)返回?cái)?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。