初識 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 説代碼】,值得關注的公眾號

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