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