天天看點

ElasticSearch原理篇

一、基本資訊

1.簡介

介紹ElasticSearch前我們先預熱一些常識,提到非結構化資料的檢索的時候,經常聽到Lucene、Solr、ElasticSearch,那他們的差別是什麼呢?

Apache Lucene不是搜尋引擎,而是一個開源的搜尋引擎工具包,他提供了查詢和檢索能力。Lucene是為了給開發人員提供一個簡單易用的搜尋工具包,以友善開發人員的在目标系統中實作全文檢索的功能,或者是以此為基礎建立起完整的全文檢索引擎。

Solr是一個基于Apache Lucene的高性能企業級搜尋引擎,它具有高可用、易擴充、可容錯特性,提供分布式的索引存儲、多副本、基于負載均衡的查詢、中心化配置等能力。

ElasticSearch是一個基于Apache Lucene的分布式搜尋引擎,它還可以作為實時文檔存儲系統,并且文檔中的每一個内容都可以被檢索,能夠處理PB級别的結構化或非結構化資料。

Elasticsearch本身就具有分布式的特性和易安裝使用的特點,目前已經是最受歡迎的企業搜尋引擎; Solr 的分布式需要借助第三方來實作,例如通過使用 ZooKeeper 來達到分布式協調管理。

2.名詞解釋

為了友善了解,在接下來的介紹中,将Index、Type、Document、Field和關系資料庫中的概念做了一個類比,注意這裡僅僅是類比,友善第一次接觸搜尋引擎的小夥伴了解,實際上他們還是有較大的差異。

1)索引(Index)

索引指的是包含一堆有相似結構的文檔資料(Document)。例如:商品索引、訂單索引。在商品索引中我們可以存放成千上萬的商品資訊;在訂單索引中我們可以存放成千上萬的訂單資訊。

這個角度上看,Index和關系型資料庫中的資料庫類似。

2)類型(Type)

類型是索引内部的邏輯分區(category/partition),每個Index裡都可以有一個或多個type,type是Index中的一個邏輯資料分類,一個type下的document有相同的field。比如:在商品索引中,服裝和旅遊路線,雖然都是可售賣的商品,但因為有很多屬性都不同,就可以為他們定義兩種Type;另外,也可以為類目定義一個Type。示例如下:

服裝商品的Type:商品名字、價格、描述

旅遊路線商品的Type:商品名字、價格、描述、行程資訊

這個角度上看,Type和關系型資料庫中的表類似。

3)文檔&屬性(Document&Field

一個document是一條執行個體資料,例如一個商品,一個訂單,通常使用JSON資料結構表示。一個document裡面有多個field,每個field就是一個資料字段。

從這個角度上看,Document和關系型資料庫中的一行記錄類似,Field和關系型資料庫中的字段類似。

這就是一個簡單的Document示例:

{
    _Index: "twitter",
    _type: "_doc",
    _id: "1",
    _version: 1,
    _seq_no: 0,
    _primary_term: 1,
    found: true,
    _source: {
        user: "kimchy",
        post_date: "2009-11-15T13:12:00",
        message: "Trying out Elasticsearch, so far so good?"
    }
}           

4)詞條(Term)

索引裡面最小的存儲和查詢單元,對于英文來說是一個單詞,對于中文來說一般指分詞後的一個詞。

5)詞典(Term Dictionary)

也稱字典,是詞條Term 的集合。搜尋引擎的通常索引機關是單詞,單詞詞典是由文檔集合中出現過的所有單詞構成的字元串集合,單詞詞典内每條索引項記載單詞本身的一些資訊以及指向“倒排清單”的指針。

6)正排資料

搜尋引擎的通用叫法,即原始資料,可以了解為一個doc list。

7)反向索引(inverted index)

lucene索引的通用叫法,即實作了term到doc list的映射。反向索引建立的是分詞(Term)和文檔(Document)之間的映射關系,在反向索引中,資料是面向詞(Term)而不是面向文檔的。

8)倒排表(Post list)

一個文檔通常由多個詞組成,倒排表記錄的是某個詞在哪些文檔裡出現過以及出現的位置。

每條記錄稱為一個倒排項(Posting)。倒排表記錄的不單是文檔編号,還存儲了詞頻等資訊。

9)倒排檔案(Inverted File)

所有單詞的倒排清單往往順序地存儲在磁盤的某個檔案裡,這個檔案被稱之為倒排檔案,倒排檔案是存儲反向索引的實體檔案。

二、索引原理

1.反向索引的結構

假如現有三份資料文檔,文檔的内容如下:

Doc1: I love China。

Doc2: I love work。

Doc3: I love coding。

為了建立反向索引,首先通過分詞器将每個文檔的内容域拆分成單獨的詞(我們稱它為詞條或Term),建立一個包含所有不重複詞條的排序清單,然後列出每個詞條出現在哪個文檔。結果如下:

Term Doc1 Doc2 Doc3
I Y
China
coding
love
work

如上表格所示,這種結構由文檔中所有不重複詞的清單構成,對于其中每個詞都有一個Doc與之關聯。這種由屬性值來确定記錄的位置的結構就是反向索引(Post Index)。帶有反向索引的檔案我們稱為倒排檔案(Inverted File)。

根據以上内容,倒排檔案的結構如下所示:

ElasticSearch原理篇

詞典和倒排表是 Lucene 中很重要的兩種資料結構,是實作快速檢索的重要基石。

2.搜尋

1)建立反向索引

上面簡單介紹了反向索引的結構,接下來以一個示例講述反向索引是如何做到高效搜尋的。假設有一張學生成績表,資料如下所示:

id name gender score
1 Fern female 80
2 Alice 70
3 Bunny male 90
4 Aimee
5 Amy
6 tracy

資料存儲到ES中後,會建立以下反向索引:

Name

Posting List

Gender

[1,2,4,5,6]

Score

[2,4]
[1,5]
[3,6]

以上為詞條和反向索引的關系,即當我們搜尋score=70的記錄的時候,通過反向索引能直接找到[2,4]記錄,然後傳回對應的記錄。

2)詞典

詞典是詞條的集合,當詞條很多的時候,ES是如何保證高效查詢的呢?

在Mysql資料庫中也有類似的問題,當記錄數很多的時候,如何快速找到需要的記錄呢?Mysql的做法是建立索引,Mysql的索引是使用B+數來實作的,如果查詢條件能使用上索引,那麼就直接将查詢條件和索引進行比對,因為索引是B+數,是以查詢效率将會非常高。ES的做法也是類似的,即:對所有的詞條進行排序,然後通過二分法查找,時間複雜度降低為lgN。

假設name的取值如下:

Fern,Alice,Bunny,Aimee,Amy,tracy

經過排序後的:

Aimee,Alice,Amy,Bunny,Fern,tracy

Mysql的索引存儲在磁盤上,ES的詞典存儲在記憶體中,上面這種做法,ES雖然可以解決查詢效率問題,但是它會引起另外一個問題:當Term(詞條)很多時,記憶體必定無法容納這些詞條,那ES是如何處理這個問題的呢?ES的做法是建立Term Index。

上面的示例中Term都是英文字元,但是實際的情況是,term可以是任意的byte數組;另外,很多時候Term數量對應的内容未必均衡,如上所示:C字元開頭的term一個都沒有,而A開頭的term又特别多。Lucene 内部的Term Index是用的變種的trie樹(字首樹/字典樹/單詞查找樹),即FST(finite-state transducer),trie樹隻共享了字首,而 FST 既共享字首也共享字尾,更加的節省空間。

關于FST的原理比較複雜,這裡主要了解它在這裡起的核心作用:高效的查找到詞條(Term)。

通過Term Index可以快速地定位到Term Dictionary 的某個 offset,然後從這個位置再往後順序查找。再加上一些壓縮技術(搜尋 Lucene Finite State Transducers)Term Index的尺寸可以隻有所有Term 的尺寸的幾十分之一,使得用記憶體緩存整個Term Index變成可能。整體上來說就是這樣的效果:

ElasticSearch原理篇

如上圖所示,Term Index主要解決的是快速定位到Term,和我們用的字典或者通訊錄比較類似,例如:我們可以直接通過字母W快速找到所有以W開始的漢子或者姓氏,然後再找到對應的漢字或名字。

從上面的分析我們看到,因為ES的Term Index的原因,ES在檢索上面,有的時候表現的比Mysql更快,Mysql隻有Term Dictionary,而且這一層是以B+樹的方式存儲在磁盤上的。檢索一個Term 需要若幹次的 random access 的磁盤操作。而 Lucene 在Term Dictionary 的基礎上添加了Term Index來加速檢索,Term Index以樹的形式緩存在記憶體中,從Term Index查到對應的Term dictionary 的 block 位置之後,再去磁盤上找Term,大大減少了磁盤的 random access 次數。

三、叢集

1.索引

Elasticsearch的index和Mysql的資料庫比較類似,使用者的所有操作都是基于index完成的,每個index由若幹個shard組成,如下圖所示:

ElasticSearch原理篇

shard是Elasticsearch資料存儲的最小機關,index的存儲容量為所有shard的存儲容量之和。Elasticsearch叢集的存儲容量則為所有index存儲容量之和。

2.節點(Node)

ES本身就具備叢集能力,隻需讓每個節點設定相同的cluster.name即可加入同一個叢集;它們共同承擔整個叢集的資料和負載壓力。ES中一個節點即一個運作中的 Elasticsearch 執行個體。節點通過 node.name 來設定節點名稱,如果不設定則在啟動時給節點配置設定一個随機通用唯一辨別符作為名稱。ES叢集中的節點有三種不同的類型:主節點、資料節點、協調節點。

1)主節點

負責管理叢集的所有變更,主要責任例如:建立或删除索引;跟蹤管理那些節點屬于叢集的一部分,例如增加、删除節點等;決定那些分片給相關的節點。

可以通過屬性node.master進行設定。node.master=true表示此節點具有被選舉為主節點的資格。

2)資料節點

資料節點負責資料的存儲和相關的操作,例如對資料進行增、删、改、查和聚合等操作,資料節點對機器配置要求比較高,對 CPU、記憶體和 I/O 的消耗很大。預設每一個節點都是資料節點(包括主節點),可以通過node.data屬性進行設定。

node.data=true表示此節點為資料節點,用于存儲資料。

3)協調節點(或client節點)

如果node.master和node.data屬性均為false,則此節點稱為協調節點。協調節點隻能處理路由請求,處理搜尋、分發索引操作等。此節點的意義是在海量請求的時候可以進行負載均衡。

注意,每一個節點都具有node.master、node.data屬性,預設情況下node.master=true且node.data=true,即預設一個節點同時是資料節點和主節點。

4)最佳實踐

master節點:普通伺服器即可(CPU、記憶體消耗一般)

data 節點:主要消耗磁盤,記憶體

client | ingest 節點:普通伺服器即可(如果要進行分組聚合操作的話,建議這個節點記憶體也配置設定多一點)

3.分片(Shard)

分片是一種讓叢集支援水準擴充能力的方案,即ES是通過分片來實作分布式。每一個分片都是資料的容器,文檔儲存在分片内,分片又被配置設定到叢集内的各個節點裡。叢集中我們的文檔被存儲和索引到分片内,不過對于用戶端來說分片是透明的,他們感覺不到分片的存在。當叢集規模擴大或者縮小時, ES會自動的在各節點中遷移分片,使得資料仍然均勻分布在叢集裡。

ES中有兩種類型的分片:主分片(primary shard)、副本分片(replicas)。索引内任意一個文檔都歸屬于一個主分片,是以主分片的數目直接決定着叢集中能夠儲存的最大資料量。一個副本分片隻是一個主分片的拷貝,副本分片主要是用來實作高可用、高并發的,例如:硬體故障時保護資料不丢失,提供讀服務等。

1)叢集模型

下圖描述的是一個具有是3個節點(Node1、Node2、Node3)的ES叢集,其中Node1為主節點,Node1、Node2、Node3都是資料節點。此叢集有9個分片,其中3個主分片(P0、P1、P2)和6個從分片(R0、R1、R2),每一個主分片都有兩個從分片。

ElasticSearch原理篇

2)路由

所有資料都要先寫到主分片上,隻有主分片處理成功後,才能拷貝至其對應的副本分片上。預設情況下主分片等待所有備份完成索引更新後才傳回用戶端。

當一個叢集中存在多個主分片時,ES是根據什麼樣的規定來将資料寫到某一個分片上的呢?ES的資料路由政策由以下公式決定:

shard = hash(routing) % number_of_primary_shards           

Routing是一個可變值,預設是文檔的 _id ,也可以設定成一個自定義的值。稍有Java開發經驗的人應該對這種處理方式都很熟悉,例如:HashMap、Redis、分庫分表等等場景都會使用類似的政策。

注意:通常而言,我們需要在建立索引的時候就确定好主分片的數量,并且不會改變這個數量,因為如果number_of_primary_shards變化了,那麼上面公式計算出來的值就會發生變化,在遷移資料完成前,資料将會完全錯亂。其實我們日常使用的中間件都有這個問題,例如Redis。如果真的需要進行調整叢集容量(增加或者減少節點數目),那麼必須遷移資料。Redis的常用叢集采用的是一緻性hash政策,讓這種情況發生時資料遷移量盡可能小。

ES通過shard = hash(routing) % number_of_primary_shards公式決定路由政策,可見任何一個節點都可以計算出叢集中的文檔的存放位置,是以每個節點都有處理讀寫請求的能力。

3)寫操作

在一個寫請求被發送到某個節點後,節點會根據路由公式計算出需要寫到哪個分片上,再将請求轉發到該分片的主分片節點上,主分片處理成功以後會将請求轉發給副本分片。圖解如下所示:

ElasticSearch原理篇

具體過程如下:

  • 假設Node1收到一個建立索引的請求。
  • Node1根據shard=hash(routing)% number_of_primary_shards計算資料落到哪一個分片上。假設計算出來的shard=1。
  • 因為shard=1的主分片P1在Node3上,是以Node1将寫請求轉發到Node3節點。
  • Node3如果處理成功,因為P1的兩個副本R1在Node1、Node2上,是以Node3會将寫請求轉發到Node1、Node2上。
  • 當所有的副本R1報告處理成功後,Node3向請求的Node1傳回成功資訊,Node1最後傳回用戶端索引建立成功。

4)讀操作

在一個讀請求被發送到某個節點後,節點會根據路由公式計算出資料存儲在哪個分片上,節點會以負載均衡的方式選擇一個節點,然後将讀請求轉發到該分片節點上。過程如下所示:

ElasticSearch原理篇
  • 假設Node1節點收到一個讀請求。
  • Node1節點通過shard=hash(routing)% number_of_primary_shards計算資料落到哪一個分片上。假設計算出來的shard=1。
  • Shard=1的分片有三個,其中主分片P1在Node1上,副分片R1在Node2、Node3上,此時Node1根據負載均衡的方式來選擇一個副本。圖中選擇的是Node2節點的R1副本。Node1将請求轉發給Node2處理此次讀操作。
  • Node2處理完成以後,将結果傳回給Node1節點,Node1節點将資料傳回給用戶端。

5)更新操作

ES的更新是結合删除和新增來實作的,具體見後文。

4.叢集健康

在索引建立的時候就已經确定了主分片數,但是副本分片數可以随時修改。預設情況下,一個索引會有5個主分片,而其副本可以有任意數量。

主分片和副本分片的狀态決定了叢集的健康狀态。

每一個節點上都隻會儲存主分片或者其對應的一個副本分片,相同的副本分片不會存在于同一個節點中。如果叢集中隻有一個節點,則副本分片将不會被配置設定,此時叢集健康狀态為yellow,存在丢失資料的風險。

四、存儲

1.存儲位置

ES的索引都會落到磁盤上,這樣在斷電或重新開機的時候也不會丢失資料。ES資料存儲路徑在elasticsearch.yml 中配置。

path.data: /path/to/data  //索引資料

path.logs: /path/to/logs  //日志記錄

預設存儲在安裝目錄的 Data 檔案夾下。建議不要使用預設值,因為若 ES 進行了更新,則有可能導緻資料全部丢失。

2.分段存儲

1)Segment

索引文檔以段(Segment)的形式存儲在磁盤上,每一個段上都存儲的是反向索引資訊。Lucene在底層采用了分段的存儲模式,讀寫時幾乎完全避免了鎖的出現,大大提升了讀寫性能。

寫入lucene的資料并不直接落盤,而是先寫在記憶體中,經過了refresh間隔,lucene才将該時間段寫入的全部資料refresh成一個segment,segment多了之後會進行合并操作,合并成更大的segment。lucene查詢時會周遊每個segment完成。

2)段的不變性

段具有不變性,一旦索引的資料被寫入硬碟,就不可再修改。段被寫入到磁盤後會生成一個送出點,一個段一旦擁有了送出點,就說明這個段隻有讀的權限,失去了寫的權限。相反,當資料在記憶體中時,就隻有寫的權限,而不具備讀資料的權限,意味着不能被檢索。

按照上面的規則,新增文檔很好處理,指定時間後會refresh到磁盤中,以一個新段來存儲,那更新、删除文檔需要如何處理呢?

由于Segment不可修改,是以删除時不會把文檔從舊的段中移除,而是通過新增一個.del檔案,檔案中會列出這些被删除文檔的段資訊。被标記删除的文檔仍然可以被查詢比對到,但它會在最終結果被傳回前從結果集中移除。(這一點和Redis資料的被動過期政策很類似。)

由于Segment不可修改,更新被看成是删除+新增兩個操作。即會将舊的文檔在.del檔案中标記删除,然後文檔的新版本被儲存到一個新的Segment中。

3)段不可改變性的優缺點

  • 優點:

    不需要鎖,設計簡單,讀寫性能高。

因為段檔案不會變化,一旦索引檔案進入檔案系統緩存,隻要檔案系統緩存有足夠空間,基本上都會命中,性能高。相比于會變化的資料檔案,當資料變化後需要保持索引内容和資料一緻性,不變性的段不用考慮此問題。

  • 缺點:

    .del是對文檔進行标記,如果這種資料很多,會造成大量空間浪費。

每隔一段時間(由refresh_interval決定)會生成一個新Segment,Segment過多的時候,對大量消耗伺服器資源消耗。

修改、删除的文檔依然會出現在查詢的結果集中,雖然不會傳回給用戶端,但是這會增加查詢負擔。

新的資料重新整理到Segment中之前不能被檢索到,有一定的實效性問題。

3.延遲寫政策

為了提升寫的性能,ES采用延遲寫的政策,即每當有新增的資料時,就将其先寫入到記憶體(ES的JVM記憶體)中,當達到指定時間(由refresh_interval指定,預設是1 秒鐘)或達到一定數量時,會觸發一次重新整理(Refresh),将記憶體中的資料重新整理到一個新的Segment上并緩存到檔案緩存中,稍後再被重新整理到磁盤中并生成送出點。另外,需要注意的是資料未被重新整理到Segment中之前,不是以段的形式存儲的, 是以也就不能被檢索到。這也是ES是準實時搜尋的原因。

我們也可以手動觸發 Refresh, /_refresh 重新整理所有索引,/「index-A」/_refresh 重新整理指定的索引。注意,盡管重新整理是比送出輕量很多的操作,它還是會有性能開銷。手動重新整理适合測試的時候,在生産環境中不要每次更新一個文檔都去手動重新整理;另外,每秒重新整理并不是适合所有的場景,還是要根據具體的情況考慮。

設定refresh_interval 的時候注意要帶上機關,例如refresh_interval = "10s",否則機關是毫秒;當 refresh_interval=-1 時表示關閉索引的自動重新整理。通過延時寫的政策可以減少資料往磁盤上寫的次數,進而提升了整體的寫入能力,但是檔案緩存屬于作業系統的記憶體,隻要是記憶體都存在斷電或異常情況下丢失資料的危險。為了避免丢失資料,Elasticsearch 添加了事務日志(Translog),事務日志記錄了所有還沒有持久化到磁盤的資料。

4.索引持久化過程

通過上面的部分,我們大概了解了ES的資料持久化過程,接下來我們通過圖文的方式詳細介紹一下持久化的過程。

1)寫入資料

當有資料寫入ES時,為了提升寫入的速度,并沒有資料直接寫在磁盤上,而是先寫入到記憶體中,但是為了防止資料的丢失,會追加一份資料到事務日志裡。如下所示:

2)Refresh

當達到預設的時間(1 秒鐘)或者記憶體的資料達到一定量時,會觸發一次重新整理(Refresh)。重新整理的主要步驟如下:

将記憶體中的資料重新整理到一個新的段中(S3)。注意S3目前處于作業系統的檔案的緩存系統中,而不是磁盤上。

打開段(S3),使其可被搜尋。

清空記憶體,準備接收新的資料,事務日志不清空。

注意:此時S3雖然沒有持久化到磁盤中,但S3中的資料是可以被檢索到的.

3)Flush

當日志資料的大小超過 512MB 或者時間超過 30 分鐘時,需要觸發一次Flush,此Flush會進行如下操作:

将檔案系統緩存中的資料S3重新整理到硬碟中。

生成送出點。

删除舊的事務日志(Translog),建立一個空的事務日志(Translog)。

5.段合并政策

1)政策

在「分段存儲」部分我們知道随着時間的累積,會導緻索引中存在大量的Segment。而ES檢索的大概過程是:查詢所有Segment中滿足查詢條件的資料,然後對每個Segment裡查詢的結果集進行合并。這裡就有一個問題,每一個段都會消耗檔案句柄、記憶體和cpu運作周期,更重要的是,每個搜尋請求都必須輪流檢查每個段;是以段越多,搜尋也就越慢,是以為了控制索引裡段的數量,必須定期進行Segment合并操作。

簡單來說Elasticsearch通過以下方式解決這個問題:Elasticsearch通過在背景進行段合并來解決這個問題。小的段被合并到大的段,然後這些大的段再被合并到更大的段。段合并的時候會将那些舊的已删除文檔從檔案系統中清除。被删除的文檔(或被更新文檔的舊版本)不會被拷貝到新的大段中。啟動段合并不需要你做任何事,進行索引和搜尋時會自動進行。如果每次合并全部的Segment,則會造成很大的資源浪費,特别是“大Segment”的合并。

Lucene的合并思路是:根據Segment的大小将段進行分組,再将屬于同一組的Segment進行合并。另外,由于對于超級大的Segment的合并需要消耗更多的資源,是以 Lucene 會在Segment的大小達到一定規模,或者Segment裡面的資料量達到一定條數時,不會再進行合并。

 由此可以看出Lucene 的段合并主要集中在對中小Segment的合并上,這樣既可以避免對大段進行合并時消耗過多的伺服器資源,也可以很好地控制索引中段的數量。

2)段合并參數

mergeFactor:每次合并時參與合并的最少數量,當同一組的段的數量達到此值時開始合并,如果小于此值則不合并,這樣做可以減少段合并的頻率,其預設值為 10。

SegmentSize:指段的實際大小,機關為位元組。

minMergeSize:小于這個值的段會被分到一組,這樣可以加速小片段的合并。

maxMergeSize:若有一段的文本數量大于此值,就不再參與合并,因為大段合并會消耗更多的資源。

3)段合并

ES對段的生成和合并采用下面這個過程進行:

當索引的時候,重新整理(refresh)操作會建立新的段并将段打開以供搜尋使用。

合并程序選擇一小部分大小相似的段,并且在背景将它們合并到更大的段中。此時不會對檢索産生任何影響。

此圖來自ElasticSearch官網中Elasticsearch: 權威指南 » 基礎入門 » 分片内部原理 » 段合并

新的段被重新整理(flush)到了磁盤,并打開用來搜尋,老的段被删除,合并結束。

6.Refresh政策

ES的index、Update、Delete、Bulk等都支援通過api refresh屬性來控制文檔是否可以被檢索到。

1)refresh=false

refresh的預設值,更新資料之後不立刻重新整理,由系統自行排程refresh的控制邏輯。

2)refresh=true或者refresh= (空)

實時重新整理,即資料更新以後,立即将此資料重新整理到相關的主分片和副本分片(注意不是重新整理整個索引),待執行成功後,資料将會被檢索到。頻繁執行重新整理這樣的操作,會導出生成很多很多小的段檔案,這也會給後期段檔案的合并增加處理時間。

實時重新整理主要的不足:

  • 生成過多的、小的段檔案;
  • 搜尋會由于過多的小的段檔案存在,影響搜尋效率;
  • 後期段檔案的合并,也會增加處理時間;

3)reresh=wati_for

不會立刻重新整理,而是等待一段時間才重新整理。使用refresh=wait_for的注意事項:

refresh=wait_for會等到重新整理發生後才會響應,過多的使用會導緻索引的操作效率被降低,直接的展現就是TPS上不去;

不要同時發起過多的refresh=wait_for請求,可将其通過将多個請求合并為單個bulk請求,Elasticsearch會并行處理這些請求,直到所有的請求都執行成功且等到被重新整理後,再統一傳回;

refresh=wait_for的監聽隊列是有長度限制,其受限于系統中參數index.max_refresh_listeners的配置值的影響,預設是1000,如果由于過多的refresh=wait_for請求導緻該除列滿了,則新的wait_for請求不能夠放在該監聽隊列中,就隻能夠立即傳回。由于Elasticsearch對wait_for的設計理念是隻要傳回了就表示已經重新整理了,因而此時就會在傳回前立即觸發重新整理操作,在響應的字段中會增加“"forced_refresh": true”這樣的内容。

具體見ES7.8的官方文檔:

  • The more changes being made to the index the more work wait_for saves compared to true. In the case that the index is only changed once every index.refresh_interval then it saves no work.
  • true creates less efficient indexes constructs (tiny segments) that must later be merged into more efficient index constructs (larger segments). Meaning that the cost of true is paid at index time to create the tiny segment, at search time to search the tiny segment, and at merge time to make the larger segments.
  • Never start multiple refresh=wait_for requests in a row. Instead batch them into a single bulk request with refresh=wait_for and Elasticsearch will start them all in parallel and return only when they have all finished.
  • If the refresh interval is set to -1, disabling the automatic refreshes, then requests with refresh=wait_for will wait indefinitely until some action causes a refresh. Conversely, setting index.refresh_interval to something shorter than the default like 200ms will make refresh=wait_for come back faster, but it’ll still generate inefficient segments.
  • refresh=wait_for only affects the request that it is on, but, by forcing a refresh immediately, refresh=true will affect other ongoing request. In general, if you have a running system you don’t wish to disturb then refresh=wait_for is a smaller modification.

7.Segment結構

lucene包的檔案是由很多segment檔案組成的,segments xxx檔案記錄了lucene包下面的segment檔案數量。每個segment會包含如下的檔案。

Extension Brief Description
Segment Info .si segment的中繼資料檔案
Compound File .cfs, .cfe 一個segment包含了如下表的各個檔案,為減少打開檔案的數量,在segment小的時候,segment的所有檔案内容都儲存在cfs檔案中,cfe檔案儲存了lucene各檔案在cfs檔案的位置資訊
Fields .fnm 儲存了fields的相關資訊
Field Index .fdx 正排存儲檔案的中繼資料資訊
Field Data .fdt 存儲了正排存儲資料,寫入的原文存儲在這
Term Dictionary .tim 反向索引的中繼資料資訊
Term Index .tip 反向索引檔案,存儲了所有的反向索引資料
Frequencies .doc 儲存了每個term的doc id清單和term在doc中的詞頻
Positions .pos 全文索引的字段,會有該檔案,儲存了term在doc中的位置
Payloads .pay 全文索引的字段,使用了一些像payloads的進階特性會有該檔案,儲存了term在doc中的一些進階特性
Norms .nvd, .nvm 檔案儲存索引字段權重資料
Per-Document Values .dvd, .dvm lucene的docvalues檔案,即資料的列式存儲,用作聚合和排序
Term Vector Data .tvx, .tvd, .tvf 儲存索引字段的矢量資訊,用在對term進行高亮,計算文本相關性中使用
Live Documents .liv 記錄了segment中删除的doc

五、參考文章

看完這篇還不會 Elasticsearch,我跪搓衣闆!

https://mp.weixin.qq.com/s/y8DNnj4fjiS3Gqz2DFik8w

時間序列資料庫的秘密 (2)——索引

https://www.infoq.cn/article/database-timestamp-02

ES索引存儲原理

https://blog.csdn.net/guoyuguang0/article/details/76769184

ElasticSearch入門 第八篇:存儲

https://www.cnblogs.com/ljhdo/p/5016852.html

docs-refresh

https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-refresh.html

Elasticsearch詳解

https://www.jianshu.com/p/28fb017be7a7

Elasticsearch: 權威指南

https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html