Elasticsearch之原理詳解

1 Elasticsearch

1.1 簡介

ES是使用 Java 編寫的一種開源搜索引擎,它在內(nèi)部使用 Lucene 做索引與搜索,通過對 Lucene 的封裝,隱藏了 Lucene 的復(fù)雜性,取而代之的提供一套簡單一致的 RESTful API
然而,Elasticsearch 不僅僅是 Lucene,并且也不僅僅只是一個(gè)全文搜索引擎。

它可以被下面這樣準(zhǔn)確的形容:

  • 一個(gè)分布式的實(shí)時(shí)文檔存儲,每個(gè)字段可以被索引與搜索。
  • 一個(gè)分布式實(shí)時(shí)分析搜索引擎。
  • 能勝任上百個(gè)服務(wù)節(jié)點(diǎn)的擴(kuò)展,并支持 PB 級別的結(jié)構(gòu)化或者非結(jié)構(gòu)化數(shù)據(jù)。

官網(wǎng)對 Elasticsearch 的介紹是 Elasticsearch 是一個(gè)分布式、可擴(kuò)展、近實(shí)時(shí)的搜索與數(shù)據(jù)分析引擎。

其中主要有如下幾個(gè)核心術(shù)語需要理解:

  • 詞條(Term): 索引里面最小的存儲和查詢單元,對于英文來說是一個(gè)單詞,對于中文來說一般指分詞后的一個(gè)詞。
  • 詞典(Term Dictionary): 或字典,是詞條 Term 的集合。搜索引擎的通常索引單位是單詞,單詞詞典是由文檔集合中出現(xiàn)過的所有單詞構(gòu)成的字符串集合,單詞詞典內(nèi)每條索引項(xiàng)記載單詞本身的一些信息以及指向倒排列表的指針。
  • 倒排表(Post list):一個(gè)文檔通常由多個(gè)詞組成,倒排表記錄的是某個(gè)詞在哪些文檔里出現(xiàn)過以及出現(xiàn)的位置。每條記錄稱為一個(gè)倒排項(xiàng)(Posting)。倒排表記錄的不僅是文檔編號,還存儲了詞頻等信息。
  • 倒排文件(Inverted File): 所有單詞的倒排列表往往順序地存儲在磁盤的某個(gè)文件里,這個(gè)文件被稱之為倒排文件,倒排文件是存儲倒排索引的物理文件
    由屬性值來確定記錄的位置的結(jié)構(gòu)就是倒排索引。帶有倒排索引的文件稱為倒排文件

詞典倒排表Lucene 中很重要的兩種數(shù)據(jù)結(jié)構(gòu),是實(shí)現(xiàn)快速檢索的重要基石。詞典倒排文件是分兩部分存儲的,詞典在內(nèi)存中倒排文件存儲在磁盤

1.2 分片,副本,映射

1.2.1 分片(Shards)

ES 支持 PB 級全文搜索,當(dāng)索引上的數(shù)據(jù)量太大的時(shí)候,ES 通過水平拆分的方式將一個(gè)索引上的數(shù)據(jù)拆分出來分配到不同的數(shù)據(jù)塊上,拆分出來的數(shù)據(jù)庫塊稱之為一個(gè)分片。
這類似于 MySQL 的分庫分表,只不過 MySQL 分庫分表需要借助第三方組件而 ES 內(nèi)部自身實(shí)現(xiàn)了此功能。

在一個(gè)多分片的索引中寫入數(shù)據(jù)時(shí),通過路由來確定具體寫入哪一個(gè)分片中,所以在創(chuàng)建索引的時(shí)候需要指定分片的數(shù)量,并且分片的數(shù)量一旦確定就不能修改。
分片的數(shù)量和下面介紹的副本數(shù)量都是可以通過創(chuàng)建索引時(shí)的 Settings 來配置,ES 默認(rèn)為一個(gè)索引創(chuàng)建 5 個(gè)主分片, 并分別為每個(gè)分片創(chuàng)建一個(gè)副本。

PUT /myIndex  
{  
   "settings" : {  
      "number_of_shards" : 5,  
      "number_of_replicas" : 1  
   }  
}  

ES 通過分片的功能使得索引在規(guī)模上和性能上都得到提升,每個(gè)分片都是 Lucene 中的一個(gè)索引文件,每個(gè)分片必須有一個(gè)主分片零到多個(gè)副本。

1.2.2 副本(Replicas)

副本就是對分片的 Copy,每個(gè)主分片都有一個(gè)或多個(gè)副本分片,當(dāng)主分片異常時(shí),副本可以提供數(shù)據(jù)的查詢等操作。
主分片和對應(yīng)的副本分片是不會在同一個(gè)節(jié)點(diǎn)上的,所以副本分片數(shù)的最大值是 N-1(其中 N 為節(jié)點(diǎn)數(shù))。
對文檔的新建、索引和刪除請求都是寫操作,必須在主分片上面完成之后才能被復(fù)制到相關(guān)的副本分片。

ES 為了提高寫入的能力這個(gè)過程是并發(fā)寫的,同時(shí)為了解決并發(fā)寫的過程中數(shù)據(jù)沖突的問題,ES 通過樂觀鎖的方式控制,每個(gè)文檔都有一個(gè) _version (版本)號,當(dāng)文檔被修改時(shí)版本號遞增。
一旦所有的副本分片都報(bào)告寫成功才會向協(xié)調(diào)節(jié)點(diǎn)報(bào)告成功,協(xié)調(diào)節(jié)點(diǎn)向客戶端報(bào)告成功

image.png

從上圖可以看出為了達(dá)到高可用,Master 節(jié)點(diǎn)會避免將主分片和副本分片放在同一個(gè)節(jié)點(diǎn)上。

假設(shè)這時(shí)節(jié)點(diǎn) Node1 服務(wù)宕機(jī)了或者網(wǎng)絡(luò)不可用了,那么主節(jié)點(diǎn)上主分片 S0 也就不可用了。幸運(yùn)的是還存在另外兩個(gè)節(jié)點(diǎn)能正常工作,這時(shí) ES 會重新選舉新的主節(jié)點(diǎn),而且這兩個(gè)節(jié)點(diǎn)上存在我們所需要的 S0 的所有數(shù)據(jù)。我們會將 S0 的副本分片提升為主分片,這個(gè)提升主分片的過程是瞬間發(fā)生的。此時(shí)集群的狀態(tài)將會為 Yellow。

為什么我們集群狀態(tài)是 Yellow 而不是 Green 呢?雖然我們擁有所有的 2 個(gè)主分片,但是同時(shí)設(shè)置了每個(gè)主分片需要對應(yīng)兩份副本分片,而此時(shí)只存在一份副本分片。所以集群不能為 Green 的狀態(tài)。
如果我們同樣關(guān)閉了 Node2 ,我們的程序依然可以保持在不丟失任何數(shù)據(jù)的情況下運(yùn)行,因?yàn)?Node3 為每一個(gè)分片都保留著一份副本。
如果我們重新啟動 Node1 ,集群可以將缺失的副本分片再次進(jìn)行分配,那么集群的狀態(tài)又將恢復(fù)到原來的正常狀態(tài)。
如果 Node1 依然擁有著之前的分片,它將嘗試去重用它們,只不過這時(shí) Node1 節(jié)點(diǎn)上的分片不再是主分片而是副本分片了,如果期間有更改的數(shù)據(jù)只需要從主分片上復(fù)制修改的數(shù)據(jù)文件即可。

小結(jié):

  • 將數(shù)據(jù)分片是為了提高可處理數(shù)據(jù)的容量和易于進(jìn)行水平擴(kuò)展,為分片做副本是為了提高集群的穩(wěn)定性和提高并發(fā)量。
  • 副本是乘法,越多消耗越大,但也越保險(xiǎn)。分片是除法,分片越多,單分片數(shù)據(jù)就越少也越分散。
  • 副本越多,集群的可用性就越高,但是由于每個(gè)分片都相當(dāng)于一個(gè) Lucene 的索引文件,會占用一定的文件句柄、內(nèi)存及 CPU。并且分片間的數(shù)據(jù)同步也會占用一定的網(wǎng)絡(luò)帶寬,所以索引的分片數(shù)和副本數(shù)也不是越多越好。

1.2.3 映射(Mapping)

映射是用于定義 ES 對索引中字段的存儲類型、分詞方式和是否存儲等信息,就像數(shù)據(jù)庫中的 Schema ,描述了文檔可能具有的字段或?qū)傩浴⒚總€(gè)字段的數(shù)據(jù)類型。
只不過關(guān)系型數(shù)據(jù)庫建表時(shí)必須指定字段類型,而 ES 對于字段類型可以不指定然后動態(tài)對字段類型猜測,也可以在創(chuàng)建索引時(shí)具體指定字段的類型。
對字段類型根據(jù)數(shù)據(jù)格式自動識別的映射稱之為動態(tài)映射(Dynamic Mapping),我們創(chuàng)建索引時(shí)具體定義字段類型的映射稱之為靜態(tài)映射或顯示映射(Explicit Mapping)。

在講解動態(tài)映射和靜態(tài)映射的使用前,我們先來了解下 ES 中的數(shù)據(jù)有哪些字段類型?之后我們再講解為什么我們創(chuàng)建索引時(shí)需要建立靜態(tài)映射而不使用動態(tài)映射。

ES(v6.8)中字段數(shù)據(jù)類型主要有以下幾類:

類別 數(shù)據(jù)類型
核心類型 text,keywords,long,integer,short,double,data,boolean
復(fù)雜類型 Object,Nested
地理類型 geo_point,geo_shape
特殊類型 ip,completion,token_count,join

Text 用于索引全文值的字段,例如電子郵件正文或產(chǎn)品說明。這些字段是被分詞的,它們通過分詞器傳遞 ,以在被索引之前將字符串轉(zhuǎn)換為單個(gè)術(shù)語的列表。分析過程允許 Elasticsearch 搜索單個(gè)單詞中每個(gè)完整的文本字段。文本字段不用于排序,很少用于聚合。
Keyword 用于索引結(jié)構(gòu)化內(nèi)容的字段,例如電子郵件地址,主機(jī)名,狀態(tài)代碼,郵政編碼或標(biāo)簽。它們通常用于過濾,排序,和聚合。Keyword 字段只能按其確切值進(jìn)行搜索。

通過對字段類型的了解我們知道有些字段需要明確定義的,例如某個(gè)字段是 Text 類型還是 Keyword 類型差別是很大的,時(shí)間字段也許我們需要指定它的時(shí)間格式,還有一些字段我們需要指定特定的分詞器等等。

如果采用動態(tài)映射是不能精確做到這些的,自動識別常常會與我們期望的有些差異。所以創(chuàng)建索引的時(shí)候一個(gè)完整的格式應(yīng)該是指定分片和副本數(shù)以及 Mapping的定義,如下:

PUT my_index   
{  
   "settings" : {  
      "number_of_shards" : 5,  
      "number_of_replicas" : 1  
   }  
  "mappings": {  
    "_doc": {   
      "properties": {   
        "title":    { "type": "text"  },   
        "name":     { "type": "text"  },   
        "age":      { "type": "integer" },    
        "created":  {  
          "type":   "date",   
          "format": "strict_date_optional_time||epoch_millis"  
        }  
      }  
    }  
  }  
}  

1.3 ES機(jī)制原理

1.3.1 寫索引原理

下圖描述了 3 個(gè)節(jié)點(diǎn)的集群,共擁有 12 個(gè)分片,其中有 4 個(gè)主分片(S0、S1、S2、S3)和 8 個(gè)副本分片(R0、R1、R2、R3),每個(gè)主分片對應(yīng)兩個(gè)副本分片,節(jié)點(diǎn) 1 是主節(jié)點(diǎn)(Master 節(jié)點(diǎn))負(fù)責(zé)整個(gè)集群的狀態(tài)。


image.png

寫索引只能寫在主分片上,然后同步到副本分片。這里有四個(gè)主分片,一條數(shù)據(jù) ES 是根據(jù)什么規(guī)則寫到特定分片上的呢?
這條索引數(shù)據(jù)為什么被寫到 S0 上而不寫到 S1 或 S2 上?那條數(shù)據(jù)為什么又被寫到 S3 上而不寫到 S0 上了?

首先這肯定不會是隨機(jī)的,否則將來要獲取文檔的時(shí)候我們就不知道從何處尋找了。實(shí)際上,這個(gè)過程是根據(jù)下面這個(gè)公式?jīng)Q定的:

shard = hash(routing) % number_of_primary_shards  

Routing 是一個(gè)可變值,默認(rèn)是文檔的 _id ,也可以設(shè)置成一個(gè)自定義的值。
Routing 通過 Hash 函數(shù)生成一個(gè)數(shù)字,然后這個(gè)數(shù)字再除以 number_of_primary_shards (主分片的數(shù)量)后得到余數(shù)。這個(gè)在 0number_of_primary_shards-1 之間的余數(shù),就是我們所尋求的文檔所在分片的位置。

這就解釋了為什么我們要在創(chuàng)建索引的時(shí)候就確定好主分片的數(shù)量并且永遠(yuǎn)不會改變這個(gè)數(shù)量:因?yàn)槿绻麛?shù)量變化了,那么所有之前路由的值都會無效,文檔也再也找不到了

由于在 ES 集群中每個(gè)節(jié)點(diǎn)通過上面的計(jì)算公式都知道集群中的文檔的存放位置,所以每個(gè)節(jié)點(diǎn)都有處理讀寫請求的能力。
在一個(gè)寫請求被發(fā)送到某個(gè)節(jié)點(diǎn)后,該節(jié)點(diǎn)即為前面說過的協(xié)調(diào)節(jié)點(diǎn),協(xié)調(diào)節(jié)點(diǎn)會根據(jù)路由公式計(jì)算出需要寫到哪個(gè)分片上,再將請求轉(zhuǎn)發(fā)到該分片的主分片節(jié)點(diǎn)上

image.png

假如此時(shí)數(shù)據(jù)通過路由計(jì)算公式取余后得到的值是

shard=hash(routing)%4=0

則具體流程如下:

  • 客戶端向 ES1 節(jié)點(diǎn)(協(xié)調(diào)節(jié)點(diǎn))發(fā)送寫請求,通過路由計(jì)算公式得到值為 0,則當(dāng)前數(shù)據(jù)應(yīng)被寫到主分片 S0 上。
  • ES1 節(jié)點(diǎn)將請求轉(zhuǎn)發(fā)到 S0 主分片所在的節(jié)點(diǎn) ES3,ES3 接受請求并寫入到磁盤。
  • 并發(fā)將數(shù)據(jù)復(fù)制到兩個(gè)副本分片 R0 上,其中通過樂觀并發(fā)控制數(shù)據(jù)的沖突。一旦所有的副本分片都報(bào)告成功,則節(jié)點(diǎn) ES3 將向協(xié)調(diào)節(jié)點(diǎn)報(bào)告成功,協(xié)調(diào)節(jié)點(diǎn)向客戶端報(bào)告成功。

1.3.2 存儲原理

上面介紹了在 ES 內(nèi)部索引的寫處理流程,這個(gè)流程是在 ES內(nèi)存中執(zhí)行的,數(shù)據(jù)被分配到特定的分片副本上之后,最終是存儲到磁盤上的,這樣在斷電的時(shí)候就不會丟失數(shù)據(jù)。
具體的存儲路徑可在配置文件 ../config/elasticsearch.yml 中進(jìn)行設(shè)置,默認(rèn)存儲在安裝目錄的 Data 文件夾下。
建議不要使用默認(rèn)值,因?yàn)槿?ES 進(jìn)行了升級,則有可能導(dǎo)致數(shù)據(jù)全部丟失:

path.data: /path/to/data  //索引數(shù)據(jù)  
path.logs: /path/to/logs  //日志記錄  

1.3.2.1 分段存儲

索引文檔以的形式存儲在磁盤上,索引文件被拆分為多個(gè)子文件,則每個(gè)子文件叫作,每一個(gè)段本身都是一個(gè)倒排索引,并且段具有不變性,一旦索引的數(shù)據(jù)被寫入硬盤,就不可再修改。

在底層采用了分段存儲模式,使它在讀寫時(shí)幾乎完全避免了鎖的出現(xiàn),大大提升了讀寫性能。
段被寫入到磁盤后會生成一個(gè)提交點(diǎn),提交點(diǎn)是一個(gè)用來記錄所有提交后段信息的文件。

一個(gè)段一旦擁有了提交點(diǎn),就說明這個(gè)段只有讀的權(quán)限,失去了寫的權(quán)限 。相反, 當(dāng)段在內(nèi)存中時(shí),就只有寫的權(quán)限,而不具備讀數(shù)據(jù)的權(quán)限,意味著不能被檢索。

段的概念提出主要是因?yàn)椋涸谠缙谌臋z索中為整個(gè)文檔集合建立了一個(gè)很大的倒排索引,并將其寫入磁盤中。如果索引有更新,就需要重新全量創(chuàng)建一個(gè)索引來替換原來的索引。這種方式在數(shù)據(jù)量很大時(shí)效率很低,并且由于創(chuàng)建一次索引的成本很高,所以對數(shù)據(jù)的更新不能過于頻繁,也就不能保證時(shí)效性。

索引文件分段存儲并且不可修改,那么新增、更新和刪除如何處理呢?

  • 新增,新增很好處理,由于數(shù)據(jù)是新的,所以只需要對當(dāng)前文檔新增一個(gè)段就可以了。
  • 刪除,由于不可修改,所以對于刪除操作,不會把文檔從舊的段中移除而是通過新增一個(gè) .del 文件,文件中會列出這些被刪除文檔的段信息。這個(gè)被標(biāo)記刪除的文檔仍然可以被查詢匹配到, 但它會在最終結(jié)果被返回前從結(jié)果集中移除。
  • 更新,不能修改舊的段來進(jìn)行反映文檔的更新,其實(shí)更新相當(dāng)于是刪除新增這兩個(gè)動作組成。會將舊的文檔在 .del 文件中標(biāo)記刪除,然后文檔的新版本被索引到一個(gè)新的段中??赡軆蓚€(gè)版本的文檔都會被一個(gè)查詢匹配到,但被刪除的那個(gè)舊版本文檔在結(jié)果集返回前就會被移除。

段被設(shè)定為不可修改具有一定的優(yōu)勢也有一定的缺點(diǎn),優(yōu)勢主要表現(xiàn)在:

  • 不需要鎖,如果從來不更新索引,那就不需要擔(dān)心多進(jìn)程同時(shí)修改數(shù)據(jù)的問題。
  • 一旦索引被讀入內(nèi)核的文件系統(tǒng)緩存,便會留在哪里,由于其不變性。只要文件系統(tǒng)緩存中還有足夠的空間,那么大部分讀請求會直接請求內(nèi)存,而不會命中磁盤。這提供了很大的性能提升。
  • 其它緩存(像 Filter 緩存),在索引的生命周期內(nèi)始終有效。它們不需要在每次數(shù)據(jù)改變時(shí)被重建,因?yàn)閿?shù)據(jù)不會變化。
  • 寫入單個(gè)大的倒排索引允許數(shù)據(jù)被壓縮,減少磁盤 I/O 和需要被緩存到內(nèi)存的索引的使用量。

段的不變性的缺點(diǎn)如下:

  • 當(dāng)對舊數(shù)據(jù)進(jìn)行刪除時(shí),舊數(shù)據(jù)不會馬上被刪除,而是在 .del 文件中被標(biāo)記為刪除。而舊數(shù)據(jù)只能等到段更新時(shí)才能被移除,這樣會造成大量的空間浪費(fèi)。
  • 若有一條數(shù)據(jù)頻繁的更新,每次更新都是新增新的標(biāo)記舊的,則會有大量的空間浪費(fèi)。
  • 每次新增數(shù)據(jù)時(shí)都需要新增一個(gè)段來存儲數(shù)據(jù)。當(dāng)段的數(shù)量太多時(shí),對服務(wù)器的資源例如文件句柄的消耗會非常大。
  • 在查詢的結(jié)果中包含所有的結(jié)果集,需要排除被標(biāo)記刪除的舊數(shù)據(jù),這增加了查詢的負(fù)擔(dān)。

1.3.2.2 延遲寫策略

介紹完了存儲的形式,那么索引寫入到磁盤的過程是怎樣的?是否是直接調(diào) Fsync物理性地寫入磁盤?
答案是顯而易見的,如果是直接寫入到磁盤上,磁盤的 I/O 消耗上會嚴(yán)重影響性能。那么當(dāng)寫數(shù)據(jù)量大的時(shí)候會造成 ES 停頓卡死,查詢也無法做到快速響應(yīng)。如果真是這樣 ES 也就不會稱之為近實(shí)時(shí)全文搜索引擎了。

為了提升寫的性能,ES 并沒有每新增一條數(shù)據(jù)就增加一個(gè)段到磁盤上,而是采用延遲寫的策略。每當(dāng)有新增的數(shù)據(jù)時(shí),就將其先寫入到內(nèi)存中,在內(nèi)存磁盤之間是文件系統(tǒng)緩存
當(dāng)達(dá)到默認(rèn)的時(shí)間(1 秒鐘)或者內(nèi)存的數(shù)據(jù)達(dá)到一定量時(shí),會觸發(fā)一次刷新(Refresh),將內(nèi)存中的數(shù)據(jù)生成到一個(gè)新的段上并緩存到文件緩存系統(tǒng) 上,稍后再被刷新到磁盤中并生成提交點(diǎn)。
這里的內(nèi)存使用的是 ESJVM 內(nèi)存,而文件緩存系統(tǒng)使用的是操作系統(tǒng)的內(nèi)存。

新的數(shù)據(jù)會繼續(xù)的被寫入內(nèi)存,但內(nèi)存中的數(shù)據(jù)并不是以段的形式存儲的,因此不能提供檢索功能。由內(nèi)存刷新到文件緩存系統(tǒng)的時(shí)候會生成新的段,并將段打開以供搜索使用,而不需要等到被刷新到磁盤。

Elasticsearch 中,寫入和打開一個(gè)新段的輕量的過程叫做 Refresh (即內(nèi)存刷新到文件緩存系統(tǒng))。默認(rèn)情況下每個(gè)分片會每秒自動刷新一次。這就是為什么我們說 Elasticsearch近實(shí)時(shí)搜索,因?yàn)槲臋n的變化并不是立即對搜索可見,但會在一秒之內(nèi)變?yōu)榭梢姟?br> 我們也可以手動觸發(fā) Refresh,POST /_refresh 刷新所有索引,POST /nba/_refresh 刷新指定的索引。

注意:盡管刷新是比提交輕量很多的操作,它還是會有性能開銷。當(dāng)寫測試的時(shí)候, 手動刷新很有用,但是不要在生產(chǎn)環(huán)境下每次索引一個(gè)文檔都去手動刷新。而且并不是所有的情況都需要每秒刷新。

假如正在使用 Elasticsearch 索引大量的日志文件, 想優(yōu)化索引速度而不是近實(shí)時(shí)搜索。這時(shí)可以在創(chuàng)建索引時(shí)在 Settings 中通過調(diào)大 refresh_interval = "30s" 的值 , 降低每個(gè)索引的刷新頻率,設(shè)值時(shí)需要注意后面帶上時(shí)間單位,否則默認(rèn)是毫秒。當(dāng) refresh_interval=-1 時(shí)表示關(guān)閉索引的自動刷新。

雖然通過延時(shí)寫的策略可以減少數(shù)據(jù)往磁盤上寫的次數(shù)并提升了整體的寫入能力,但是我們知道文件緩存系統(tǒng)也是內(nèi)存空間,屬于操作系統(tǒng)的內(nèi)存,只要是內(nèi)存都存在斷電或異常情況下丟失數(shù)據(jù)的危險(xiǎn)。
為了避免丟失數(shù)據(jù),Elasticsearch 添加了事務(wù)日志(Translog),事務(wù)日志記錄了所有還沒有持久化到磁盤的數(shù)據(jù)

image.png

添加了事務(wù)日志后整個(gè)寫索引的流程如上圖所示:

  • 一個(gè)新文檔被索引之后,先被寫入到內(nèi)存中,但是為了防止數(shù)據(jù)的丟失,會追加一份數(shù)據(jù)到事務(wù)日志中。
  • 不斷有新的文檔被寫入到內(nèi)存,同時(shí)也都會記錄到事務(wù)日志中。這時(shí)新數(shù)據(jù)還不能被檢索和查詢。
  • 當(dāng)達(dá)到默認(rèn)的刷新時(shí)間或內(nèi)存中的數(shù)據(jù)達(dá)到一定量后,會觸發(fā)一次 Refresh,將內(nèi)存中的數(shù)據(jù)以一個(gè)新段形式刷新到文件緩存系統(tǒng)中并清空內(nèi)存。這時(shí)雖然新段未被提交到磁盤,但是可以提供文檔的檢索功能且不能被修改。
  • 隨著新文檔索引不斷被寫入,當(dāng)日志數(shù)據(jù)大小超過 512M 或者時(shí)間超過 30 分鐘時(shí),會觸發(fā)一次 Flush
  • 內(nèi)存中的數(shù)據(jù)被寫入到一個(gè)新段同時(shí)被寫入到文件緩存系統(tǒng),文件系統(tǒng)緩存中數(shù)據(jù)通過 Fsync 刷新到磁盤中,生成提交點(diǎn),日志文件被刪除,創(chuàng)建一個(gè)空的新日志。

通過這種方式當(dāng)斷電或需要重啟時(shí),ES 不僅要根據(jù)提交點(diǎn)去加載已經(jīng)持久化過的段,還需要工具 Translog 里的記錄,把未持久化的數(shù)據(jù)重新持久化到磁盤上,避免了數(shù)據(jù)丟失的可能。

1.3.2.3 段合并

由于自動刷新流程每秒會創(chuàng)建一個(gè)新的段 ,這樣會導(dǎo)致短時(shí)間內(nèi)的段數(shù)量暴增。而段數(shù)目太多會帶來較大的麻煩。每一個(gè)段都會消耗文件句柄、內(nèi)存和 CPU 運(yùn)行周期。更重要的是,每個(gè)搜索請求都必須輪流檢查每個(gè)段然后合并查詢結(jié)果,所以段越多,搜索也就越慢。

Elasticsearch 通過在后臺定期進(jìn)行段合并來解決這個(gè)問題。小的段被合并到大的段,然后這些大的段再被合并到更大的段。

段合并的時(shí)候會將那些舊的已刪除文檔從文件系統(tǒng)中清除。被刪除的文檔不會被拷貝到新的大段中。合并的過程中不會中斷索引和搜索。


image.png

段合并在進(jìn)行索引和搜索時(shí)會自動進(jìn)行,合并進(jìn)程選擇一小部分大小相似的段,并且在后臺將它們合并到更大的段中,這些段既可以是未提交的也可以是已提交的
合并結(jié)束后老的段會被刪除,新的段被 Flush 到磁盤,同時(shí)寫入一個(gè)包含新段且排除舊的和較小的段的新提交點(diǎn),新的段被打開可以用來搜索。

段合并的計(jì)算量龐大, 而且還要吃掉大量磁盤 I/O,段合并會拖累寫入速率,如果任其發(fā)展會影響搜索性能。
Elasticsearch 在默認(rèn)情況下會對合并流程進(jìn)行資源限制,所以搜索仍然有足夠的資源很好地執(zhí)行。

1.4 性能優(yōu)化

1.4.1 存儲設(shè)備

磁盤在現(xiàn)代服務(wù)器上通常都是瓶頸。Elasticsearch 重度使用磁盤,磁盤能處理的吞吐量越大,節(jié)點(diǎn)就越穩(wěn)定。

這里有一些優(yōu)化磁盤 I/O 的技巧:

  • 使用 SSD。比機(jī)械磁盤優(yōu)秀多了。
  • 使用 RAID 0。條帶化 RAID 會提高磁盤 I/O,代價(jià)顯然就是當(dāng)一塊硬盤故障時(shí)整個(gè)就故障了。不要使用鏡像或者奇偶校驗(yàn) RAID 因?yàn)楦北疽呀?jīng)提供了這個(gè)功能。
  • 使用多塊硬盤,并允許 Elasticsearch 通過多個(gè) path.data 目錄配置把數(shù)據(jù)條帶化分配到它們上面。
  • 不要使用遠(yuǎn)程掛載的存儲,比如 NFS 或者 SMB/CIFS。這個(gè)引入的延遲對性能來說完全是背道而馳的。

1.4.2 內(nèi)部索引優(yōu)化

image.png

Elasticsearch 為了能快速找到某個(gè) Term,先將所有的 Term 排個(gè)序,然后根據(jù)二分法查找 Term,時(shí)間復(fù)雜度為 logN,就像通過字典查找一樣,這就是 Term Dictionary。
現(xiàn)在再看起來,似乎和傳統(tǒng)數(shù)據(jù)庫通過 B-Tree 的方式類似。但是如果 Term 太多,Term Dictionary 也會很大,放內(nèi)存不現(xiàn)實(shí),于是有了 Term Index。
就像字典里的索引頁一樣,A 開頭的有哪些 Term,分別在哪頁,可以理解 Term Index是一棵樹。這棵樹不會包含所有的 Term,它包含的是 Term 的一些前綴。通過 Term Index 可以快速地定位到 Term Dictionary 的某個(gè) Offset,然后從這個(gè)位置再往后順序查找。

在內(nèi)存中用 FST 方式壓縮 Term Index,FST 以字節(jié)的方式存儲所有的 Term,這種壓縮方式可以有效的縮減存儲空間,使得 Term Index 足以放進(jìn)內(nèi)存,但這種方式也會導(dǎo)致查找時(shí)需要更多的 CPU 資源。

對于存儲在磁盤上的倒排表同樣也采用了壓縮技術(shù)減少存儲所占用的空間。

1.4.3 調(diào)整配置參數(shù)

調(diào)整配置參數(shù)建議如下:

  • 給每個(gè)文檔指定有序的具有壓縮良好的序列模式 ID,避免隨機(jī)的 UUID-4這樣的 ID,這樣的 ID 壓縮比很低,會明顯拖慢 Lucene。
  • 對于那些不需要聚合和排序的索引字段禁用 Doc valuesDoc Values 是有序的基于 document=>field value 的映射列表。
  • 不需要做模糊檢索的字段使用 Keyword 類型代替 Text 類型,這樣可以避免在建立索引前對這些文本進(jìn)行分詞。
  • 如果搜索結(jié)果不需要近實(shí)時(shí)的準(zhǔn)確度,考慮把每個(gè)索引的 index.refresh_interval 改到 30s
  • 如果在做大批量導(dǎo)入,導(dǎo)入期間可以通過設(shè)置這個(gè)值為 -1 關(guān)掉刷新,還可以通過設(shè)置 index.number_of_replicas: 0 關(guān)閉副本。別忘記在完工的時(shí)候重新開啟它。
  • 避免深度分頁查詢建議使用 Scroll 進(jìn)行分頁查詢。普通分頁查詢時(shí),會創(chuàng)建一個(gè) from+size 的空優(yōu)先隊(duì)列,每個(gè)分片會返回 from+size 條數(shù)據(jù),默認(rèn)只包含文檔 ID 和得分 Score 給協(xié)調(diào)節(jié)點(diǎn)。
  • 如果有 N 個(gè)分片,則協(xié)調(diào)節(jié)點(diǎn)再對(from+size)×n條數(shù)據(jù)進(jìn)行二次排序,然后選擇需要被取回的文檔。當(dāng) from 很大時(shí),排序過程會變得很沉重,占用 CPU 資源嚴(yán)重。
  • 減少映射字段,只提供需要檢索,聚合或排序的字段。其他字段可存在其他存儲設(shè)備上,例如 Hbase,在 ES 中得到結(jié)果后再去 Hbase 查詢這些字段。
  • 創(chuàng)建索引和查詢時(shí)指定路由 Routing 值,這樣可以精確到具體的分片查詢,提升查詢效率。路由的選擇需要注意數(shù)據(jù)的分布均衡。

轉(zhuǎn)載于:https://mp.weixin.qq.com/s/Fy_FXDHNIVK3A3lAwfflNw

1.5 與關(guān)系型數(shù)據(jù)庫聯(lián)系

1.5.1 與SQL發(fā)展

眾所周知,Elasticsearch 是一個(gè)基于 Apache LuceneLucene 可以被認(rèn)為是迄今為止最先進(jìn)、性能最好的、功能最全的搜索引擎庫。

Elasticsearch 是一種 NoSQL 數(shù)據(jù)庫(非關(guān)系型數(shù)據(jù)庫),和常規(guī)的關(guān)系型數(shù)據(jù)庫(比如:MySQL,Oralce等)的基本概念,對應(yīng)關(guān)系如下:

  • Elasticsearch:index --> type --> doc --> field
  • MySQL:數(shù)據(jù)庫 --> 數(shù)據(jù)表 --> 行 --> 列

因?yàn)殛P(guān)系型數(shù)據(jù)庫比非關(guān)系型數(shù)據(jù)庫的概念提出的早,而且很成熟,應(yīng)用廣泛。所以,后來很多 NoSQL(包括:MongoDB,Elasticsearch 等)都參考并延用了傳統(tǒng)關(guān)系型數(shù)據(jù)庫的基本概念。

1.5.2 ES去除表 type

一個(gè)客觀的現(xiàn)象和事實(shí)是Elasticsearch 官網(wǎng)提出的近期版本對 type 概念的演變情況如下:

  • 5.X 版本中,一個(gè) index 下可以創(chuàng)建多個(gè) type;
  • 6.X 版本中,一個(gè) index 下只能存在一個(gè) type;
  • 7.X 版本中,直接去除了 type 的概念,就是說 index 不再會有 type。

為何要去除 type 的概念?
為何不是在 6.X 版本開始就直接去除 type,而是要逐步去除type?

為何要去除 type 的概念?

因?yàn)?Elasticsearch 設(shè)計(jì)初期,是直接查考了關(guān)系型數(shù)據(jù)庫的設(shè)計(jì)模式,存在了 type(數(shù)據(jù)表)的概念。
但是,其搜索引擎是基于 Lucene 的,這種 “基因”決定了 type 是多余的。 Lucene 的全文檢索功能之所以快,是因?yàn)?倒序索引 的存在。
而這種 倒序索引 的生成是基于 index 的,而并非 type。多個(gè)type 反而會減慢搜索的速度。
為了保持 Elasticsearch 一切為了搜索 的宗旨,適當(dāng)?shù)淖鲂└淖儯ㄈコ?type)也是無可厚非的,也是值得的。

為何不是在 6.X 版本開始就直接去除 type,而是要逐步去除type?

因?yàn)闅v史原因,前期 Elasticsearch 支持一個(gè) index 下存在多個(gè) type 的,而且,有很多項(xiàng)目在使用 Elasticsearch 作為數(shù)據(jù)庫。
如果直接去除 type 的概念,不僅是很多應(yīng)用 Elasticsearch 的項(xiàng)目將面臨 業(yè)務(wù)、功能和代碼的大改,
而且對于 Elasticsearch 官方來說,也是一個(gè)巨大的挑戰(zhàn)(這個(gè)是傷筋動骨的大手術(shù),很多涉及到 type 源碼是要修改的)。
所以,權(quán)衡利弊,采取逐步過渡的方式,最終,推遲到 7.X 版本才完成 去除 type 這個(gè) 革命性的變革。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容