ES集羣(elasticsearch)

爲什麼要用ES集羣?

Elasticsearch(ES)是一個基於Lucene構建的開源、分佈式、RESTful接口的全文搜索引擎。Elasticsearch還是一個分佈式文檔數據庫,其中每個字段均可被索引,而且每個字段的數據均可被搜索,可以在極短的時間內存儲、搜索和分析大量的數據。
用戶對數據進行新建或編輯的操作,這些數據都會被存在ES中,當用戶使用搜索功能對想要的目標數據進行搜索時,邏輯流程如下:
客戶端傳入搜索的參數 - 調用服務端搜索接口 - 服務端通過接口實現去ES中查詢已儲存的現成的數據 - 將查詢結果返回給客戶端。
對比如果不用ES,調用服務端接口時會通過sql語句去數據庫查詢然後給客戶端返回查詢結果,這樣是相當慢的。
大家都很熟悉數據庫,爲了簡單理解,搞清裏面的結構,先來一個ES與數據庫的類比關係:

ES數據架構的主要概念(與關係數據庫Mysql對比)

關係數據庫 ⇒ 數據庫 ⇒ 表 ⇒ 行 ⇒ 列(Columns)
Elasticsearch ⇒ 索引(Index) ⇒ 類型(type) ⇒ 文檔(Docments) ⇒ 字段(Fields)
(1)關係型數據庫中的數據庫(DataBase),等價於ES中的索引(Index)
(2)一個數據庫下面有N張表(Table),等價於1個索引Index下面有N多類型(Type)
(3)一個數據庫表(Table)下的數據由多行(ROW)多列(column,屬性)組成,等價於1個Type由多個文檔(Document)和多Field組成。
(4)在一個關係型數據庫裏面,schema定義了表、每個表的字段,還有表和字段之間的關係。 與之對應的,在ES中:Mapping定義索引下的Type的字段處理規則,即索引如何建立、索引類型、是否保存原始索引JSON文檔、是否壓縮原始JSON文檔、是否需要分詞處理、如何進行分詞處理等。
(5)在數據庫中的增insert、刪delete、改update、查search操作等價於ES中的增PUT/POST、刪Delete、改_update、查GET.

ES核心概念

1)Cluster:集羣。

ES可以作爲一個獨立的單個搜索服務器。不過,爲了處理大型數據集,實現容錯和高可用性,ES可以運行在許多互相合作的服務器上。這些服務器的集合稱爲集羣。

2)Node:節點。

形成集羣的每個服務器稱爲節點。

3)Index:索引。

在 ES 中, 索引是一組文檔的集合。索引的作用相當於圖書的目錄,可以根據目錄中的頁碼快速找到所需的內容。當表中有大量記錄時,若要對錶進行查詢,第一種搜索信息方式是全表搜索,是將所有記錄一一取出,和查詢條件進行一一對比,然後返回滿足條件的記錄,這樣做會消耗大量數據庫系統時間,並造成大量磁盤I/O操作;第二種就是在表中建立索引,然後在索引中找到符合查詢條件的索引值,最後通過保存在索引中的ROWID(相當於頁碼)快速找到表中對應的記錄。

4)Shard:分片。

當有大量的文檔時,由於內存的限制、磁盤處理能力不足、無法足夠快的響應客戶端的請求等,一個節點可能不夠。這種情況下,數據可以分爲較小的分片。每個分片放到不同的服務器上。
當你查詢的索引分佈在多個分片上時,ES會把查詢發送給每個相關的分片,並將結果組合在一起,而應用程序並不知道分片的存在。即:這個過程對用戶來說是透明的。
需要注意:在創建索引的時候就確定好主分片的數量,並且永遠不能改變這個數量。

 

分片.jpg

 

比如上圖所示,開始設置爲5個分片,在單個節點上,後來擴容到5個節點,每個節點有一個分片。如果繼續擴容,是不能自動切分進行數據遷移的。官方文檔的說法是分片切分成本和重新索引的成本差不多,所以建議乾脆通過接口重新索引。

路由一個文檔到一個分片

當索引一個文檔的時候,文檔會被存儲到一個主分片中。 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)Replia:副本。

爲提高查詢吞吐量或實現高可用性,可以使用分片副本。
副本是一個分片的精確複製,每個分片可以有零個或多個副本。ES中可以有許多相同的分片,其中之一被選擇更改索引操作,這種特殊的分片稱爲主分片。
當主分片丟失時,如:該分片所在的數據不可用時,集羣將副本提升爲新的主分片。
Elasticsearch 禁止同一個分片的主分片和副本分片在同一個節點上,所以如果是一個節點的集羣是不能有副本的。

 

它在節點失敗的情況下提供高可用性。由於這個原因,需要注意的是,副本分片永遠不會分配到與主分片相同的節點上。

ES集羣簡圖

詳解

1.我們能夠發送請求給集羣中任意一個節點。每個節點都有能力處理任意請求。每個節點都知道任意文檔所在的節點
2.新建索引和刪除請求都是寫操作,它們必須在主分片上成功完成才能賦值到相關的複製分片上
3.在主分片和複製分片上成功新建、索引或刪除一個文檔必要的順序步驟:
(1) 客戶端給Node1 發送新建、索引或刪除請求。
(2) 節點使用文檔的_id確定文檔屬於分片0.轉發請求到Node3,分片0位於這個節點上。
(3) Node3在主分片上執行請求,如果成功,它轉發請求到相應的位於Node1和Node2的複製節點上。當所有的複製節點報告成功,Node3報告成功到請求的節點,請求的節點再報告給客戶端。
(4)客戶端接收到成功響應的時候,文檔的修改已經被用於主分片和所有的複製分片,修改生效了。

ES分片複製

複製默認的值是sync。這將導致主分片得到複製分片的成功響應後才返回。
如果你設置replication爲async,請求在主分片上被執行後就會返回給客戶端。它依舊會轉發給複製節點,單你將不知道複製節點成功與否。
上面的這個選項不建議使用。默認的sync複製允許ES強制反饋傳輸。async複製可能會因爲在不等待其他分片就緒的情況下發送過多的請求而使ES過載。

6)全文檢索。

全文檢索就是對一篇文章進行索引,可以根據關鍵字搜索,類似於mysql裏的like語句。
全文索引就是把內容根據詞的意義進行分詞,然後分別創建索引,例如”你們的激情是因爲什麼事情來的” 可能會被分詞成:“你們“,”激情“,“什麼事情“,”來“ 等token,這樣當你搜索“你們” 或者 “激情” 都會把這句搜出來。

Elasticsearch是如何做到快速索引的

Elasticsearch的索引思路:
將磁盤裏的東西儘量搬進內存,減少磁盤隨機讀取次數。
Elasticsearch是通過Lucene的倒排索引技術實現比關係型數據庫更快的過濾。倒排索引很多地方都有介紹,但是其比關係型數據庫的b-tree索引快在哪裏?到底爲什麼快呢?
這裏有好幾個概念。我們來看一個實際的例子,假設有如下的數據:
docid 年齡 性別
1 18 女
2 20 女
3 18 男
這裏每一行是一個document。每個document都有一個docid。那麼給這些document建立的倒排索引就是:
年齡
18 [1,3]
20 [2]
性別
女 [1,2]
男 [3]
可以看到,倒排索引是per field的,一個字段有一個自己的倒排索引。18,20這些叫做 term,而[1,3]就是posting list。Posting list就是一個int的數組,存儲了所有符合某個term的文檔id。那麼什麼是term dictionary 和 term index?
假設我們有很多個term,比如:
姓名:Carla,Sara,Elin,Ada,Patty,Kate,Selena
如果按照這樣的順序排列,找出某個特定的term一定很慢,因爲term沒有排序,需要全部過濾一遍才能找出特定的term。排序之後就變成了:
Ada,Carla,Elin,Kate,Patty,Sara,Selena
這樣我們可以用二分查找的方式,比全遍歷更快地找出目標的term。這個就是 term dictionary。有了term dictionary之後,可以用 logN 次磁盤查找得到目標。但是磁盤的隨機讀操作仍然是非常昂貴的(一次random access大概需要10ms的時間)。所以儘量少的讀磁盤,有必要把一些數據緩存到內存裏。但是整個term dictionary本身又太大了,無法完整地放到內存裏。於是就有了term index。term index有點像一本字典的大的章節表。比如:
A開頭的term ……………. Xxx頁
C開頭的term ……………. Xxx頁
E開頭的term ……………. Xxx頁

 

如果所有的term都是英文字符的話,可能這個term index就真的是26個英文字符表構成的了。但是實際的情況是,term未必都是英文字符,term可以是任意的byte數組。而且26個英文字符也未必是每一個字符都有均等的term,比如x字符開頭的term可能一個都沒有,而s開頭的term又特別多。實際的term index是一棵trie 樹:

Alt text

例子是一個包含 "A", "to", "tea", "ted", "ten", "i", "in", 和 "inn" 的 trie 樹。這棵樹不會包含所有的term,它包含的是term的一些前綴。通過term index可以快速地定位到term dictionary的某個offset,然後從這個位置再往後順序查找。再加上一些壓縮技術(搜索 Lucene Finite State Transducers) term index 的尺寸可以只有所有term的尺寸的幾十分之一,使得用內存緩存整個term index變成可能。整體上來說就是這樣的效果。

Alt text


現在我們可以回答“爲什麼Elasticsearch/Lucene檢索可以比mysql快了。Mysql只有term dictionary這一層,是以b-tree排序的方式存儲在磁盤上的。檢索一個term需要若干次的random access的磁盤操作。而Lucene在term dictionary的基礎上添加了term index來加速檢索,term index以樹的形式緩存在內存中。從term index查到對應的term dictionary的block位置之後,再去磁盤上找term,大大減少了磁盤的random access次數。
額外值得一提的兩點是:term index在內存中是以FST(finite state transducers)的形式保存的,其特點是非常節省內存。Term dictionary在磁盤上是以分block的方式保存的,一個block內部利用公共前綴壓縮,比如都是Ab開頭的單詞就可以把Ab省去。這樣term dictionary可以比b-tree更節約磁盤空間。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章