初識 ElastricSearch

語言: CN / TW / HK

不知道大家在專案中是否使用過 ElastricSearch?大家對它的瞭解又有多少呢?官網的定義:Elasticsearch 是一個分散式、可擴充套件、近實時的搜尋與資料分析引擎。今天我們就來揭開一下它的神祕面紗(以下簡稱 ES)。

ES 是使用 Java 編寫的一種開源搜尋引擎,它在內部使用 Lucene 做索引與搜尋,通過對 Lucene 的封裝,隱藏了 Lucene 的複雜性,取而代之的提供一套簡單一致的 RESTful API。然而,ES 不僅僅是 Lucene,並且也不僅僅只是一個全文搜尋引擎,它還有如下特點:

  • 一個分散式的實時文件儲存,每個欄位可以被索引與搜尋;

  • 一個分散式實時分析搜尋引擎;

  • 能勝任上百個服務節點的擴充套件,並支援 PB 級別的結構化或者非結構化資料。

@[toc]

一、結構化資料、非結構化資料

首先我們將資料大體分為兩類,一類是 結構化資料 :也稱作行資料,指具有固定格式或有限長度的資料,是由二維表結構來邏輯表達和實現的資料,嚴格地遵循資料格式與長度規範,主要通過關係型資料庫進行儲存和管理,如資料庫、元資料等;一類是 非結構化資料 :又可稱為全文資料,不定長或無固定格式,不適於由資料庫二維表來表現,包括所有格式的辦公文件、XML、HTML、Word 文件,郵件,各類報表、圖片和音訊、影片資訊等。

如果要更細緻的區分的話,XML、HTML 可劃分為半結構化資料。因為它們也具有自己特定的標籤格式,所以既可以根據需要按結構化資料來處理,也可抽取出純文字按非結構化資料來處理。

根據兩種資料分類,搜尋也相應的分為兩種:

(1)結構化資料搜尋:因為它們具有特定的結構,所以我們一般都是可以通過關係型資料庫(MySQL,Oracle 等)的二維表(Table)的方式儲存和搜尋,也可以建立索引。

(2)非結構化資料搜尋:即對全文資料的搜尋,主要有兩種方法:順序掃描、全文搜尋;

  • 順序掃描:即按照順序掃描的方式查詢特定的關鍵字。例如給你一張報紙,讓你找到該報紙中“阿 Q”的文字在哪些地方出現過。你肯定需要從頭到尾把報紙閱讀掃描一遍然後標記出關鍵字在哪些版塊出現過以及它的出現位置。這種方式無疑是最耗時的最低效的。

  • 全文搜尋:將非結構化資料中的一部分資訊提取出來,重新組織,使其變得有一定結構(這種結構,我們稱之為 索引 ),然後對此有一定結構的資料進行搜尋,從而達到搜尋相對較快的目的。這種方式的主要工作量在前期索引的建立,但是對於後期搜尋卻是快速高效的。

二、Lucene

Lucene 是 apache 軟體基金會 4 jakarta 專案組的一個子專案,是一個開放原始碼的全文檢索引擎工具包,但它不是一個完整的全文檢索引擎,而是一個全文檢索引擎的架構,提供了完整的查詢引擎和索引引擎,部分文字分析引擎(英文與德文兩種西方語言)。Lucene 的目的是為軟體開發人員提供一個簡單易用的工具包,以方便的在目標系統中實現全文檢索的功能,或者是以此為基礎建立起完整的全文檢索引擎。Lucene 是一套用於全文檢索和搜尋的開源程式庫,由 Apache 軟體基金會支援和提供。

我所熟知的以 Lucene 為基礎建立的開源可用全文搜尋引擎主要有 Solr 和 ES :ES 本身就具有分散式的特性和易安裝使用的特點,而 Solr 的分散式需要藉助第三方來實現,例如通過使用 ZooKeeper 來達到分散式協調管理。

接下來我們說一下 Lucene 能實現全文搜尋的一個重要的查詢結構: 倒排索引

舉例

  • Java is the best programming language.

  • PHP is the best programming language.

  • Javascript is the best programming language.

通過分詞器將每個文件的內容域拆分成單獨的詞(我們稱它為詞條或 Term),建立一個包含所有不重複詞條的排序列表,然後列出每個詞條出現在哪個文件。 結果

這種結構由文件中所有不重複詞的列表構成,對於其中每個詞都有一個文件列表與之關聯。這種由 屬性值來確定記錄的位置的結構就是倒排索引 。帶有倒排索引的檔案我們稱為倒排檔案。

將上面的內容轉換為圖的形式來說明倒排索引的結構資訊

概念如下:

  • 詞條(Term):索引裡面最小的儲存和查詢單元,對於英文來說是一個單詞,對於中文來說一般指分詞後的一個詞。

  • 詞典(Term Dictionary):或字典,是詞條 Term 的集合。搜尋引擎的通常索引單位是單詞,單詞詞典是由文件集合中出現過的所有單詞構成的字串集合,單詞詞典內每條索引項記載單詞本身的一些資訊以及指向“倒排列表”的 指標

  • 倒排表(Post list):一個文件通常由多個片語成,倒排表記錄的是某個詞在哪些文件裡出現過以及出現的位置。每條記錄稱為一個倒排項(Posting)。倒排表記錄的不單是文件編號,還儲存了詞頻等資訊。

  • 倒排檔案(Inverted File):所有單詞的倒排列表往往順序地儲存在磁碟的某個檔案裡,這個檔案被稱之為倒排檔案,倒排檔案是儲存倒排索引的物理檔案。

詞典和倒排表是 Lucene 中很重要的兩種資料結構,是實現快速檢索的重要基石。詞典和倒排檔案是分兩部分儲存的,詞典在記憶體中而倒排檔案儲存在磁碟上。

三、ES 中的概念解釋

為了大家更好的理解 ES,下面介紹一下 ES 中的概念:

(1)Near Realtime(NRT):近實時,一是從寫入資料到資料可以被搜尋到有一個小延遲(預設 1 秒);二是基於 ES 執行搜尋和分析可以達到秒級;

(2)Cluster(叢集):ES 叢集由一個或多個 ES 節點組成,每個節點配置相同的 cluster.name (預設值為 “elasticsearch”)即可加入叢集。確保不同的環境中使用不同的叢集名稱,否則最終會導致節點加入錯誤的叢集。ES 的叢集搭建很簡單,不需要依賴第三方協調管理元件,自身內部就實現了叢集的管理功能。

(3)Node:一個 ES 服務啟動例項就是一個節點(Node)。節點通過 node.name 來設定節點名稱,如果不設定則在啟動時給節點分配一個隨機通用唯一識別符號作為名稱(預設是隨機分配的)。預設節點會去加入一個名稱為“elasticsearch”的叢集。如果直接啟動一堆節點,那麼它們會自動組成一個 elasticsearch 叢集,當然一個節點也可以組成一個 elasticsearch 叢集。

(4)Index(索引):簡單理解就是一個數據庫,索引有名稱,包含一堆有相似結構的文件資料,一個 index 包含很多 document,一個 index 就代表了一類類似的或者相同的 document。比如說建立一個 product index,商品索引,裡面可能就存放了所有的商品資料,所有的商品 document。索引是儲存相關資料的地方。 索引實際上是指向一個或者多個物理 分片 的 邏輯名稱空間 。

(5)Type(型別):簡單理解就是一張表,每個索引裡都可以有一個或多個 type,type 是 index 中的一個邏輯資料分類,一個 type 下的 document,都有相同的 field,比如部落格系統,有一個索引,可以定義使用者資料 type,部落格資料 type,評論資料 type。

(6)Document(文件)&field(欄位):Document 就是一行資料,es 中的最小資料單元,一個 document 可以是一條客戶資料,一條商品分類資料,通常用 JSON 資料結構表示,每個 index 下的 type 中,都可以去儲存多個 document。一個 document 裡面有多個 field,每個 field 就是一個數據欄位。

四、ES 叢集的選舉原理

①節點的角色

節點分為主節點、候選主節點、資料節點和協調節點,其中候選主節點和資料節點是在配置檔案中指定的,而主節點是由候選主節點選舉出來的,協調節點則可以是任何節點,以下是它們的功能介紹:

資料節點:負責資料的儲存和相關的操作,例如對資料進行增、刪、改、查和聚合等操作,所以資料節點(Data 節點)對機器配置要求比較高,對 CPU、記憶體和 I/O 的消耗很大。通常隨著叢集的擴大,需要增加更多的資料節點來提高效能和可用性。

候選主節點:可以被選舉為主節點(Master 節點),叢集中只有候選主節點才有選舉權和被選舉權,其他節點不參與選舉的工作。

主節點:負責建立索引、刪除索引、跟蹤哪些節點是群集的一部分,並決定哪些分片分配給相關的節點、追蹤叢集中節點的狀態等。主節點和其他節點之間通過 Ping 的方式互相檢查,主節點負責 Ping 所有其他節點,判斷是否有節點已經掛掉;其他節點也通過 Ping 的方式判斷主節點是否處於可用狀態;穩定的主節點對叢集的健康是非常重要的。

協調節點:雖然對節點做了角色區分,但是使用者的請求可以發往任何一個節點,並由該節點負責分發請求、收集結果等操作,而不需要主節點轉發,這種節點可稱之為協調節點,協調節點是不需要指定和配置的,叢集中的任何節點都可以充當協調節點的角色。

ES 中每個節點既可以是候選主節點也可以是資料節點,通過在配置檔案 ../config/elasticsearch.yml 中設定即可,預設都為 true。

node.master: true  //是否候選主節點node.data: true    //是否資料節點

複製程式碼

但是由於資料節點對 CPU、記憶體核 I/O 消耗都很大,所以如果某個節點既是資料節點又是主節點,那麼可能會對主節點產生影響從而對整個叢集的狀態產生影響。因此為了提高叢集的健康性,我們應該對 ES 叢集中的節點做好角色上的劃分和隔離,可以使用幾個 配置較低 的機器群作為候選主節點群。

②發現與選舉機制

發現機制(Zen Discovery)

那麼有一個問題,ES 內部是如何通過一個相同的設定 cluster.name 就能將不同的節點連線到同一個叢集的呢?答案是 Zen DiscoveryZen Discovery 是 ES 的內建預設發現模組(發現模組的職責是發現叢集中的節點以及選舉 Master 節點)。它提供單播和基於檔案的發現,並且可以擴充套件為通過外掛支援雲環境和其他形式的發現。 Zen Discovery 與其它模組整合:例如,節點之間的所有通訊都使用 Transport 模組完成,節點使用發現機制通過 Ping 的方式查詢其他節點。

ES 預設被配置為使用 單播 發現,以防止節點無意中加入叢集,只有在同一臺機器上執行的節點才會自動組成叢集。如果叢集的節點執行在不同的機器上,使用單播,你可以為 ES 節點提供一些它應該去嘗試連線的節點列表即單播列表。當該節點聯絡到單播列表中的成員時,它就會得到整個叢集所有節點的狀態,然後它會聯絡 Master 節點,並加入叢集。這意味著單播列表不需要包含叢集中的所有節點, 它只是需要足夠的節點,當一個新節點聯絡上其中一個並且說上話就可以了。如果你使用 Master 候選節點作為單播列表,你只要列出三個就可以了。這個配置在 elasticsearch.yml 檔案中:

discovery.zen.ping.unicast.hosts: ["host1", "host2:port"]

複製程式碼

ES 節點啟動後先 Ping ,如果 discovery.zen.ping.unicast.hosts 有設定,則 Ping 設定中的 Host ,否則嘗試 ping 本地的幾個埠。ES 支援同一個主機啟動多個節點,Ping 的 Response 會包含該節點的基本資訊以及該節點認為的 Master 節點。

Master 選舉

採用分散式系統思路,保證選舉出的 master 被多數派(quorum)的 master-eligible node(候選主節點)認可,以此來保證只有一個 master。這個 quorum 通過以下配置進行配置:

conf/elasticsearch.yml:    discovery.zen.minimum_master_nodes: 2

複製程式碼

(1)master 選舉誰發起,什麼時候發起?

master(主節點)選舉當然是由 master-eligible(候選主節點)節點發起,當一個 master-eligible 節點發現滿足以下條件時發起選舉:

  • 該 master-eligible 節點的當前狀態不是 master;

  • 該 master-eligible 節點通過 ZenDiscovery 模組的 ping 操作詢問其已知的叢集其他節點,沒有任何節點連線到 master;

  • 包括本節點在內,當前已有超過 minimum_master_nodes 個節點沒有連線到 master;

總結一句話,即當一個節點發現包括自己在內的多數派的 master-eligible 節點認為叢集沒有 master 時,就可以發起 master 選舉。

(2)當需要選舉 master 時,選舉誰?

如果各節點都沒有認為的 Master ,則從所有候選主節點中選擇,規則很簡單,按照 ID(ID 為節點第一次啟動時隨機生成) 的字典序排序,取第一個。之所以這麼設計,應該是為了讓選舉結果儘可能穩定,不要出現都想當 master 而選不出來的情況。

(3) 什麼時候選舉成功?

這裡有個限制條件就是 discovery.zen.minimum_master_nodes :如果候選主節點數達不到最小值的限制,則迴圈上述過程,直到候選主節點數足夠可以開始選舉,如果只有一個 Local 節點那就選出的是自己。如果當前節點被選為 Master ,則開始等待節點數達到 discovery.zen.minimum_master_nodes,然後提供服務;如果當前節點不是 Master ,則嘗試加入 Master 。

舉例說明:

當一個 master-eligible node(我們假設為 Node_A)發起一次選舉時:

a、假設 Node_A 選 Node_B 當 Master:Node_A 會向 Node_B 傳送 join 請求,那麼此時:

  • 如果 Node_B 已經成為 Master,Node_B 就會把 Node_A 加入到叢集中,然後釋出最新的 cluster_state, 最新的 cluster_state 就會包含 Node_A 的資訊,相當於一次正常情況的新節點加入。對於 Node_A,等新的 cluster_state 釋出到 Node_A 的時候,Node_A 也就完成 join 了。

  • 如果 Node_B 在競選 Master,那麼 Node_B 會把這次 join 當作一張選票。對於這種情況,Node_A 會等待一段時間,看 Node_B 是否能成為真正的 Master,直到超時或者有別的 Master 選成功。

  • 如果 Node_B 認為自己不是 Master(現在不是,將來也選不上),那麼 Node_B 會拒絕這次 join。對於這種情況,Node_A 會開啟下一輪選舉。

b、假設 Node_A 選自己當 Master:

此時 NodeA 會等別的 node 來 join,即等待別的 node 的選票,當收集到超過半數的選票時,認為自己成為 master,然後變更 cluster_state 中的 master node 為自己,並向叢集釋出這一訊息。

③腦裂現象

如果由於網路或其他原因導致叢集中選舉出多個 Master 節點,使得資料更新時出現不一致,這種現象稱之為腦裂,即叢集中不同的節點對於 Master 的選擇出現了分歧,出現了多個 Master 競爭。

腦裂問題可能有以下幾個原因造成:

  • 網路問題:叢集間的網路延遲導致一些節點訪問不到 Master,認為 Master 掛掉了從而選舉出新的 Master,並對 Master 上的分片和副本標紅,分配新的主分片。

  • 節點負載:主節點的角色既為 Master 又為 Data,訪問量較大時可能會導致 ES 停止響應(假死狀態)造成大面積延遲,此時其他節點得不到主節點的響應認為主節點掛掉了,會重新選取主節點。

  • 記憶體回收:主節點的角色既為 Master 又為 Data,當 Data 節點上的 ES 程序佔用的記憶體較大,引發 JVM 的大規模記憶體回收,造成 ES 程序失去響應。

為了避免腦裂現象的發生,我們可以從原因著手通過以下幾個方面來做出優化措施:

  • 適當調大響應時間,減少誤判:通過引數 discovery.zen.ping_timeout 設定節點狀態的響應時間,預設為 3s,可以適當調大。如果 Master 在該響應時間的範圍內沒有做出響應應答,判斷該節點已經掛掉了。

  • 選舉觸發:我們需要在候選叢集中的節點的配置檔案中設定引數 discovery.zen.munimum_master_nodes 的值。這個引數表示在選舉主節點時需要參與選舉的候選主節點的節點數,預設值是 1,官方建議取值(master_eligibel_nodes/2)+1,其中 master_eligibel_nodes 為候選主節點的個數。這樣做既能防止腦裂現象的發生,也能最大限度地提升叢集的高可用性,因為只要不少於 discovery.zen.munimum_master_nodes 個候選節點存活,選舉工作就能正常進行;當小於這個值的時候,無法觸發選舉行為,叢集無法使用,不會造成分片混亂的情況。

  • 角色分離:即是上面我們提到的候選主節點和資料節點進行角色分離,這樣可以減輕主節點的負擔,防止主節點的假死狀態發生,減少對主節點“已死”的誤判。

阿 Q 將持續更新 java 實戰方面的文章,如果你有不同的意見或者更好的 idea,歡迎聯絡阿 Q。

【阿 Q 說程式碼】,值得關注的公眾號

文章風格多變,配圖通俗易懂,故事生動有趣,來聊聊技術呀!