天天看點

[ElasticSearch]原理之分布式文檔存儲(Distributed Document Store)

之前的文章中,我們已經知道如何存儲資料到索引中以及如何檢索它。但是我們掩蓋了資料存儲到叢集中以及從叢集中擷取資料的具體實作的技術細節(But we glossed over many technical details surrounding how the data is distributed and fetched from the cluster)。

https://note.youdao.com/md/preview/preview.html?file=%2Fyws%2Fapi%2Fpersonal%2Ffile%2FWEB7353a01e847e3bf03de38cd21615d160%3Fmethod%3Ddownload%26read%3Dtrue#1-%E8%B7%AF%E7%94%B1%E6%96%87%E6%A1%A3%E5%88%B0%E5%88%86%E7%89%87%E4%B8%ADrouting-a-document-to-a-shard 1. 路由文檔到分片中(Routing a Document to a Shard)

當你索引一篇文檔時,它會存儲到一個主分片中。但是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參數,來自定義文檔與分片之間的映射。一個自定義的路由參數可以用來確定所有相關的文檔——例如所有屬于同一個使用者的文檔——都被存儲到同一個分片中。

https://note.youdao.com/md/preview/preview.html?file=%2Fyws%2Fapi%2Fpersonal%2Ffile%2FWEB7353a01e847e3bf03de38cd21615d160%3Fmethod%3Ddownload%26read%3Dtrue#2-%E4%B8%BB%E5%88%86%E7%89%87%E4%B8%8E%E5%89%AF%E6%9C%AC%E5%88%86%E7%89%87%E5%A6%82%E4%BD%95%E4%BA%A4%E4%BA%92 2. 主分片與副本分片如何互動

假設我們有一個三個節點的叢集。叢集裡有一個名稱為

blog

的索引,有兩個主分片(primary shards)。每個主分片都有兩個副本。相同節點的副本不會配置設定到同一節點,最後如下圖展示:

我們可以發送請求到叢集中的任何一個節點,每個節點都有能力處理我們的請求。每個節點都知道叢集中任意文檔的存儲位置,是以可以直接将請求轉發到所需的節點(Every node knows the location of every document in the cluster,so can forward requests directly to the required node)。

在下面的例子中,我們将請求都發送到節點1上,我們将其稱為協調節點(coordinating node)。

https://note.youdao.com/md/preview/preview.html?file=%2Fyws%2Fapi%2Fpersonal%2Ffile%2FWEB7353a01e847e3bf03de38cd21615d160%3Fmethod%3Ddownload%26read%3Dtrue#21-%E5%88%9B%E5%BB%BA%E7%B4%A2%E5%BC%95%E5%92%8C%E5%88%A0%E9%99%A4%E6%96%87%E6%A1%A3 2.1 建立,索引和删除文檔

建立,索引和删除請求都是寫操作,是以必須在主分片上寫操作完成之後才能被複制到相關的副本分片上(Create, index, and delete requests are write operations, which must be successfully completed on the primary shard before they can be copied to any associated replica shards)。

互動過程如下圖所示:

下面是成功在主分片和副本分片上建立,索引以及删除文檔所必須的步驟:

  • 用戶端發送了一個建立,索引 或者 删除文檔 請求給節點 1;
  • 節點 1 通過請求文檔的 id 值判斷出該文檔應該被存儲在分片 0 中,并且知道分片 0 的主分片 P0 位于節點 3 上。是以節點 1 會把這個請求轉發給節點 3;
  • 節點 3 在主分片上執行請求。如果請求執行成功,節點 3 并行将該請求轉發給節點 1 和節點 2 上的的副本分片(R0)。一旦所有的副本分片都成功地執行了請求,則向節點 3 報告成功,節點 3 向協調節點 (Node 1 )報告成功,協調節點向用戶端報告成功。

在用戶端收到成功響應時,文檔變更已經在主分片和所有副本分片執行完成,變更是安全的。

有一些可選的請求參數允許您影響這個過程,可能以資料安全為代價提升性能。這些選項很少使用,因為Elasticsearch已經很快,但是為了完整起見,在這裡闡述如下:

https://note.youdao.com/md/preview/preview.html?file=%2Fyws%2Fapi%2Fpersonal%2Ffile%2FWEB7353a01e847e3bf03de38cd21615d160%3Fmethod%3Ddownload%26read%3Dtrue#211-%E4%B8%80%E8%87%B4%E6%80%A7 2.1.1 一緻性

預設情況下,在嘗試進行寫操作之前,主分片需要規定數量(quorum)或大多數(majority)的分片副本

shard copies

(其中分片副本可以是主分片或副本分片 

a shard copy can be a primary or a replica shard

)。 這是為了防止将資料寫入網絡分區的“錯誤的一邊

wrong side

”。 規定數量

quorum

定義如下:

int( (primary + number_of_replicas) / 2 ) + 1           

一緻性

consistency

值可以是

one

(隻有主分片),

all

(主分片和所有的副本),或者預設值

quorum

,或者大多數的分片副本(The allowed values for consistency are one (just the primary shard), all (the primary and all replicas), or the default quorum, or majority, of shard copies.)。

請注意,

number_of_replicas

是索引設定中指定的副本數,而不是目前活躍的副本數。 如果指定索引有三個副本,則

quorum

将如下定義:

int( (primary + 3 replicas) / 2 ) + 1 = 3           

但是,如果僅啟動兩個節點,則活躍的分片副本不滿足規定數量,您将無法對任何文檔進行索引或删除。

https://note.youdao.com/md/preview/preview.html?file=%2Fyws%2Fapi%2Fpersonal%2Ffile%2FWEB7353a01e847e3bf03de38cd21615d160%3Fmethod%3Ddownload%26read%3Dtrue#212-%E8%B6%85%E6%97%B6 2.1.2 逾時

如果沒有足夠的副本分片會發生什麼? Elasticsearch會等待,希望更多的分片出現。預設情況下,它最多等待1分鐘。 如果你需要,你可以使用 timeout 參數 使它更早終止: 100 100毫秒,30s 是30秒。

備注

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

https://note.youdao.com/md/preview/preview.html?file=%2Fyws%2Fapi%2Fpersonal%2Ffile%2FWEB7353a01e847e3bf03de38cd21615d160%3Fmethod%3Ddownload%26read%3Dtrue#22-%E6%A3%80%E7%B4%A2%E6%96%87%E6%A1%A3 2.2 檢索文檔

我們可以從一個主分片(primary shard)或者它們任一副本中檢索文檔,流程如下圖:

下面是從主分片或者副本分片上檢索文檔所需要的一系列步驟:

  • 用戶端發送了一個 Get 請求給節點 1;
  • 節點 1 通過請求文檔的 id 值判斷出該文檔被存儲在分片 0 中。三個節點上都存有分片 0 的複制(節點1上R0,節點2上R0,節點3上P0)。這一次,它将請求轉發給節點 2 。
  • 節點 2 傳回文檔給節點 1 ,節點 1 在傳回文檔給用戶端。

對于讀請求,對于每一次請求,請求節點都會選擇一個不同的副本分本,達到負載均衡。通過輪詢所有的副本分片。

在文檔被檢索時,已經被索引的文檔可能已經存在于主分片上但是還沒有複制到副本分片。 在這種情況下,副本分片可能會報告文檔不存在,但是主分片可能成功傳回文檔。 一旦索引請求成功傳回給使用者,文檔在主分片和副本分片都是可用的。

https://note.youdao.com/md/preview/preview.html?file=%2Fyws%2Fapi%2Fpersonal%2Ffile%2FWEB7353a01e847e3bf03de38cd21615d160%3Fmethod%3Ddownload%26read%3Dtrue#23-%E5%B1%80%E9%83%A8%E6%9B%B4%E6%96%B0%E6%96%87%E6%A1%A3 2.3 局部更新文檔

更新 API (Update API)融合了上面解釋的兩種讀寫模式,如下圖所示:

下面是部分更新一篇文檔所需要的一系列步驟:

  • 用戶端發送了一個 Update 請求給節點 1;
  • 節點 1 通過請求文檔的 id 值判斷出該文檔被存儲在分片 0 中。并且知道分片 0 的主分片 P0 位于節點 3 上。是以節點 1 會把這個請求轉發給節點 3;
  • 節點 3 從主分片(P0)上檢索出指定文檔,并更改_source字段中的JSON,修改完畢之後試着重新索引文檔到主分片(P0)上。如果有人已經修改了該文檔,那麼會重複步驟3,如果嘗試retry_on_conflict次還沒有成功則放棄。
  • 如果節點 3 更新文檔成功,節點 3 會把文檔新版本并行發給節點 1 和 節點 2 上的副本分片,重新索引文檔。一旦所有的副本分片傳回成功,節點 3 向協調節點傳回成功,協調節點向用戶端傳回成功。

基于文檔的複制

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

https://note.youdao.com/md/preview/preview.html?file=%2Fyws%2Fapi%2Fpersonal%2Ffile%2FWEB7353a01e847e3bf03de38cd21615d160%3Fmethod%3Ddownload%26read%3Dtrue#24-%E5%A4%9A%E6%96%87%E6%A1%A3%E6%A8%A1%E5%BC%8F 2.4 多文檔模式

mget 和 bulk API的模式類似于單文檔模式。 不同的是,協調節點知道每個文檔存儲在哪個分片中。 它将多文檔請求分解成對每個分片的多文檔請求,并将請求并行轉發到每個參與節點。

一旦從每個節點接收到應答,将每個節點的響應整合到單個響應中,并傳回給用戶端

https://note.youdao.com/md/preview/preview.html?file=%2Fyws%2Fapi%2Fpersonal%2Ffile%2FWEB7353a01e847e3bf03de38cd21615d160%3Fmethod%3Ddownload%26read%3Dtrue#241-mget 2.4.1 mget

如下圖所示:

以下是使用單個 mget 請求取回多個文檔所需的步驟順序:

  • 用戶端向節點 1 發送 mget 請求。
  • 節點 1 為每個分片建構多文檔擷取請求,然後并行轉發這些請求到托管在每個所需的主分片或者副本分片的節點上。一旦收到所有應答, 節點 1 建構響應并将其傳回給用戶端。
https://note.youdao.com/md/preview/preview.html?file=%2Fyws%2Fapi%2Fpersonal%2Ffile%2FWEB7353a01e847e3bf03de38cd21615d160%3Fmethod%3Ddownload%26read%3Dtrue#242-bulk 2.4.2 bulk

bulk API,允許在單個批量請求中執行多個建立、索引、删除和更新請求,如下圖所示:

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

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

bulk API 還可以在整個批量請求的最頂層使用 consistency 參數,以及在每個請求中的中繼資料中使用 routing 參數。