Elasticsearch詳解

Elasticsearch詳解

96 
Chandler_珏瑜 22d8d123 271c 4d80 9c59 6990844a9e37
 5.8 2019.05.05 17:19* 字數 10971 閱讀 1147評論 5

5.1 Lucene簡介

 Lucene是一種高性能、可伸縮的信息搜索(IR)庫,在2000年開源,最初由鼎鼎大名的Doug Cutting開發,是基於Java實現的高性能的開源項目。Lucene採用了基於倒排表的設計原理,能夠很是高效地實現文本查找,在底層採用了分段的存儲模式,使它在讀寫時幾乎徹底避免了鎖的出現,大大提高了讀寫性能。css

Elasticsearch基於lucene,隱藏其複雜性,並提供簡單易用的restful API接口、java API接口。因此理解ES的關鍵在於理解lucene的基本原理。java

5.1.1 核心模塊

 Lucene的寫流程和讀流程如圖5-1所示。node


 
圖 5-1 Lucene的寫流程和讀流程

 其中,虛線箭頭(a、b、c、d)表示寫索引的主要過程,實線箭頭(1-9)表示查詢的主要過程。算法

 Lucene中的主要模塊(見圖5-1)及模塊說明以下:數據庫

  1. analysis:主要負責詞法分析及語言處理,也就是咱們常說的分詞,經過該模塊可最終造成存儲或者搜索的最小單元Term。
  2. index模塊:主要負責索引的建立工做。
  3. store模塊:主要負責索引的讀寫,主要是對文件的一些操做,其主要目的是抽象出和平臺文件系統無關的存儲。
  4. queryParser模塊:主要負責語法分析,把咱們的查詢語句生成Lucene底層能夠識別的條件。
  5. search模塊:主要負責對索引的搜索工做。
  6. similarity模塊:主要負責相關性打分和排序的實現。

5.1.2 核心術語

 下面介紹Lucene中的核心術語。數組

▪️Term:是索引裏最小的存儲和查詢單元,對於英文來講通常是指一個單詞,對於中文來講通常是指一個分詞後的詞。緩存

▪️詞典(Term Dictionary,也叫做字典):是Term的集合。詞典的數據結構能夠有不少種,每種都有本身的優缺點,好比:排序數組經過二分查找來檢索數據:HashMap(哈希表)比排序數組的檢索速度更快,可是會浪費存儲空間;fst(finite-state transducer)有更高的數據壓縮率和查詢效率,由於詞典是常駐內存的,而fst有很好的壓縮率,因此fst在Lucene的最新版本中有很是多的使用場景,也是默認的詞典數據結構。安全

▪️倒排序(Posting List):一篇文章一般由多個詞組成,倒排表記錄的是某個詞在哪些文章中出現過。性能優化

▪️正向信息:原始的文檔信息,能夠用來作排序、聚合、展現等。bash

▪️段(segment):索引中最小的獨立存儲單元。一個索引文件由一個或者多個段組成。在Luence中的段有不變性,也就是說段一旦生成,在其上只能有讀操做,不能有寫操做。

 Lucene的底層存儲格式如圖5-2所示。圖5-2由詞典和倒排序兩部分組成,其中的詞典就是Term的集合。詞典中的Term指向的文檔鏈表的集合,叫作倒排表。詞典和倒排表是Lucene中很重要的兩種數據結構,是實現快速檢索的重要基石。詞典和倒排表是分兩部分存儲的,在倒排序中不但存儲了文檔編號,還存儲了詞頻等信息。


 
圖 5-2 Lucene的底層存儲格式

 在圖5-2所示的詞典部分包含三個詞條(Term):elasticsearch、lucene和solr。詞典數據是查詢的入口,因此這部分數據是以fst的形式存儲在內存中的。

 在倒排表中,「lucene」指向有序鏈表3,7,15,30,35,67,表示字符串「lucene」在文檔編號爲三、七、1五、30、3五、67的文章中出現過,elasticsearch和solr同理。

5.1.3 檢索方式

 在Lucene的查詢過程當中的主要檢索方式有如下四種。

1. 單個詞查詢

 指對一個Term進行查詢。好比,若要查找包含字符串「lucene」的文檔,則只需在詞典中找到Term「lucene」,再得到在倒排表中對應的文檔鏈表便可。

2. AND

 指對多個集合求交集。好比,若要查找既包含字符串「lucene」又包含字符串「solr」的文檔,則查找步驟以下。

 (1)在詞典中找到Term 「lucene」,獲得「lucene」對應的文檔鏈表。

 (2)在詞典中找到Term 「solr」,獲得「solr」對應的文檔鏈表。

 (3)合併鏈表,對兩個文檔鏈表作交集運算,合併後的結果既包含「lucene」也包含「solr」。

3. OR

 指多個集合求並集。好比,若要查找包含字符串「luence」或者包含字符串「solr」的文檔,則查找步驟以下。

 (1)在詞典中找到Term 「lucene」,獲得「lucene」對應的文檔鏈表。

 (2)在詞典中找到Term 「solr」,獲得「solr」對應的文檔鏈表。

 (3)合併鏈表,對兩個文檔鏈表作並集運算,合併後的結果包含「lucene」或者包含「solr」。

4. NOT

 指對多個集合求差集。好比,若要查找包含字符串「solr」但不包含字符串「lucene」的文檔,則查找步驟以下。

 (1)在詞典中找到Term 「lucene」,獲得「lucene」對應的文檔鏈表。

 (2)在詞典中找到Term 「solr」,獲得「solr」對應的文檔鏈表。

 (3)合併鏈表,對兩個文檔鏈表作差集運算,用包含「solr」的文檔集減去包含「lucene」的文檔集,運算後的結果就是包含「solr」但不包含「lucene」。

 經過上述四種查詢方式,咱們不難發現,因爲Lucene是以倒排表的形式存儲的,因此在Lucene的查找過程當中只需在詞典中找到這些Term,根據Term得到文檔鏈表,而後根據具體的查詢條件對鏈表進行交、並、差等操做,就能夠準確地查到咱們想要的結果,相對於在關係型數據庫中的「like」查找要作全表掃描來講,這種思路是很是高效的。雖然在索引建立時要作不少工做,但這種一次生成、屢次使用的思路也是很是高明的。

5.1.4 分段存儲

 在早期的全文檢索中爲整個文檔集合創建了一個很大的倒排索引,並將其寫入磁盤中,若是索引有更新,就須要從新全量建立一個索引來替換原來的索引。這種方式在數據量很大時效率很低,而且因爲建立一次索引的成本很高,因此對數據的更新不能過於頻繁,也就不能保證明效性。

 如今,在搜索中引入了段的概念(將一個索引文件拆分爲多個子文件,則每一個子文件叫作段),每一個段都是一個獨立的可被搜索的數據集,而且段具備不變性,一旦索引的數據被寫入硬盤,就不可修改。

 在分段的思想下,對數據寫操做的過程以下。

  • 新增:當有新的數據須要建立索引時,因爲段段不變性,因此選擇新建一個段來存儲新增的數據。
  • 刪除:當須要刪除數據時,因爲數據所在的段只可讀,不可寫,因此Lucene在索引文件新增一個.del的文件,用來專門存儲被刪除的數據id。當查詢時,被刪除的數據仍是能夠被查到的,只是在進行文檔鏈表合併時,才把已經刪除的數據過濾掉。被刪除的數據在進行段合併時纔會被真正被移除。
  • 更新:更新的操做其實就是刪除和新增的組合,先在.del文件中記錄舊數據,再在新段中添加一條更新後的數據。

 段不可變性的優勢以下:

  • 不須要鎖:由於數據不會更新,因此不用考慮多線程下的讀寫不一致狀況。
  • 能夠常駐內存:段在被加載到內存後,因爲具備不變性,因此只要內存的空間足夠大,就能夠長時間駐存,大部分查詢請求會直接訪問內存,而不須要訪問磁盤,使得查詢的性能有很大的提高。
  • 緩存友好:在段的聲明週期內始終有效,不須要在每次數據更新時被重建。
  • 增量建立:分段能夠作到增量建立索引,能夠輕量級地對數據進行更新,因爲每次建立的成本很低,因此能夠頻繁地更新數據,使系統接近實時更新。

段不可變性的缺點以下:

  • 刪除:當對數據進行刪除時,舊數據不會被立刻刪除,而是在.del文件中被標記爲刪除。而舊數據只能等到段更新時才能真正地被移除,這樣會有大量的空間浪費。
  • 更新:更新數據由刪除和新增這兩個動做組成。如有一條數據頻繁更新,則會有大量的空間浪費。
  • 新增:因爲索引具備不變性,因此每次新增數據時,都須要新增一個段來存儲數據。當段段數量太多時,對服務器的資源(如文件句柄)的消耗會很是大,查詢的性能也會受到影響。
  • 過濾:在查詢後須要對已經刪除的舊數據進行過濾,這增長了查詢的負擔。

 爲了提高寫的性能,Lucene並無每新增一條數據就增長一個段,而是採用延遲寫的策略,每當有新增的數據時,就將其先寫入內存中,而後批量寫入磁盤中。如有一個段被寫到硬盤,就會生成一個提交點,提交點就是一個用來記錄全部提交後的段信息的文件。一個段一旦擁有了提交點,就說明這個段只有讀到權限,失去了寫的權限;相反,當段在內存中時,就只有寫數據的權限,而不具有讀數據的權限,因此也就不能被檢索了。從嚴格意義上來講,Lucene或者Elasticsearch並不能被稱爲實時的搜索引擎,只能被稱爲準實時的搜索引擎。

 寫索引的流程以下:

 (1)新數據被寫入時,並無被直接寫到硬盤中,而是被暫時寫到內存中。Lucene默認是一秒鐘,或者當內存中數據量達到必定階段時,再批量提交到磁盤中,固然,默認的時間和數據量的大小是能夠經過參數控制的。經過延時寫的策略,能夠減小數據往磁盤上寫的次數,從而提高總體的寫入性能。如圖5-7所示。

 (2)在達到出觸發條件之後,會將內存中緩存的數據一次性寫入磁盤中,並生成提交點。

 (3)狀況內存,等待新的數據寫入。如圖5-8所示。

 
圖 5-7 圖 5-8 Elasticsearch寫索引.png

 從上述流程能夠看出,數據先被暫時緩存在內存中,在達到必定的條件再被一次性寫入硬盤中,這種作法能夠大大提高數據寫入的書單。可是數據先被暫時存放在內存中,並無真正持久化到磁盤中,因此若是這時出現斷電等不可控的狀況,就會丟失數據,爲此,Elasticsearch添加了事務日誌,來保證數據的安全,參見5.2.3節。

5.1.5 段合併策略

 雖然分段比每次都全量建立索引有更高的效率,可是因爲在每次新增數據時都會新增一個段,因此通過長時間的的積累,會致使在索引中存在大量的段,當索引中段的數量太多時,不只會嚴重消耗服務器的資源,還會影響檢索的性能。

 由於索引檢索的過程是:查詢全部段中知足查詢條件的數據,而後對每一個段裏查詢的結果集進行合併,因此爲了控制索引裏段的數量,咱們必須按期進行段合併操做。可是若是每次合併所有的段,則會形成很大的資源浪費,特別是「大段」的合併。因此Lucene如今的段合併思路是:根據段的大小將段進行分組,再將屬於同一組的段進行合併。可是因爲對於超級大的段的合併須要消耗更多的資源,因此Lucene會在段的大小達到必定規模,或者段裏面的數據量達到必定條數時,不會再進行合併。因此Lucene的段合併主要集中在對中小段的合併上,這樣既能夠避免對大段進行合併時消耗過多的服務器資源,也能夠很好地控制索引中段的數量。

 段合併的主要參數以下:

  • mergeFactor:每次合併時參與合併的最少數量,當同一組的段的數量達到此值時開始合併,若是小於此值則不合並,這樣作能夠減小段合併的頻率,其默認值爲10。
  • SegmentSize:指段的實際大小,單位爲字節。
  • minMergeSize:小於這個值的段會被分到一組,這樣能夠加速小片斷的合併。
  • maxMergeSize:如有一段的文本數量大於此值,就再也不參與合併,由於大段合併會消耗更多的資源。

 段合併相關的動做主要有如下兩個:

  • 對索引中的段進行分組,把大小相近的段分到一組,主要由LogMergePolicy1類來處理。
  • 將屬於同一分組的段合併成一個更大的段。

 在段合併前對段的大小進行了標準化處理,經過

  logMergeFactorSegmentSize

 計算得出,其中MergeFactor表示一次合併的段的數量,Lucene默認該數量爲10;SegmentSize表示段的實際大小。經過上面的公式計算後,段的大小更加緊湊,對後續的分組更加友好。

 段分組的步驟以下:

 (1)根據段生成的時間對段進行排序,而後根據上述標準化公式計算每一個段的大小而且存放到段信息中,後面用到的描述段大小的值都是標準化後的值。如圖5-9所示。


 
圖 5-9 Lucene段排序

 (2)在數組中找到最大的段,而後生成一個由最大段的標準化值做爲上線,減去LEVEL_LOG_SPAN(默認值爲0.75)後的值做爲下限的區間,小於等於上限而且大於下限的段,都被認爲是屬於同一組的段,能夠合併。

 (3)在肯定一個分組的上下限值後,就須要查找屬於這個分組的段了,具體過程是:建立兩個指針(在這裏使用指針的概念是爲了更好地理解)start和end,start指向數組的第1個段,end指向第start+MergeFactor個段,而後從end逐個向前查找落在區間的段,當找到第1個知足條件的段時,則中止,並把當前段到start之間的段統一分到一個組,不管段的大小是否知足當前分組的條件。如圖5-10所示,第2個段明顯小於該分組的下限,但仍是被分到了這一組。


 
圖 5-10 Lucene段分組

 這樣作的好處以下:

  • 增長段合併的機率,避免因爲段的大小良莠不齊致使段難以合併。
  • 簡化了查找的邏輯,使代碼的運行效率更高。

 (4)在分組找到後,須要排除不參加合併的「超大」段,而後判斷剩餘的段是否知足合併的條件,如圖5-10所示,mergeFactor=5,而找到的知足合併條件的段的個數爲4,因此不知足合併的條件,暫時不進行合併,繼續找尋下一個組的上下限。

 (5)因爲在第4步並無找到知足段合併的段的數量,因此這一分組的段不知足合併的條件,繼續進行下一分組段的查找。具體過程是:將start指向end,在剩下的段(從end指向的元素開始到數組的最後一個元素)中中尋找最大的段,在找到最大的值後再減去LEVEL_LOG_SPAN的值,再生成一下分組的區間值;而後把end指向數組的第start+MergeFactor個段,逐個向前查找第1個知足條件的段:重複第3步和第4步。

 (6)若是一直沒有找到知足合併條件的段,則一直重複第5步,直到遍歷完整個數組。若是如圖5-11所示。


 
圖 5-11 Lucene段分組二.png

 (7)在找到知足條件的mergeFactor個段時,就須要開始合併了。可是在知足合併條件的段大於mergeFactor時,就須要進行屢次合併,也就是說每次依然選擇mergeFactor個段進行合併,直到該分組的全部段合併完成,再進行下一分組的查找合併操做。

 (8)經過上述幾步,若是找到了知足合併要求的段,則將會進行段的合併操做。由於索引裏面包含了正向信息和反向信息,因此段合併的操做分爲兩部分:一個是正向信息合併,例如存儲域、詞向量、標準化因子等;一個是反向信息的合併,例如詞典、倒排表等。在段合併時,除了須要對索引數據進行合併,還須要移除段中已經刪除的數據。

5.1.6 Lucene類似度打分

 咱們在前面瞭解到,Lucene的查詢過程是:首先在詞典中查找每一個Term,根據Term得到每一個Term所在的文檔鏈表;而後根據查詢條件對鏈表作交、並、差等操做,鏈表合併後的結果集就是咱們要查找的數據。這樣作能夠徹底避免對關係型數據庫進行全表掃描,能夠大大提高查詢效率。可是,當咱們一次查詢出不少數據時,這些數據和咱們的查詢條件又有多大關係呢?其文本類似度是多少?本節會回答這個問題,並介紹Lucene最經典的兩個文本類似度算法:基於向量空間模型的算法和基於機率的算法(BM25)。

 若是對此算法不太感興趣,那麼只需瞭解對文本類似度有影響的因子有哪些,哪些是正向的,哪些是逆向的便可,不須要理解每一個算法的推理過程。可是這兩個文本類似度算法有很好的借鑑意義。

5.2 Elasticsearch簡介

 Elasticsearch是使用Java編寫的一種開源搜索引擎,它在內部使用Luence作索引與搜索,經過對Lucene的封裝,提供了一套簡單一致的RESTful API。Elasticsearch也是一種分佈式的搜索引擎架構,能夠很簡單地擴展到上百個服務節點,並支持PB級別的數據查詢,使系統具有高可用和高併發性。

5.2.1 核心概念

 Elasticsearch的核心概念以下:

  • Cluster:集羣,由一個或多個Elasticsearch節點組成。
  • Node:節點,組成Elasticsearch集羣的服務單元,同一個集羣內節點的名字不能重複。一般在一個節點上分配一個或者多個分片。
  • Shards:分片,當索引上的數據量太大的時候,咱們一般會將一個索引上的數據進行水平拆分,拆分出來的每一個數據庫叫做一個分片。在一個多分片的索引中寫入數據時,經過路由來肯定具體寫入那一個分片中,因此在建立索引時須要指定分片的數量,而且分片的數量一旦肯定就不能更改。分片後的索引帶來了規模上(數據水平切分)和性能上(並行執行)的提高。每一個分片都是Luence中的一個索引文件,每一個分片必須有一個主分片和零到多個副本分片。
  • Replicas:備份也叫做副本,是指對主分片的備份。主分片和備份分片均可以對外提供查詢服務,寫操做時先在主分片上完成,而後分發到備份上。當主分片不可用時,會在備份的分片中選舉出一個做爲主分片,因此備份不只能夠提高系統的高可用性能,還能夠提高搜索時的併發性能。可是若副本太多的話,在寫操做時會增長數據同步的負擔。
  • Index:索引,由一個和多個分片組成,經過索引的名字在集羣內進行惟一標識。
  • Type:類別,指索引內部的邏輯分區,經過Type的名字在索引內進行惟一標識。在查詢時若是沒有該值,則表示在整個索引中查詢。
  • Document:文檔,索引中的每一條數據叫做一個文檔,相似於關係型數據庫中的一條數據經過_id在Type內進行惟一標識。
  • Settings:對集羣中索引的定義,好比一個索引默認的分片數、副本數等信息。
  • Mapping:相似於關係型數據庫中的表結構信息,用於定義索引中字段(Field)的存儲類型、分詞方式、是否存儲等信息。Elasticsearch中的mapping是能夠動態識別的。若是沒有特殊需求,則不須要手動建立mapping,由於Elasticsearch會自動根據數據格式識別它的類型,可是當須要對某些字段添加特殊屬性(好比:定義使用其餘分詞器、是否分詞、是否存儲等)時,就須要手動設置mapping了。一個索引的mapping一旦建立,若已經存儲了數據,就不可修改了。
  • Analyzer:字段的分詞方式的定義。一個analyzer一般由一個tokenizer、零到多個filter組成。好比默認的標準Analyzer包含一個標準的tokenizer和三個filter:Standard Token Filter、Lower Case Token Filter、Stop Token Filter。
     Elasticsearch的節點的分類以下:
  • 主節點(Master Node):也叫做主節點,主節點負責建立索引、刪除索引、分配分片、追蹤集羣中的節點狀態等工做。Elasticsearch中的主節點的工做量相對較輕。用戶的請求能夠發往任何一個節點,並由該節點負責分發請求、收集結果等操做,而並不須要通過主節點轉發。經過在配置文件中設置node.master=true來設置該節點成爲候選主節點(但該節點不必定是主節點,主節點是集羣在候選節點中選舉出來的),在Elasticsearch集羣中只有候選節點纔有選舉權和被選舉權。其餘節點是不參與選舉工做的。
  • 數據節點(Data Node):數據節點,負責數據的存儲和相關具體操做,好比索引數據的建立、修改、刪除、搜索、聚合。因此,數據節點對機器配置要求比較高,首先須要有足夠的磁盤空間來存儲數據,其次數據操做對系統CPU、Memory和I/O的性能消耗都很大。一般隨着集羣的擴大,須要增長更多的數據節點來提升可用性。經過在配置文件中設置node.data=true來設置該節點成爲數據節點。
  • 客戶端節點(Client Node):就是既不作候選主節點也不作數據節點的節點,只負責請求的分發、彙總等,也就是下面要說到的協調節點的角色。其實任何一個節點均可以完成這樣的工做,單獨增長這樣的節點更多地是爲了提升併發性。可在配置文件中設置該節點成爲數據節點:
node.master=false node.data=false 
  • 部落節點(Tribe Node):部落節點能夠跨越多個集羣,它能夠接收每一個集羣的狀態,而後合併成一個全局集羣的狀態,它能夠讀寫全部集羣節點上的數據,在配置文件中經過以下設置使節點成爲部落節點:
tribe: one: cluster.name: cluster_one two: cluster.name: cluster_two 

 由於Tribe Node要在Elasticsearch 7.0之後移除,因此不建議使用。

  • 協調節點(Coordinating Node):協調節點,是一種角色,而不是真實的Elasticsearch的節點,咱們沒有辦法經過配置項來配置哪一個節點爲協調節點。集羣中的任何節點均可以充當協調節點的角色。當一個節點A收到用戶的查詢請求後,會把查詢語句分發到其餘的節點,而後合併各個節點返回的查詢結果,最好返回一個完整的數據集給用戶。在這個過程當中,節點A扮演的就是協調節點的角色。因而可知,協調節點會對CPU、Memory和I/O要求比較高。
     集羣的狀態有Green、Yellow和Red三種,以下所述:
  • Green:綠色,健康。全部的主分片和副本分片均可正常工做,集羣100%健康。
  • Yellow:黃色,預警。全部的主分片均可以正常工做,但至少有一個副本分片是不能正常工做的。此時集羣能夠正常工做,可是集羣的高可用性在某種程度上被弱化。
  • Red:紅色,集羣不可正常使用。集羣中至少有一個分片的主分片及它的所有副本分片都不可正常工做。這時雖然集羣的查詢操做還能夠進行,可是也只能返回部分數據(其餘正常分片的數據能夠返回),而分配到這個分片上的寫入請求將會報錯,最終會致使數據的丟失。

5.2.2 3C和腦裂

1. 共識性(Consensus)

 共識性是分佈式系統中最基礎也最主要的一個組件,在分佈式系統中的全部節點必須對給定的數據或者節點的狀態達成共識。雖然如今有很成熟的共識算法如Raft、Paxos等,也有比較成熟的開源軟件如Zookeeper。可是Elasticsearch並無使用它們,而是本身實現共識系統zen discovery。Elasticsearch之父Shay Banon解釋了其中主要的緣由:「zen discovery是Elasticsearch的一個核心的基礎組件,zen discovery不只可以實現共識系統的選擇工做,還可以很方便地監控集羣的讀寫狀態是否健康。固然,咱們也不保證其後期會使用Zookeeper代替如今的zen discovery」。zen discovery模塊以「八卦傳播」(Gossip)的形式實現了單播(Unicat):單播不一樣於多播(Multicast)和廣播(Broadcast)。節點間的通訊方式是一對一的。

2. 併發(Concurrency)

 Elasticsearch是一個分佈式系統。寫請求在發送到主分片時,同時會以並行的形式發送到備份分片,可是這些請求的送達時間多是無序的。在這種狀況下,Elasticsearch用樂觀併發控制(Optimistic Concurrency Control)來保證新版本的數據不會被舊版本的數據覆蓋。
 樂觀併發控制是一種樂觀鎖,另外一種經常使用的樂觀鎖即多版本併發控制(Multi-Version Concurrency Control),它們的主要區別以下:

  • 樂觀併發控制(OCC):是一種用來解決寫-寫衝突的無鎖併發控制,認爲事務間的競爭不激烈時,就先進行修改,在提交事務前檢查數據有沒有變化,若是沒有就提交,若是有就放棄並重試。樂觀併發控制相似於自選鎖,適用於低數據競爭且寫衝突比較少的環境。
  • 多版本併發控制(MVCC):是一種用來解決讀-寫衝突的無所併發控制,也就是爲事務分配單向增加的時間戳,爲每個修改保存一個版本,版本與事務時間戳關聯,讀操做只讀該事務開始前的數據庫的快照。這樣在讀操做不用阻塞操做且寫操做不用阻塞讀操做的同時,避免了髒讀和不可重複讀。
3. 一致性(Consistency)

 Elasticsearch集羣保證寫一致性的方式是在寫入前先檢查有多少個分片可供寫入,若是達到寫入條件,則進行寫操做,不然,Elasticsearch會等待更多的分片出現,默認爲一分鐘。
 有以下三種設置來判斷是否容許寫操做:

  • One:只要主分片可用,就能夠進行寫操做。
  • All:只有當主分片和全部副本均可用時,才容許寫操做。
  • Quorum(k-wu-wo/reng,法定人數):是Elasticsearch的默認選項。當有大部分的分片可用時才容許寫操做。
     其中,對「大部分」的計算公式爲int((primary+number_of_replicas)/2)+1。
     Elasticsearch集羣保證讀寫一致性的方式是,爲了保證搜索請求的返回結果是最新版本的文檔,備份能夠被設置爲sync(默認值),寫操做在主分片和備份分片同時完成後纔會返回寫請求的結果。這樣,不管搜索請求至哪一個分片都會返回最新的文檔。可是若是咱們的應用對寫要求很高,就能夠經過設置replication=async來提高寫的效率,若是設置replication=async,則只要主分片的寫完成,就會返回寫成功。
4. 腦裂

 在Elasticsearch集羣中主節點經過ping命令來檢查集羣中的其餘節點是否處於可用狀態,同時非主節點也會經過ping來檢查主節點是否處於可用狀態。當集羣網絡不穩定時,有可能會發生一個節點ping不通Master節點,則會認爲Master節點發生了故障,而後從新選出一個Master節點,這就會致使在一個集羣內出現多個Master節點。當在一個集羣中有多個Master節點時,就有可能會致使數據丟失。咱們稱這種現象爲腦裂。在5.4.7節會介紹如何避免腦裂的發生。

5.2.3 事務日誌

 咱們在5.1節瞭解到,Lucene爲了加快寫索引的速度,採用了延遲寫入的策略。雖然這種策略提升了寫入的效率,但其最大的弊端是,若是數據在內存中尚未持久化到磁盤上時發生了相似斷電等不可控狀況,就可能丟失數據。爲了不丟失數據,Elasticsearch添加了事務日誌(Translog),事務日誌記錄了全部尚未被持久化磁盤的數據。
 Elasticsearch寫索引的具體過程以下。
 首先,當有數據寫入時,爲了提高寫入的速度,並無數據直接寫在磁盤上,而是先寫入到內存中,可是爲了防止數據的丟失,會追加一份數據到事務日誌裏。由於內存中的數據還會繼續寫入,因此內存中的數據並非以段的形式存儲的,是檢索不到的。總之,Elasticsearch是一個準實時的搜索引擎,而不是一個實時的搜索引擎。此時的狀態如圖5-14所示。


 
圖 5-14 Elasticsearch寫數據的過程

 而後,當達到默認的時間(1秒鐘)或者內存的數據達到必定量時,會觸發一次刷新(Refresh)。刷新的主要步驟以下。
(1)將內存中的數據刷新到一個新的段中,可是該段並無持久化到硬盤中,而是緩存在操做系統的文件緩存系統中。雖然數據還在內存中,可是內存裏的數據和文件緩存系統裏的數據有如下區別。

  • 內存使用的是JVM的內存,而文件緩存系統使用的是操做系統的內存。
  • 內存的數據不是以段的形式存儲的,而且能夠繼續向內存裏寫數據。文件緩存系統中的數據是以段的形式存儲的,因此只能讀,不能寫。
  • 內存中的數據是搜索不到,文件緩存系統中的數據是能夠搜索的。

(2)打開保存在文件緩存系統中的段,使其可被搜索。
(3)清空內存,準備接收新的數據。日誌不作清空處理。
 此時的狀態如圖5-15所示。


 
圖 5-16 Elasticsearch寫數據的過程

 最後,刷新(Flush)。當日志數據的大小超過512MB或者時間超過30分鐘時,須要觸發一次刷新。刷新的主要步驟以下。

  1. 在文件緩存系統中建立一個新的段,並把內存中的數據寫入,使其可被搜索。
  2. 清空內存,準備接收新的數據。
  3. 將文件系統緩存中的數據經過fsync函數刷新到硬盤中。
  4. 生成提交點。
  5. 刪除舊的日誌,建立一個空的日誌。
     此時的狀態如圖5-17所示。


     
    圖 5-17 Elasticsearch寫數據的過程

     由上面索引建立的過程可知,內存裏面的數據並無直接被刷新(Flush)到硬盤中,而是被刷新(Refresh)到了文件緩存系統中,這主要是由於持久化數據十分耗費資源,頻繁地調用會使寫入的性能急劇降低,因此Elasticsearch,爲了提升寫入的效率,利用了文件緩存系統和內存來加速寫入時的性能,並使用日誌來防止數據的丟失。
     在須要重啓時,Elasticsearch不只要根據提交點去加載已經持久化過的段,還須要根據Translog裏的記錄,把未持久化的數據從新持久化到磁盤上。
     根據上面對Elasticsearch,寫操做流程的介紹,咱們能夠整理出一個索引數據所要經歷的幾個階段,以及每一個階段的數據的存儲方式和做用。如圖5-18所示。


     
    圖 5-18 Elasticsearch寫操做流程

5.2.4 在集羣中寫索引

 假設咱們有如圖5-19所示(圖片來自官網)的一個集羣,該集羣由三個節點組成(Node 一、Node 2和Node 3),包含一個由兩個主分片和每一個主分片由兩個副本分片組成的索引。其中,標星號的Node 1是Master節點,負責管理整個集羣的狀態;p1和p2是主分片;r0和r1是副本分片。爲了達到高可用,Master節點避免將主分片和副本放在同一個節點。


 
圖 5-19 寫索引

 將數據分片是爲了提升可處理數據的容量和易於進行水平擴展,爲分片作副本是爲了提升集羣的穩定性和提升併發量。在主分片掛掉後,會從副本分片中選舉出一個升級爲主分片,當副本升級爲主分片後,因爲少了一個副本分片,因此集羣狀態會從green改變爲yellow,可是此時集羣仍然可用。在一個集羣中有一個分片的主分片和副本分片都掛掉後,集羣狀態會由yellow改變爲red,集羣狀態爲red時集羣不可正常使用。

 由上面的步驟可知,副本分片越多,集羣的可用性就越高,可是因爲每一個分片都至關於一個Lucene的索引文件,會佔用必定的文件句柄、內存及CPU,而且分片間的數據同步也會佔用必定的網絡帶寬,因此,索引的分片數和副本數並非越多越好。

 寫索引時只能寫在主分片上,而後同步到副本上,那麼,一個數據應該被寫在哪一個分片上呢?如圖5-19所示,如何知道一個數據應該被寫在p0仍是p1上呢答案就是路由(routing),路由公式以下:

shard = hash(routing)%number_of_primary_shards 

 其中,routing是一個可選擇的值,默認是文檔的_id(文檔的惟一主鍵,文檔在建立時,若是文檔的_id已經存在,則進行更新,若是不存在則建立)。後面會介紹如何經過自定義routing參數使查詢落在一個分片中,而不用查詢全部的分片,從而提高查詢的性能。routing經過hash函數生成一個數字,將這個數字除以number_of_primary_shards(分片的數量)後獲得餘數。這個分佈在0到number_of_primary_shards - 1之間的餘數,就是咱們所尋求的文檔所在分片的位置。這也就說明了一旦分片數定下來就不能再改變的緣由,由於分片數改變以後,全部以前的路由值都會變得無效,前期建立的文檔也就找不到了。

 因爲在Elasticsearch集羣中每一個節點都知道集羣中的文檔的存放位置(經過路由公式定位),因此每一個節點都有處理讀寫請求的能力。在一個寫請求被髮送到集羣中的一個節點後,此時,該節點被稱爲協調點(Coordinating Node),協調點會根據路由公式計算出須要寫到哪一個分片上,再將請求轉發到該分片的主分片節點上。寫操做的流程以下(鍵圖5-20,圖片來自官網)。

(1)客戶端向Node 1(協調節點)發送寫請求。

(2)Node 1經過文檔的_id(默認是_id,但不表示必定是_id)肯定文檔屬於哪一個分片(在本例中是編號爲0的分片)。請求會被轉發到主分片所在的節點Node 3上。

(3)Node 3在主分片上執行請求,若是成功,則將請求並行轉發到Node 1和Node 2的副本分片上。一旦全部的副本分片都報告成功(默認),則Node 3將向協調節點報告成功,協調節點向客戶端報告成功。


 
圖 5-20 寫索引

5.2.5 集羣中的查詢流程

 根據routing字段進行的單個文檔的查詢,在Elasticsearch集羣中能夠在主分片或者副本分片上進行。查詢字段恰好是routing的分片字段如「_id」的查詢流程以下(見圖5-21,圖片來自官網)。
(1)客戶端向集羣發送查詢請求,集羣再隨機選擇一個節點做爲協調點(Node 1),負責處理此次查詢。
(2)Node 1使用文檔的routing id來計算要查詢的文檔在哪一個分片上(在本例中落在了0分片上)分片0的副本分片存在全部的三個節點上。在這種狀況下,協調節點能夠把請求轉發到任意節點,本例將請求轉發到Node 2上。
(3)Node 2執行查找,並將查找結果返回給協調節點Node 1,Node 1再將文檔返回給客戶端。

 
圖 5-22

 當一個搜索請求被髮送到某個節點時,這個節點就變成了協調節點(Node 1)。協調節點的任務是廣播查詢請求到全部分片(主分片或者副本分片),並將它們的響應結果整合成全局排序後的結果集合,由上面步驟3所示,默認返回給協調節點並非全部的數據,而是隻有文檔的id和得分score,由於咱們最後只返回給用戶size條數據,因此這樣作的好處是能夠節省不少帶寬,特別是from很大時。協調節點對收集回來的數據進行排序後,找到要返回的size條數據的id,再根據id查詢要返回的數據,好比title、content等。取回數據等流程以下(見圖5-23,圖片來自官網)。
(1)Node 3進行二次排序來找出要返回的文檔id,並向相關的分片提交多個得到文檔詳情的請求。
(2)每一個分片加載文檔,並將文檔返回給Node 3。
(3)一旦全部的文檔都取回了,Node 3就返回結果給客戶端。
 
圖 5-23

協調節點收集各個分片查詢出來的數據,再進行二次排序,而後選擇須要被取回的文檔。例如,若是咱們的查詢指定了{"from": 20, "size": 10},那麼咱們須要在每一個分片中查詢出來得分最高的20+10條數據,協調節點在收集到30✖️n(n爲分片數)條數據後再進行排序,排序位置在0-20的結果會被丟棄,只有從第21個開始的10個結果須要被取回。這些文檔可能來自多個甚至所有分片。
 由上面的搜索策略能夠知道,在查詢時深翻(Deep Pagination)並非一種好方法。由於深翻時,from會很大,這時的排序過程可能會變得很是沉重,會佔用大量的cpu、內存和帶寬。由於這個緣由,因此強烈建議慎重使用深翻。
分片能夠減小每一個片上的數據量,加快查詢的速度,可是在查詢時,協調節點要在收集數(from+size)✖️n條數據後再作一次全局排序,若這個數據量很大,則也會佔用大量的CPU、內存、帶寬等,而且分片查詢的速度取決於最慢的分片查詢的速度,因此分片數並非越多越好。
 ES的API應用測試,性能優化,開發使用等內容將在下一篇《Elasticsearch詳解-續》中完成。

《Elasticsearch詳解-續》

 

若是須要給我修改意見的發送郵箱:erghjmncq6643981@163.com
資料參考:《可伸縮服務架構》
轉發博客,請註明,謝謝。

走過路過不要錯過,您的支持是我持續技術輸出的動力所在,金額隨意,感謝!!

相關文章
相關標籤/搜索