1. 程式人生 > 其它 >Elasticsearch(二)進階

Elasticsearch(二)進階

第五章、Elasticsearch進階

5.1 核心概念

索引Index

一個索引就是一個擁有幾分相似特徵的文件的集合。比如說,你可以有一個客戶資料的索引,另一個產品目錄的索引,還有一個訂單資料的索引。一個索引由一個名字來標識(必須全部是小寫字母),並且當我們要對這個索引中的文件進行索引、搜尋、更新和刪除(CRUD)的時候,都要使用到這個名字。在一個叢集中,可以定義任意多的索引。

能搜尋的資料必須索引,這樣的好處是可以提高查詢速度,比如:新華字典前面的目錄就是索引的意思,目錄可以提高查詢速度。

Elasticsearch 索引的精髓:一切設計都是為了提高搜尋的效能。

型別Type

在一個索引中,你可以定義一種或多種型別。

一個型別是你的索引的一個邏輯上的分類/分割槽,其語義完全由你來定。通常,會為具有一組共同欄位的文件定義一個型別。不同的版本,型別發生了不同的變化。

版本 Type
5.x 支援多種 type
6.x 只能有一種 type
7.x 預設不再支援自定義索引型別(預設型別為: _doc)

文件Document

一個文件是一個可被索引的基礎資訊單元,也就是一條資料。

比如:你可以擁有某一個客戶的文件,某一個產品的一個文件,當然,也可以擁有某個訂單的一個文件。文件以 JSON(Javascript Object Notation)格式來表示,而 JSON 是一個到處存在的網際網路資料互動格式。

在一個 index/type 裡面,你可以儲存任意多的文件。

欄位Field

相當於是資料表的欄位,對文件資料根據不同屬性進行的分類標識。

對映Mapping

mapping 是處理資料的方式和規則方面做一些限制,如:某個欄位的資料型別、預設值、分析器、是否被索引等等。這些都是對映裡面可以設定的,其它就是處理 ES 裡面資料的一些使用規則設定也叫做對映,按著最優規則處理資料對效能提高很大,因此才需要建立對映,並且需要思考如何建立對映才能對效能更好。

分片Shards

一個索引可以儲存超出單個節點硬體限制的大量資料。比如,一個具有 10 億文件資料的索引佔據 1TB 的磁碟空間,而任一節點都可能沒有這樣大的磁碟空間。 或者單個節點處理搜尋請求,響應太慢。為了解決這個問題,Elasticsearch 提供了將索引劃分成多份的能力,每一份就稱之為分片。

當你建立一個索引的時候,你可以指定你想要的分片的數量。每個分片本身也是一個功能完善並且獨立的“索引”,這個“索引”可以被放置到叢集中的任何節點上。

分片很重要,主要有兩方面的原因:

  • 允許你水平分割 / 擴充套件你的內容容量。
  • 允許你在分片之上進行分散式的、並行的操作,進而提高效能/吞吐量。

至於一個分片怎樣分佈,它的文件怎樣聚合和搜尋請求,是完全由 Elasticsearch 管理的,對於作為使用者的你來說,這些都是透明的,無需過分關心。

被混淆的概念是,一個 Lucene 索引 我們在 Elasticsearch 稱作 分片 。 一個Elasticsearch 索引 是分片的集合。 當 Elasticsearch 在索引中搜索的時候, 他傳送查詢到每一個屬於索引的分片(Lucene 索引),然後合併每個分片的結果到一個全域性的結果集

Lucene 是 Apache 軟體基金會 Jakarta 專案組的一個子專案,提供了一個簡單卻強大的應用程式介面,能夠做全文索引和搜尋。在 Java 開發環境裡 Lucene 是一個成熟的免費開源工具。就其本身而言, Lucene 是當前以及最近幾年最受歡迎的免費 Java 資訊檢索程式庫。但 Lucene 只是一個提供全文搜尋功能類庫的核心工具包,而真正使用它還需要一個完善的服務框架搭建起來進行應用。

目前市面上流行的搜尋引擎軟體,主流的就兩款: Elasticsearch 和 Solr,這兩款都是基於 Lucene 搭建的,可以獨立部署啟動的搜尋引擎服務軟體。由於核心相同,所以兩者除了伺服器安裝、部署、管理、叢集以外,對於資料的操作 修改、新增、儲存、查詢等等都十分類似。

副本Replicas

在一個網路 / 雲的環境裡,失敗隨時都可能發生,在某個分片/節點不知怎麼的就處於離線狀態,或者由於任何原因消失了,這種情況下,有一個故障轉移機制是非常有用並且是強烈推薦的。為此目的, Elasticsearch 允許你建立分片的一份或多份拷貝,這些拷貝叫做複製分片(副本)。

複製分片之所以重要,有兩個主要原因:

  • 在分片/節點失敗的情況下,提供了高可用性。因為這個原因,注意到複製分片從不與原/主要(original/primary)分片置於同一節點上是非常重要的。
  • 擴充套件你的搜尋量/吞吐量,因為搜尋可以在所有的副本上並行執行。

總之,每個索引可以被分成多個分片。一個索引也可以被複制 0 次(意思是沒有複製)或多次。一旦複製了,每個索引就有了主分片(作為複製源的原來的分片)和複製分片(主分片的拷貝)之別。

分片和複製的數量可以在索引建立的時候指定。在索引建立之後,你可以在任何時候動態地改變複製的數量,但是你事後不能改變分片的數量。

預設情況下,Elasticsearch 中的每個索引被分片 1 個主分片和 1 個複製,這意味著,如果你的叢集中至少有兩個節點,你的索引將會有 1 個主分片和另外 1 個複製分片(1 個完全拷貝),這樣的話每個索引總共就有 2 個分片, 我們需要根據索引需要確定分片個數。

分配Allocation

將分片分配給某個節點的過程,包括分配主分片或者副本。如果是副本,還包含從主分片複製資料的過程。這個過程是由 master 節點完成的。

5.2 系統架構

一個執行中的 Elasticsearch 例項稱為一個節點,而叢集是由一個或者多個擁有相同cluster.name 配置的節點組成, 它們共同承擔資料和負載的壓力。當有節點加入叢集中或者從叢集中移除節點時,叢集將會重新平均分佈所有的資料。

當一個節點被選舉成為主節點時, 它將負責管理叢集範圍內的所有變更,例如增加、刪除索引,或者增加、刪除節點等。 而主節點並不需要涉及到文件級別的變更和搜尋等操作,所以當叢集只擁有一個主節點的情況下,即使流量的增加它也不會成為瓶頸。 任何節點都可以成為主節點。我們的示例叢集就只有一個節點,所以它同時也成為了主節點。

作為使用者,我們可以將請求傳送到叢集中的任何節點 ,包括主節點。 每個節點都知道任意文件所處的位置,並且能夠將我們的請求直接轉發到儲存我們所需文件的節點。 無論我們將請求傳送到哪個節點,它都能負責從各個包含我們所需文件的節點收集回資料,並將最終結果返回給客戶端。

Elasticsearch 對這一切的管理都是透明的。

5.3 分散式叢集

單節點叢集

我們在包含一個空節點的叢集內建立名為 users 的索引,為了演示目的,我們將分配 3個主分片和一份副本(每個主分片擁有一個副本分片)。

叢集現在是擁有一個索引的單節點叢集。所有 3 個主分片都被分配在 node-1 。

通過 elasticsearch-head 外掛(一個Chrome外掛)檢視叢集情況 。

  • 叢集健康值:yellow( 3 of 6 ):表示當前叢集的全部主分片都正常執行,但是副本分片沒有全部處在正常狀態。
  • :3 個主分片正常。
  • :3 個副本分片都是 Unassigned,它們都沒有被分配到任何節點。 在同 一個節點上既儲存原始資料又儲存副本是沒有意義的,因為一旦失去了那個節點,我們也將丟失該節點 上的所有副本資料。

當前叢集是正常執行的,但存在丟失資料的風險。


elasticsearch-head chrome外掛安裝

外掛獲取網址,下載壓縮包,解壓後將內容放入自定義命名為elasticsearch-head資料夾。

接著點選Chrome右上角選項->工具->管理擴充套件(或則位址列輸入chrome://extensions/),選擇開啟“開發者模式”,讓後點擊“載入已解壓得擴充套件程式”,選擇elasticsearch-head/_site,即可完成chrome外掛安裝。

故障轉移

當叢集中只有一個節點在執行時,意味著會有一個單點故障問題——沒有冗餘。 幸運的是,我們只需再啟動一個節點即可防止資料丟失。當你在同一臺機器上啟動了第二個節點時,只要它和第一個節點有同樣的 cluster.name 配置,它就會自動發現叢集並加入到其中。但是在不同機器上啟動節點的時候,為了加入到同一叢集,你需要配置一個可連線到的單播主機列表。之所以配置為使用單播發現,以防止節點無意中加入叢集。只有在同一臺機器上執行的節點才會自動組成叢集。

如果啟動了第二個節點,叢集將會擁有兩個節點 : 所有主分片和副本分片都已被分配 。

通過 elasticsearch-head 外掛檢視叢集情況

  • 叢集健康值:green( 3 of 6 ):表示所有 6 個分片(包括 3 個主分片和 3 個副本分片)都在正常執行。
  • :3 個主分片正常。
  • :第二個節點加入到集群后, 3 個副本分片將會分配到這個節點上——每 個主分片對應一個副本分片。這意味著當叢集內任何一個節點出現問題時,我們的資料都完好無損。所 有新近被索引的文件都將會儲存在主分片上,然後被並行的複製到對應的副本分片上。這就保證了我們既可以從主分片又可以從副本分片上獲得文件。

水平擴容

怎樣為我們的正在增長中的應用程式按需擴容呢?當啟動了第三個節點,我們的叢集將會擁有三個節點的叢集 : 為了分散負載而對分片進行重新分配 。

通過 elasticsearch-head 外掛檢視叢集情況。

  • 叢集健康值:green( 3 of 6 ):表示所有 6 個分片(包括 3 個主分片和 3 個副本分片)都在正常執行。

  • Node 1 和 Node 2 上各有一個分片被遷移到了新的 Node 3 節點,現在每個節點上都擁有 2 個分片, 而不是之前的 3 個。 這表示每個節點的硬體資源(CPU, RAM, I/O)將被更少的分片所共享,每個分片 的效能將會得到提升。

分片是一個功能完整的搜尋引擎,它擁有使用一個節點上的所有資源的能力。 我們這個擁有 6 個分片(3 個主分片和 3 個副本分片)的索引可以最大擴容到 6 個節點,每個節點上存在一個分片,並且每個 分片擁有所在節點的全部資源。

但是如果我們想要擴容超過 6 個節點怎麼辦呢?

主分片的數目在索引建立時就已經確定了下來。實際上,這個數目定義了這個索引能夠儲存的最大資料量。(實際大小取決於你的資料、硬體和使用場景。)

但是,讀操作——搜尋和返回資料——可以同時被主分片或副本分片所處理,所以當你擁有越多的副本分片時,也將擁有越高的吞吐量。

在執行中的叢集上是可以動態調整副本分片數目的,我們可以按需伸縮叢集。讓我們把副本數從預設的 1 增加到 2。

users 索引現在擁有 9 個分片: 3 個主分片和 6 個副本分片。 這意味著我們可以將叢集擴容到 9 個節點,每個節點上一個分片。相比原來 3 個節點時,叢集搜尋效能可以提升 3 倍。

通過 elasticsearch-head 外掛檢視叢集情況:

當然,如果只是在相同節點數目的叢集上增加更多的副本分片並不能提高效能,因為每個分片從節點上獲得的資源會變少。 你需要增加更多的硬體資源來提升吞吐量。

但是更多的副本分片數提高了資料冗餘量:按照上面的節點配置,我們可以在失去 2 個節點的情況下不丟失任何資料。

應對故障

我們關閉第一個節點,這時叢集的狀態為:關閉了一個節點後的叢集。

我們關閉的節點是一個主節點。而叢集必須擁有一個主節點來保證正常工作,所以發生的第一件事情就是選舉一個新的主節點: Node 2 。

在我們關閉 Node 1 的同時也失去了主分片 1 和 2 ,並且在缺失主分片的時候索引也不能正常工作。

如果此時來檢查叢集的狀況,我們看到的狀態將會為 red :不是所有主分片都在正常工作。

幸運的是,在其它節點上存在著這兩個主分片的完整副本, 所以新的主節點立即將這些分片在 Node 2 和 Node 3 上對應的副本分片提升為主分片, 此時叢集的狀態將會為yellow。這個提升主分片的過程是瞬間發生的,如同按下一個開關一般。

為什麼我們叢集狀態是 yellow 而不是 green 呢?

雖然我們擁有所有的三個主分片,但是同時設定了每個主分片需要對應 2 份副本分片,而此時只存在一份副本分片。 所以叢集不能為 green 的狀態,不過我們不必過於擔心:如果我們同樣關閉了 Node 2 ,我們的程式依然可以保持在不丟任何資料的情況下執行,因為Node 3 為每一個分片都保留著一份副本。

如果想回復原來的樣子,要確保Node-1的配置檔案有如下配置:

discovery.seed_hosts: ["localhost:9302", "localhost:9303"]

叢集可以將缺失的副本分片再次進行分配,那麼叢集的狀態也將恢復成之前的狀態。 如果 Node 1 依然擁有著之前的分片,它將嘗試去重用它們,同時僅從主分片複製發生了修改的資料檔案。和之前的叢集相比,只是 Master 節點切換了。

5.4 路由計算

當索引一個文件的時候,文件會被儲存到一個主分片中。 Elasticsearch 如何知道一個文件應該存放到哪個分片中呢?當我們建立文件時,它如何決定這個文件應當被儲存在分片 1 還是分片 2 中呢?首先這肯定不會是隨機的,否則將來要獲取文件的時候我們就不知道從何處尋找了。實際上,這個過程是根據下面這個公式決定的:

shard = hash(routing) % number_of_primary_shards

routing 是一個可變值,預設是文件的 _id ,也可以設定成一個自定義的值。 routing 通過hash 函式生成一個數字,然後這個數字再除以 number_of_primary_shards (主分片的數量)後得到餘數 。這個分佈在 0 到 number_of_primary_shards-1 之間的餘數,就是我們所尋求的文件所在分片的位置。

這就解釋了為什麼我們要在建立索引的時候就確定好主分片的數量並且永遠不會改變這個數量:因為如果數量變化了,那麼所有之前路由的值都會無效,文件也再也找不到了。

所有的文件API ( get . index . delete 、 bulk , update以及 mget )都接受一個叫做routing 的路由引數,通過這個引數我們可以自定義文件到分片的對映。一個自定義的路由引數可以用來確保所有相關的文件—一例如所有屬於同一個使用者的文件——都被儲存到同一個分片中。

5.5 分片控制

我們可以傳送請求到叢集中的任一節點。每個節點都有能力處理任意請求。每個節點都知道叢集中任一文件位置,所以可以直接將請求轉發到需要的節點上。在下面的例子中,如果將所有的請求傳送到Node 1001,我們將其稱為協調節點coordinating node

當傳送請求的時候, 為了擴充套件負載,更好的做法是輪詢叢集中所有的節點。

資料寫流程

新建、索引和刪除請求都是寫操作, 必須在主分片上面完成之後才能被複制到相關的副本分片。

新建,索引和刪除文件所需要的步驟順序

  1. 客戶端向 Node 1 傳送新建、索引或者刪除請求。

  2. 節點使用文件的 _id 確定文件屬於分片 0 。請求會被轉發到 Node 3,因為分片 0 的主分片目前被分配在 Node 3 上。

  3. Node 3 在主分片上面執行請求。如果成功了,它將請求並行轉發到 Node 1 和 Node 2 的副本分片上。一旦所有的副本分片都報告成功, Node 3 將向協調節點報告成功,協調節點向客戶端報告成功。

在客戶端收到成功響應時,文件變更已經在主分片和所有副本分片執行完成,變更是安全的。有一些可選的請求引數允許您影響這個過程,可能以資料安全為代價提升效能。這些選項很少使用,因為 Elasticsearch 已經很快,但是為了完整起見, 請參考下文:

  1. consistency
  • 即一致性。在預設設定下,即使僅僅是在試圖執行一個寫操作之前,主分片都會要求必須要有規定數量quorum(或者換種說法,也即必須要有大多數)的分片副本處於活躍可用狀態,才會去執行寫操作(其中分片副本 可以是主分片或者副本分片)。這是為了避免在發生網路分割槽故障(network partition)的時候進行寫操作,進而導致資料不一致。 規定數量即: int((primary + number_of_replicas) / 2 ) + 1
  • consistency 引數的值可以設為:
    • one :只要主分片狀態 ok 就允許執行寫操作。
    • all:必須要主分片和所有副本分片的狀態沒問題才允許執行寫操作。
    • quorum:預設值為quorum , 即大多數的分片副本狀態沒問題就允許執行寫操作。
  • 注意,規定數量的計算公式中number_of_replicas指的是在索引設定中的設定副本分片數,而不是指當前處理活動狀態的副本分片數。如果你的索引設定中指定了當前索引擁有3個副本分片,那規定數量的計算結果即:int((1 primary + 3 replicas) / 2) + 1 = 3,如果此時你只啟動兩個節點,那麼處於活躍狀態的分片副本數量就達不到規定數量,也因此您將無法索引和刪除任何文件。
  1. timeout
    • 如果沒有足夠的副本分片會發生什麼?Elasticsearch 會等待,希望更多的分片出現。預設情況下,它最多等待 1 分鐘。 如果你需要,你可以使用timeout引數使它更早終止:100是100 毫秒,30s是30秒。

新索引預設有1個副本分片,這意味著為滿足規定數量應該需要兩個活動的分片副本。 但是,這些預設的設定會阻止我們在單一節點上做任何事情。為了避免這個問題,要求只有當number_of_replicas 大於1的時候,規定數量才會執行。

資料讀流程

我們可以從主分片或者從其它任意副本分片檢索文件

從主分片或者副本分片檢索文件的步驟順序

  1. 客戶端向 Node 1 傳送獲取請求。

  2. 節點使用文件的 _id 來確定文件屬於分片 0 。分片 0 的副本分片存在於所有的三個節點上。 在這種情況下,它將請求轉發到 Node 2 。

  3. Node 2 將文件返回給 Node 1 ,然後將文件返回給客戶端。

在處理讀取請求時,協調結點在每次請求的時候都會通過輪詢所有的副本分片來達到負載均衡。在文件被檢索時,已經被索引的文件可能已經存在於主分片上但是還沒有複製到副本分片。 在這種情況下,副本分片可能會報告文件不存在,但是主分片可能成功返回文件。 一旦索引請求成功返回給使用者,文件在主分片和副本分片都是可用的。

更新流程

部分更新一個文件結合了先前說明的讀取和寫入流程:

部分更新一個文件的步驟如下:

  1. 客戶端向Node 1傳送更新請求。
  2. 它將請求轉發到主分片所在的Node 3 。
  3. Node 3從主分片檢索文件,修改_source欄位中的JSON,並且嘗試重新索引主分片的文件。如果文件已經被另一個程序修改,它會重試步驟3 ,超過retry_on_conflict次後放棄。
  4. 如果 Node 3成功地更新文件,它將新版本的文件並行轉發到Node 1和 Node 2上的副本分片,重新建立索引。一旦所有副本分片都返回成功,Node 3向協調節點也返回成功,協調節點向客戶端返回成功。

當主分片把更改轉發到副本分片時, 它不會轉發更新請求。 相反,它轉發完整文件的新版本。請記住,這些更改將會非同步轉發到副本分片,並且不能保證它們以傳送它們相同的順序到達。 如果 Elasticsearch 僅轉發更改請求,則可能以錯誤的順序應用更改,導致得到損壞的文件。

多文件操作流程

mget和 bulk API的模式類似於單文件模式。區別在於協調節點知道每個文件存在於哪個分片中。它將整個多文件請求分解成每個分片的多文件請求,並且將這些請求並行轉發到每個參與節點。

協調節點一旦收到來自每個節點的應答,就將每個節點的響應收集整理成單個響應,返回給客戶端。

用單個 mget 請求取回多個文件所需的步驟順序:

  1. 客戶端向 Node 1 傳送 mget 請求。
  2. Node 1為每個分片構建多文件獲取請求,然後並行轉發這些請求到託管在每個所需的主分片或者副本分片的節點上。一旦收到所有答覆,Node 1 構建響應並將其返回給客戶端。

可以對docs陣列中每個文件設定routing引數。

bulk API, 允許在單個批量請求中執行多個建立、索引、刪除和更新請求。

bulk API 按如下步驟順序執行:

  1. 客戶端向Node 1 傳送 bulk請求。
  2. Node 1為每個節點建立一個批量請求,並將這些請求並行轉發到每個包含主分片的節點主機。
  3. 主分片一個接一個按順序執行每個操作。當每個操作成功時,主分片並行轉發新文件(或刪除)到副本分片,然後執行下一個操作。一旦所有的副本分片報告所有操作成功,該節點將向協調節點報告成功,協調節點將這些響應收集整理並返回給客戶端。

5.6 分片原理

分片是Elasticsearch最小的工作單元。但是究竟什麼是一個分片,它是如何工作的?

傳統的資料庫每個欄位儲存單個值,但這對全文檢索並不夠。文字欄位中的每個單詞需要被搜尋,對資料庫意味著需要單個欄位有索引多值的能力。最好的支援是一個欄位多個值需求的資料結構是倒排索引

倒排索引

Elasticsearch使用一種稱為倒排索引的結構,它適用於快速的全文搜尋。

見其名,知其意,有倒排索引,肯定會對應有正向索引。正向索引(forward index),反向索引(inverted index)更熟悉的名字是倒排索引

所謂的正向索引,就是搜尋引擎會將待搜尋的檔案都對應一個檔案ID,搜尋時將這個ID和搜尋關鍵字進行對應,形成K-V對,然後對關鍵字進行統計計數。(統計??下文有解釋)

但是網際網路上收錄在搜尋引擎中的文件的數目是個天文數字,這樣的索引結構根本無法滿足實時返回排名結果的要求。所以,搜尋引擎會將正向索引重新構建為倒排索引,即把檔案ID對應到關鍵詞的對映轉換為關鍵詞到檔案ID的對映,每個關鍵詞都對應著一系列的檔案,這些檔案中都出現這個關鍵詞。

一個倒排索引由文件中所有不重複詞的列表構成,對於其中每個詞,有一個包含它的文件列表。例如,假設我們有兩個文件,每個文件的content域包含如下內容:

  • The quick brown fox jumped over the lazy dog
  • Quick brown foxes leap over lazy dogs in summer

為了建立倒排索引,我們首先將每個文件的content域拆分成單獨的詞(我們稱它為詞條或tokens ),建立一個包含所有不重複詞條的排序列表,然後列出每個詞條出現在哪個文件。結果如下所示:

現在,如果我們想搜尋 quick brown ,我們只需要查詢包含每個詞條的文件:

兩個文件都匹配,但是第一個文件比第二個匹配度更高。如果我們使用僅計算匹配詞條數量的簡單相似性演算法,那麼我們可以說,對於我們查詢的相關性來講,第一個文件比第二個文件更佳。

但是,我們目前的倒排索引有一些問題:

  • Quickquick以獨立的詞條出現,然而使用者可能認為它們是相同的詞。
  • foxfoxes非常相似,就像dogdogs;他們有相同的詞根。
  • jumpedleap,儘管沒有相同的詞根,但他們的意思很相近。他們是同義詞。

使用前面的索引搜尋+Quick +fox不會得到任何匹配文件。(記住,+字首表明這個詞必須存在)。

只有同時出現Quickfox 的文件才滿足這個查詢條件,但是第一個文件包含quick fox ,第二個文件包含Quick foxes

我們的使用者可以合理的期望兩個文件與查詢匹配。我們可以做的更好。

如果我們將詞條規範為標準模式,那麼我們可以找到與使用者搜尋的詞條不完全一致,但具有足夠相關性的文件。例如:

  • Quick可以小寫化為quick
  • foxes可以詞幹提取變為詞根的格式為fox。類似的,dogs可以為提取為dog
  • jumpedleap是同義詞,可以索引為相同的單詞jump

現在索引看上去像這樣:

這還遠遠不夠。我們搜尋+Quick +fox 仍然會失敗,因為在我們的索引中,已經沒有Quick了。但是,如果我們對搜尋的字串使用與content域相同的標準化規則,會變成查詢+quick +fox,這樣兩個文件都會匹配!分詞和標準化的過程稱為分析,這非常重要。你只能搜尋在索引中出現的詞條,所以索引文字和查詢字串必須標準化為相同的格式。

文件搜尋

早期的全文檢索會為整個文件集合建立一個很大的倒排索引並將其寫入到磁碟。 一旦新的索引就緒,舊的就會被其替換,這樣最近的變化便可以被檢索到。

倒排索引被寫入磁碟後是不可改變的:它永遠不會修改。

  • 不需要鎖。如果你從來不更新索引,你就不需要擔心多程序同時修改資料的問題。
  • 一旦索引被讀入核心的檔案系統快取,便會留在哪裡,由於其不變性。只要檔案系統快取中還有足夠的空間,那麼大部分讀請求會直接請求記憶體,而不會命中磁碟。這提供了很大的效能提升。
  • 其它快取(像filter快取),在索引的生命週期內始終有效。它們不需要在每次資料改變時被重建,因為資料不會變化。
  • 寫入單個大的倒排索引允許資料被壓縮,減少磁碟IO和需要被快取到記憶體的索引的使用量。

當然,一個不變的索引也有不好的地方。主要事實是它是不可變的! 你不能修改它。如果你需要讓一個新的文件可被搜尋,你需要重建整個索引。這要麼對一個索引所能包含的資料量造成了很大的限制,要麼對索引可被更新的頻率造成了很大的限制。

動態更新索引

如何在保留不變性的前提下實現倒排索引的更新?

答案是:用更多的索引。通過增加新的補充索引來反映新近的修改,而不是直接重寫整個倒排索引。每一個倒排索引都會被輪流查詢到,從最早的開始查詢完後再對結果進行合併。

Elasticsearch基於,這個java庫引入了按段搜尋的概念。每一段本身都是一個倒排索引,但索引在 Lucene 中除表示所有段的集合外,還增加了提交點的概念—一個列出了所有已知段的檔案。

按段搜尋會以如下流程執行:

1、新文件被收集到記憶體索引快取。

2、不時地, 快取被提交。

(1)一個新的段,一個追加的倒排索引,被寫入磁碟。

(2)一個新的包含新段名字的提交點被寫入磁碟。

(3)磁碟進行同步,所有在檔案系統快取中等待的寫入都重新整理到磁碟,以確保它們被寫入物理檔案

3、新的段被開啟,讓它包含的文件可見以被搜尋。

4、記憶體快取被清空,等待接收新的文件。

當一個查詢被觸發,所有已知的段按順序被查詢。詞項統計會對所有段的結果進行聚合,以保證每個詞和每個文件的關聯都被準確計算。這種方式可以用相對較低的成本將新文件新增到索引。

段是不可改變的,所以既不能從把文件從舊的段中移除,也不能修改舊的段來進行反映文件的更新。取而代之的是,每個提交點會包含一個.del 檔案,檔案中會列出這些被刪除文件的段資訊。

當一個文件被“刪除”時,它實際上只是在 .del 檔案中被標記刪除。一個被標記刪除的文件仍然可以被查詢匹配到,但它會在最終結果被返回前從結果集中移除。

文件更新也是類似的操作方式:當一個文件被更新時,舊版本文件被標記刪除,文件的新版本被索引到一個新的段中。可能兩個版本的文件都會被一個查詢匹配到,但被刪除的那個舊版本文件在結果集返回前就已經被移除。

近實時搜尋

隨著按段(per-segment)搜尋的發展,一個新的文件從索引到可被搜尋的延遲顯著降低了。新文件在幾分鐘之內即可被檢索,但這樣還是不夠快。磁碟在這裡成為了瓶頸。提交(Commiting)一個新的段到磁碟需要一個fsync來確保段被物理性地寫入磁碟,這樣在斷電的時候就不會丟失資料。但是fsync操作代價很大;如果每次索引一個文件都去執行一次的話會造成很大的效能問題。

fsync: 同步記憶體中所有已修改的檔案資料到儲存裝置

我們需要的是一個更輕量的方式來使一個文件可被搜尋,這意味著fsync要從整個過程中被移除。在Elasticsearch和磁碟之間是檔案系統快取。像之前描述的一樣,在記憶體索引緩衝區中的文件會被寫入到一個新的段中。但是這裡新段會被先寫入到檔案系統快取—這一步代價會比較低,稍後再被重新整理到磁碟—這一步代價比較高。不過只要檔案已經在快取中,就可以像其它檔案一樣被開啟和讀取了。

Lucene允許新段被寫入和開啟,使其包含的文件在未進行一次完整提交時便對搜尋可見。這種方式比進行一次提交代價要小得多,並且在不影響效能的前提下可以被頻繁地執行。

在 Elasticsearch 中,寫入和開啟一個新段的輕量的過程叫做refresh。預設情況下每個分片會每秒自動重新整理一次。這就是為什麼我們說 Elasticsearch是近實時搜尋:文件的變化並不是立即對搜尋可見,但會在一秒之內變為可見。

這些行為可能會對新使用者造成困惑:他們索引了一個文件然後嘗試搜尋它,但卻沒有搜到。這個問題的解決辦法是用refresh API執行一次手動重新整理:/usersl_refresh

儘管重新整理是比提交輕量很多的操作,它還是會有效能開銷。當寫測試的時候,手動重新整理很有用,但是不要在生產環境下每次索引一個文件都去手動重新整理。相反,你的應用需要意識到Elasticsearch 的近實時的性質,並接受它的不足。

並不是所有的情況都需要每秒重新整理。可能你正在使用Elasticsearch索引大量的日誌檔案,你可能想優化索引速度而不是近實時搜尋,可以通過設定refresh_interval ,降低每個索引的重新整理頻率

{
    "settings": {
    	"refresh_interval": "30s"
    }
}

refresh_interval可以在既存索引上進行動態更新。在生產環境中,當你正在建立一個大的新索引時,可以先關閉自動重新整理,待開始使用該索引時,再把它們調回來。

# 關閉自動重新整理
PUT /users/_settings
{ "refresh_interval": -1 }

# 每一秒重新整理
PUT /users/_settings
{ "refresh_interval": "1s" }

持久化變更

如果沒有用fsync把資料從檔案系統快取刷(flush)到硬碟,我們不能保證資料在斷電甚至是程式正常退出之後依然存在。為了保證Elasticsearch 的可靠性,需要確保資料變化被持久化到磁碟。在動態更新索引,我們說一次完整的提交會將段刷到磁碟,並寫入一個包含所有段列表的提交點。Elasticsearch 在啟動或重新開啟一個索引的過程中使用這個提交點來判斷哪些段隸屬於當前分片。

即使通過每秒重新整理(refresh)實現了近實時搜尋,我們仍然需要經常進行完整提交來確保能從失敗中恢復。但在兩次提交之間發生變化的文件怎麼辦?我們也不希望丟失掉這些資料。Elasticsearch 增加了一個translog ,或者叫事務日誌,在每一次對Elasticsearch進行操作時均進行了日誌記錄。

整個流程如下:

1、一個文件被索引之後,就會被新增到記憶體緩衝區,並且追加到了 translog

2、重新整理(refresh)使分片每秒被重新整理(refresh)一次:

  • 這些在記憶體緩衝區的文件被寫入到一個新的段中,且沒有進行fsync操作。
  • 這個段被開啟,使其可被搜尋。
  • 記憶體緩衝區被清空。

3、這個程序繼續工作,更多的文件被新增到記憶體緩衝區和追加到事務日誌。

4、每隔一段時間—例如translog變得越來越大,索引被重新整理(flush);一個新的translog被建立,並且一個全量提交被執行。

  • 所有在記憶體緩衝區的文件都被寫入一個新的段。
  • 緩衝區被清空。
  • 一個提交點被寫入硬碟。
  • 檔案系統快取通過fsync被重新整理(flush) 。
  • 老的translog被刪除。

translog 提供所有還沒有被刷到磁碟的操作的一個持久化紀錄。當Elasticsearch啟動的時候,它會從磁碟中使用最後一個提交點去恢復己知的段,並且會重放translog 中所有在最後一次提交後發生的變更操作。

translog 也被用來提供實時CRUD。當你試著通過ID查詢、更新、刪除一個文件,它會在嘗試從相應的段中檢索之前,首先檢查 translog任何最近的變更。這意味著它總是能夠實時地獲取到文件的最新版本。

執行一個提交併且截斷translog 的行為在 Elasticsearch被稱作一次flush。分片每30分鐘被自動重新整理(flush),或者在 translog 太大的時候也會重新整理。

你很少需要自己手動執行flush操作,通常情況下,自動重新整理就足夠了。這就是說,在重啟節點或關閉索引之前執行 flush有益於你的索引。當Elasticsearch嘗試恢復或重新開啟一個索引,它需要重放translog中所有的操作,所以如果日誌越短,恢復越快。

translog 的目的是保證操作不會丟失,在檔案被fsync到磁碟前,被寫入的檔案在重啟之後就會丟失。預設translog是每5秒被fsync重新整理到硬碟,或者在每次寫請求完成之後執行(e.g. index, delete, update, bulk)。這個過程在主分片和複製分片都會發生。最終,基本上,這意味著在整個請求被fsync到主分片和複製分片的translog之前,你的客戶端不會得到一個200 OK響應。

在每次請求後都執行一個fsync會帶來一些效能損失,儘管實踐表明這種損失相對較小(特別是 bulk 匯入,它在一次請求中平攤了大量文件的開銷)。

但是對於一些大容量的偶爾丟失幾秒資料問題也並不嚴重的叢集,使用非同步的 fsync還是比較有益的。比如,寫入的資料被快取到記憶體中,再每5秒執行一次 fsync 。如果你決定使用非同步translog 的話,你需要保證在發生 crash 時,丟失掉 sync_interval時間段的資料也無所謂。請在決定前知曉這個特性。如果你不確定這個行為的後果,最好是使用預設的引數{“index.translog.durability”: “request”}來避免資料丟失。

段合併

由於自動重新整理流程每秒會建立一個新的段,這樣會導致短時間內的段數量暴增。而段數目太多會帶來較大的麻煩。每一個段都會消耗檔案控制代碼、記憶體和 cpu執行週期。更重要的是,每個搜尋請求都必須輪流檢查每個段;所以段越多,搜尋也就越慢。

Elasticsearch通過在後臺進行段合併來解決這個問題。小的段被合併到大的段,然後這些大的段再被合併到更大的段。

段合併的時候會將那些舊的已刪除文件從檔案系統中清除。被刪除的文件(或被更新文件的舊版本)不會被拷貝到新的大段中。

啟動段合併不需要你做任何事。進行索引和搜尋時會自動進行。

1、當索引的時候,重新整理(refresh)操作會建立新的段並將段開啟以供搜尋使用。

2、合併程序選擇一小部分大小相似的段,並且在後臺將它們合併到更大的段中。這並不會中斷索引和搜尋。

3、一旦合併結束,老的段被刪除

  • 新的段被重新整理(flush)到了磁碟。
  • 寫入一個包含新段且排除舊的和較小的段的新提交點。
  • 新的段被開啟用來搜尋。老的段被刪除。

合併大的段需要消耗大量的 I/O 和 CPU 資源,如果任其發展會影響搜尋效能。 Elasticsearch在預設情況下會對合並流程進行資源限制,所以搜尋仍然有足夠的資源很好地執行。

5.7 文件分析

分析包含下面的過程:

  • 將一塊文字分成適合於倒排索引的獨立的詞條。
  • 將這些詞條統一化為標準格式以提高它們的“可搜尋性”,或者recall。

分析器執行上面的工作。分析器實際上是將三個功能封裝到了一個包裡:

  • 字元過濾器:首先,字串按順序通過每個 字元過濾器 。他們的任務是在分詞前整理字串。一個字元過濾器可以用來去掉 HTML,或者將 & 轉化成 and。
  • 分詞器:其次,字串被分詞器分為單個的詞條。一個簡單的分詞器遇到空格和標點的時候,可能會將文字拆分成詞條。
  • Token 過濾器:最後,詞條按順序通過每個 token 過濾器 。這個過程可能會改變詞條(例如,小寫化Quick ),刪除詞條(例如, 像 a, and, the 等無用詞),或者增加詞條(例如,像jump和leap這種同義詞)

內建分析器

Elasticsearch還附帶了可以直接使用的預包裝的分析器。接下來我們會列出最重要的分析器。為了證明它們的差異,我們看看每個分析器會從下面的字串得到哪些詞條:

"Set the shape to semi-transparent by calling set_trans(5)"
  • 標準分析器

標準分析器是Elasticsearch 預設使用的分析器。它是分析各種語言文字最常用的選擇。它根據Unicode 聯盟定義的單詞邊界劃分文字。刪除絕大部分標點。最後,將詞條小寫。它會產生:

set, the, shape, to, semi, transparent, by, calling, set_trans, 5
  • 簡單分析器

簡單分析器在任何不是字母的地方分隔文字,將詞條小寫。它會產生:

set, the, shape, to, semi, transparent, by, calling, set, trans
  • 空格分析器

空格分析器在空格的地方劃分文字。它會產生:

Set, the, shape, to, semi-transparent, by, calling, set_trans(5)
  • 語言分析器

特定語言分析器可用於很多語言。它們可以考慮指定語言的特點。例如,英語分析器附帶了一組英語無用詞(常用單詞,例如and或者the ,它們對相關性沒有多少影響),它們會被刪除。由於理解英語語法的規則,這個分詞器可以提取英語單詞的詞幹。

英語分詞器會產生下面的詞條:

set, shape, semi, transpar, call, set_tran, 5

注意看transparent、calling和 set_trans已經變為詞根格式。

分析器使用場景

當我們索引一個文件,它的全文域被分析成詞條以用來建立倒排索引。但是,當我們在全文域搜尋的時候,我們需要將查詢字串通過相同的分析過程,以保證我們搜尋的詞條格式與索引中的詞條格式一致。

全文查詢,理解每個域是如何定義的,因此它們可以做正確的事:

  • 當你查詢一個全文域時,會對查詢字串應用相同的分析器,以產生正確的搜尋詞條列表。
  • 當你查詢一個精確值域時,不會分析查詢字串,而是搜尋你指定的精確值。

測試分析器

有些時候很難理解分詞的過程和實際被儲存到索引中的詞條,特別是你剛接觸Elasticsearch。為了理解發生了什麼,你可以使用analyze API來看文字是如何被分析的。在訊息體裡,指定分析器和要分析的文字。

#GET http://localhost:9200/_analyze
{
    "analyzer": "standard",
    "text": "Text to analyze"
}

結果中每個元素代表一個單獨的詞條:

{
    "tokens": [
        {
            "token": "text", 
            "start_offset": 0, 
            "end_offset": 4, 
            "type": "<ALPHANUM>", 
            "position": 1
        }, 
        {
            "token": "to", 
            "start_offset": 5, 
            "end_offset": 7, 
            "type": "<ALPHANUM>", 
            "position": 2
        }, 
        {
            "token": "analyze", 
            "start_offset": 8, 
            "end_offset": 15, 
            "type": "<ALPHANUM>", 
            "position": 3
        }
    ]
}

  • token是實際儲存到索引中的詞條。
  • start_ offset 和end_ offset指明字元在原始字串中的位置。
  • position指明詞條在原始文字中出現的位置。

指定分析器

當Elasticsearch在你的文件中檢測到一個新的字串域,它會自動設定其為一個全文字串域,使用 標準 分析器對它進行分析。你不希望總是這樣。可能你想使用一個不同的分析器,適用於你的資料使用的語言。有時候你想要一個字串域就是一個字串域,不使用分析,直接索引你傳入的精確值,例如使用者 ID 或者一個內部的狀態域或標籤。要做到這一點,我們必須手動指定這些域的對映。

(細粒度指定分析器)

IK分詞器

首先通過 Postman 傳送 GET 請求查詢分詞效果

# GET http://localhost:9200/_analyze
{
	"text":"測試單詞"
}

ES 的預設分詞器無法識別中文中測試、 單詞這樣的詞彙,而是簡單的將每個字拆完分為一個詞。

{
    "tokens": [
        {
            "token": "測", 
            "start_offset": 0, 
            "end_offset": 1, 
            "type": "<IDEOGRAPHIC>", 
            "position": 0
        }, 
        {
            "token": "試", 
            "start_offset": 1, 
            "end_offset": 2, 
            "type": "<IDEOGRAPHIC>", 
            "position": 1
        }, 
        {
            "token": "單", 
            "start_offset": 2, 
            "end_offset": 3, 
            "type": "<IDEOGRAPHIC>", 
            "position": 2
        }, 
        {
            "token": "詞", 
            "start_offset": 3, 
            "end_offset": 4, 
            "type": "<IDEOGRAPHIC>", 
            "position": 3
        }
    ]
}

這樣的結果顯然不符合我們的使用要求,所以我們需要下載 ES 對應版本的中文分詞器。

IK 中文分詞器下載網址

將解壓後的後的資料夾放入 ES 根目錄下的 plugins 目錄下,重啟 ES 即可使用。

我們這次加入新的查詢引數"analyzer":“ik_max_word”。

# GET http://localhost:9200/_analyze
{
	"text":"測試單詞",
	"analyzer":"ik_max_word"
}
  • ik_max_word:會將文字做最細粒度的拆分。
  • ik_smart:會將文字做最粗粒度的拆分。

使用中文分詞後的結果為:

{
    "tokens": [
        {
            "token": "測試", 
            "start_offset": 0, 
            "end_offset": 2, 
            "type": "CN_WORD", 
            "position": 0
        }, 
        {
            "token": "單詞", 
            "start_offset": 2, 
            "end_offset": 4, 
            "type": "CN_WORD", 
            "position": 1
        }
    ]
}

ES 中也可以進行擴充套件詞彙,首先查詢

#GET http://localhost:9200/_analyze
{
    "text":"弗雷爾卓德",
    "analyzer":"ik_max_word"
}

僅僅可以得到每個字的分詞結果,我們需要做的就是使分詞器識別到弗雷爾卓德也是一個詞語。

{
    "tokens": [
        {
            "token": "弗",
            "start_offset": 0,
            "end_offset": 1,
            "type": "CN_CHAR",
            "position": 0
        },
        {
            "token": "雷",
            "start_offset": 1,
            "end_offset": 2,
            "type": "CN_CHAR",
            "position": 1
        },
        {
            "token": "爾",
            "start_offset": 2,
            "end_offset": 3,
            "type": "CN_CHAR",
            "position": 2
        },
        {
            "token": "卓",
            "start_offset": 3,
            "end_offset": 4,
            "type": "CN_CHAR",
            "position": 3
        },
        {
            "token": "德",
            "start_offset": 4,
            "end_offset": 5,
            "type": "CN_CHAR",
            "position": 4
        }
    ]
}

僅僅可以得到每個字的分詞結果,我們需要做的就是使分詞器識別到弗雷爾卓德也是一個詞語。

  1. 首先進入 ES 根目錄中的 plugins 資料夾下的 ik 資料夾,進入 config 目錄,建立 custom.dic檔案,寫入“弗雷爾卓德”。
  2. 同時開啟 IKAnalyzer.cfg.xml 檔案,將新建的 custom.dic 配置其中。
  3. 重啟 ES 伺服器 。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
	<comment>IK Analyzer 擴充套件配置</comment>
	<!--使用者可以在這裡配置自己的擴充套件字典 -->
	<entry key="ext_dict">custom.dic</entry>
	 <!--使用者可以在這裡配置自己的擴充套件停止詞字典-->
	<entry key="ext_stopwords"></entry>
	<!--使用者可以在這裡配置遠端擴充套件字典 -->
	<!-- <entry key="remote_ext_dict">words_location</entry> -->
	<!--使用者可以在這裡配置遠端擴充套件停止詞字典-->
	<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>

擴充套件後再次查詢

# GET http://localhost:9200/_analyze
{
	"text":"弗雷爾卓德",
	"analyzer":"ik_max_word"
}

返回結果如下:

{
    "tokens": [
        {
            "token": "弗雷爾卓德",
            "start_offset": 0,
            "end_offset": 5,
            "type": "CN_WORD",
            "position": 0
        }
    ]
}

自定義分析器

雖然Elasticsearch帶有一些現成的分析器,然而在分析器上Elasticsearch真正的強大之處在於,你可以通過在一個適合你的特定資料的設定之中組合字元過濾器、分詞器、詞彙單元過濾器來建立自定義的分析器。在分析與分析器我們說過,一個分析器就是在一個包裡面組合了三種函式的一個包裝器,三種函式按照順序被執行:

字元過濾器

字元過濾器用來整理一個尚未被分詞的字串。例如,如果我們的文字是HTML格式的,它會包含像<p>或者<div>這樣的HTML標籤,這些標籤是我們不想索引的。我們可以使用html清除字元過濾器來移除掉所有的HTML標籤,並且像把Á轉換為相對應的Unicode字元Á 這樣,轉換HTML實體。一個分析器可能有0個或者多個字元過濾器。

分詞器

一個分析器必須有一個唯一的分詞器。分詞器把字串分解成單個詞條或者詞彙單元。標準分析器裡使用的標準分詞器把一個字串根據單詞邊界分解成單個詞條,並且移除掉大部分的標點符號,然而還有其他不同行為的分詞器存在。

例如,關鍵詞分詞器完整地輸出接收到的同樣的字串,並不做任何分詞。空格分詞器只根據空格分割文字。正則分詞器根據匹配正則表示式來分割文字。

詞單元過濾器

經過分詞,作為結果的詞單元流會按照指定的順序通過指定的詞單元過濾器。詞單元過濾器可以修改、新增或者移除詞單元。我們已經提到過lowercase和stop詞過濾器,但是在Elasticsearch 裡面還有很多可供選擇的詞單元過濾器。詞幹過濾器把單詞遏制為詞幹。ascii_folding過濾器移除變音符,把一個像"très”這樣的詞轉換為“tres”。

ngram和 edge_ngram詞單元過濾器可以產生適合用於部分匹配或者自動補全的詞單元。

自定義分析器例子

接下來,我們看看如何建立自定義的分析器:

#PUT http://localhost:9200/my_index

{
    "settings": {
        "analysis": {
            "char_filter": {
                "&_to_and": {
                    "type": "mapping", 
                    "mappings": [
                        "&=> and "
                    ]
                }
            }, 
            "filter": {
                "my_stopwords": {
                    "type": "stop", 
                    "stopwords": [
                        "the", 
                        "a"
                    ]
                }
            }, 
            "analyzer": {
                "my_analyzer": {
                    "type": "custom", 
                    "char_filter": [
                        "html_strip", 
                        "&_to_and"
                    ], 
                    "tokenizer": "standard", 
                    "filter": [
                        "lowercase", 
                        "my_stopwords"
                    ]
                }
            }
        }
    }
}

索引被建立以後,使用 analyze API 來 測試這個新的分析器:

# GET http://127.0.0.1:9200/my_index/_analyze
{
    "text":"The quick & brown fox",
    "analyzer": "my_analyzer"
}

返回結果為:

{
    "tokens": [
        {
            "token": "quick",
            "start_offset": 4,
            "end_offset": 9,
            "type": "<ALPHANUM>",
            "position": 1
        },
        {
            "token": "and",
            "start_offset": 10,
            "end_offset": 11,
            "type": "<ALPHANUM>",
            "position": 2
        },
        {
            "token": "brown",
            "start_offset": 12,
            "end_offset": 17,
            "type": "<ALPHANUM>",
            "position": 3
        },
        {
            "token": "fox",
            "start_offset": 18,
            "end_offset": 21,
            "type": "<ALPHANUM>",
            "position": 4
        }
    ]
}

5.8 文件處理

文件衝突

當我們使用index API更新文件,可以一次性讀取原始文件,做我們的修改,然後重新索引整個文件。最近的索引請求將獲勝:無論最後哪一個文件被索引,都將被唯一儲存在 Elasticsearch 中。如果其他人同時更改這個文件,他們的更改將丟失。

很多時候這是沒有問題的。也許我們的主資料儲存是一個關係型資料庫,我們只是將資料複製到Elasticsearch中並使其可被搜尋。也許兩個人同時更改相同的文件的機率很小。或者對於我們的業務來說偶爾丟失更改並不是很嚴重的問題。

但有時丟失了一個變更就是非常嚴重的。試想我們使用Elasticsearch 儲存我們網上商城商品庫存的數量,每次我們賣一個商品的時候,我們在 Elasticsearch 中將庫存數量減少。有一天,管理層決定做一次促銷。突然地,我們一秒要賣好幾個商品。假設有兩個web程式並行執行,每一個都同時處理所有商品的銷售。

web_1 對stock_count所做的更改已經丟失,因為 web_2不知道它的 stock_count的拷貝已經過期。結果我們會認為有超過商品的實際數量的庫存,因為賣給顧客的庫存商品並不存在,我們將讓他們非常失望。

變更越頻繁,讀資料和更新資料的間隙越長,也就越可能丟失變更。在資料庫領域中,有兩種方法通常被用來確保併發更新時變更不會丟失:

  • 悲觀併發控制:這種方法被關係型資料庫廣泛使用,它假定有變更衝突可能發生,因此阻塞訪問資源以防止衝突。一個典型的例子是讀取一行資料之前先將其鎖住,確保只有放置鎖的執行緒能夠對這行資料進行修改。
  • 樂觀併發控制:Elasticsearch 中使用的這種方法假定衝突是不可能發生的,並且不會阻塞正在嘗試的操作。然而,如果源資料在讀寫當中被修改,更新將會失敗。應用程式接下來將決定該如何解決衝突。例如,可以重試更新、使用新的資料、或者將相關情況報告給使用者。

樂觀併發控制

Elasticsearch是分散式的。當文件建立、更新或刪除時,新版本的文件必須複製到叢集中的其他節點。Elasticsearch也是非同步和併發的,這意味著這些複製請求被並行傳送,並且到達目的地時也許順序是亂的。Elasticsearch需要一種方法確保文件的舊版本不會覆蓋新的版本。

當我們之前討論index , GET和DELETE請求時,我們指出每個文件都有一個_version(版本號),當文件被修改時版本號遞增。Elasticsearch使用這個version號來確保變更以正確順序得到執行。如果舊版本的文件在新版本之後到達,它可以被簡單的忽略。

我們可以利用version號來確保應用中相互衝突的變更不會導致資料丟失。我們通過指定想要修改文件的 version號來達到這個目的。如果該版本不是當前版本號,我們的請求將會失敗。

老的版本es使用version,但是新版本不支援了,會報下面的錯誤,提示我們用if_seq _no和if _primary_term

建立文件(增加資料)

#PUT http://127.0.0.1:9200/shopping/_create/1001

返回結果

{
    "_index": "shopping",
    "_type": "_doc",
    "_id": "1001",
    "_version": 1,
    "result": "created",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 10,
    "_primary_term": 15
}

更新資料(區域性修改行)

#POST http://127.0.0.1:9200/shopping/_update/1001
{
    "doc":{
        "title":"華為手機"
    }
}

返回結果:

{
    "_index": "shopping",
    "_type": "_doc",
    "_id": "1001",
    "_version": 2,
    "result": "updated",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 11,
    "_primary_term": 15
}

舊版本使用的防止衝突更新方法:

#POST http://127.0.0.1:9200/shopping/_update/1001?version=1
{
    "doc":{
        "title":"華為手機2"
    }
}

返回結果:

{
    "error": {
        "root_cause": [
            {
                "type": "action_request_validation_exception",
                "reason": "Validation Failed: 1: internal versioning can not be used for optimistic concurrency control. Please use `if_seq_no` and `if_primary_term` instead;"
            }
        ],
        "type": "action_request_validation_exception",
        "reason": "Validation Failed: 1: internal versioning can not be used for optimistic concurrency control. Please use `if_seq_no` and `if_primary_term` instead;"
    },
    "status": 400
}

新版本使用的防止衝突更新方法:

#POST http://127.0.0.1:9200/shopping/_update/1001?if_seq_no=11&if_primary_term=15
{
    "doc":{
        "title":"華為手機2"
    }
}

返回結果:

{
    "_index": "shopping",
    "_type": "_doc",
    "_id": "1001",
    "_version": 3,
    "result": "updated",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 12,
    "_primary_term": 16
}

外部系統版本控制

一個常見的設定是使用其它資料庫作為主要的資料儲存,使用Elasticsearch做資料檢索,這意味著主資料庫的所有更改發生時都需要被複制到Elasticsearch,如果多個程序負責這一資料同步,你可能遇到類似於之前描述的併發問題。

如果你的主資料庫已經有了版本號,或一個能作為版本號的欄位值比如timestamp,那麼你就可以在 Elasticsearch 中通過增加 version_type=extermal到查詢字串的方式重用這些相同的版本號,版本號必須是大於零的整數,且小於9.2E+18,一個Java中 long型別的正值。

外部版本號的處理方式和我們之前討論的內部版本號的處理方式有些不同,Elasticsearch不是檢查當前_version和請求中指定的版本號是否相同,而是檢查當前_version是否小於指定的版本號。如果請求成功,外部的版本號作為文件的新_version進行儲存。

#POST http://127.0.0.1:9200/shopping/_doc/1001?version=300&version_type=external
{
	"title":"華為手機2"
}

返回結果:

{
    "_index": "shopping",
    "_type": "_doc",
    "_id": "1001",
    "_version": 300,
    "result": "updated",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 13,
    "_primary_term": 16
}

5.9 文件展示-Kibana

Kibana是一個免費且開放的使用者介面,能夠讓你對Elasticsearch 資料進行視覺化,並讓你在Elastic Stack 中進行導航。你可以進行各種操作,從跟蹤查詢負載,到理解請求如何流經你的整個應用,都能輕鬆完成。

Kibana下載網址

1、解壓縮下載的 zip 檔案。

2、修改 config/kibana.yml 檔案。

# 預設埠
server.port: 5601
# ES 伺服器的地址
elasticsearch.hosts: ["http://localhost:9200"]
# 索引名
kibana.index: ".kibana"
# 支援中文
i18n.locale: "zh-CN"

3、Windows 環境下執行 bin/kibana.bat 檔案。(首次啟動有點耗時)

四、通過瀏覽器訪問:http://localhost:5601