ElasticSearch架構反向思路

HitTwice發表於2018-07-27

作者:豬兒笨笨 來源:雲棲社群

原文連結: https://yq.aliyun.com/articles/617479?spm=a2c4e.11153940.bloghomeflow.526.162d291aRoS8gf


我曾經在多個場合說過,我分析一個系統的設計思路,往往不是一開始就去看看這個系統的設計文件或者原始碼,而是去看系統的基本介紹,特別是框架類的功能詳細介紹,然後根據介紹可以大概瞭解這樣一個系統用來解決什麼問題,有哪些特色,然後基於自己對這些問題的想法,根據自己的經驗來同樣設計一個系統,看包含哪些內容,使用哪些架構模式和思路,然後帶著自己設計的東西再去看另一個系統的設計思路,可能再更加清楚,也會反思自己的設計是否哪些地方存在問題,可以加以改進。

最近正好準備玩ElasticSearch,本來在2013年就想玩這個,但由於工作原因耽誤了,現在又翻出來看看有什麼好玩的,下面就詳細地記錄了我對ElasticSearch的反向架構思考。順便補充一句,目前用來研究的ElasticSearch的版本號是6.3

先來看看一份對ElasticSearch比較典型的介紹:

Elasticsearch是一個基於Apache Lucene(TM)的開源搜尋引擎。無論在開源還是專有領域,Lucene可以被認為是迄今為止最先進、效能最好的、功能最全的搜尋引擎庫。
 
但是,Lucene只是一個庫。想要使用它,你必須使用Java來作為開發語言並將其直接整合到你的應用中,更糟糕的是,Lucene非常複雜,你需要深入瞭解檢索的相關知識來理解它是如何工作的。
 
Elasticsearch也使用Java開發並使用Lucene作為其核心來實現所有索引和搜尋的功能,但是它的目的是通過簡單的RESTful API來隱藏Lucene的複雜性,從而讓全文搜尋變得簡單。
 
不過,Elasticsearch不僅僅是Lucene和全文搜尋,我們還能這樣去描述它:
 
分散式的實時檔案儲存,每個欄位都被索引並可被搜尋
分散式的實時分析搜尋引擎
可以擴充套件到上百臺伺服器,處理PB級結構化或非結構化資料
而且,所有的這些功能被整合到一個服務裡面,你的應用可以通過簡單的RESTful API、各種語言的客戶端甚至命令列與之互動。
 
上手Elasticsearch非常容易。它提供了許多合理的預設值,並對初學者隱藏了複雜的搜尋引擎理論。它開箱即用(安裝即可使用),只需很少的學習既可在生產環境中使用。
 
隨著你對Elasticsearch的理解加深,你可以根據不同的問題領域定製Elasticsearch的高階特性,這一切都是可配置的,並且配置非常靈活。

幸虧以前使用過Lucene做IDE底層專案模型關係的管理,對Lucene還算比較熟悉,否則還得先去看看Lucene的功能和用法。

從上面的介紹可以看出幾個關鍵內容:

  1. Lucene在做索引的時候本身就有儲存功能,所以儲存這個東西是天然就有的,反而不用花時間考慮。

  2. 效能是一個比較關鍵的東西,特別是要做實時引擎,怎麼保證高效能。

  3. ElasticSearch是一個分散式的系統,那麼必然存在多結點通訊,協作等問題,比如使用ZooKeeper之類的系統進行註冊和協同,當然也保不齊他自己玩一套。

  4. 既然是分散式系統,那麼資料儲存就不可能完全單機化,也就是存在Sharding的情況,如何Sharding,如何同步,在查詢結果的時候,如何聚合。

  5. 分散式系統,只要涉及到資料更新,必然存在資料不一致問題,怎麼解決。

  6. 由於索引本身原因,一旦出現Sharding,就很難做聯合的查詢,這個應該不能實現的,至少說不可能很簡單得實現。

  7. 有一個網路層或者說對外服務介面層,用來進行互動,看介紹,支援多種協議,比如Client直接呼叫,或者是Restful風格。

  8. 參考服務介面層,還允許很多地方進行配置,那麼很顯然,應該是使用了類似於外掛的技術來支援很多功能。

我的習慣是從使用者角度來倒推系統架構

  1. 對外服務,稱為Interface,這個其實還相對簡單,應該提供兩個基本功能,即BuildIndex(不一定要區分Create和Update,但Delete肯定要有)和Query(應該基於主Key和Condition兩種查詢),把這兩個基本介面設計好,然後在上面加不同的封裝或者通過Netty之類網路架構提供Rest服務,也可能基於Stub類似的機制提供RPC呼叫。

  2. 查詢功能,是採用SQL還是Query模型的方式,我更傾向於後者,因為關聯查詢等很多功能是無法提供的,SQL校驗會是比較麻煩的事情。

  3. 不管是BuildIndex還是Query,肯定要找到一臺機器或者多臺機器進行處理,由於這是一個分散式系統,而且還支援Sharding,那麼可以肯定,需要分組,即Group,一個Group中包括若干個Node,用來支援服務。

  4. 怎麼分組,正常可能是分兩級,一種是基於模型定義的,比如對於某一些資料,象商品,使用者這些資料可能分成一類資料對應一個Group來處理,這種處理比較直觀,也簡單。也就是說每一類模型會對應一個Group,而一個Group可能對著多個模型,特別是資料相對較少的時候。還有一種就是Sharding,通常來說,是對一類資料,根據某一個或者幾個欄位(Field),進行條件分組,也就說在這種分組情況下,每個Node的資料都是不全的,需要將多個Node合併在一起,才會形成完整的資料集。這兩種分組都需要支援的。

  5. 對於BuildIndex和Query,當系統分成多個Group的時候,肯定要有一個Router的概念,即一個BuildIndex或者Query服務來的時候,得找到相應的Group(應該是Group下的Node),因為Lucene中的Document和Term特性,應該需要設計一個類似於資料庫中的Table模型,一個Group負責處理多個Table。在BuildIndex和Query請求裡,1. 必須帶有Table的準確定義,比如User,Item等。
    按照前面的思考,Group是肯定應該存在的,但是每個Group否需要一個MasterNode呢?

  6. 當一個Query請求定義清楚後,會以路由的方式找到一個Group,如果資料量不大的話,一個Group中的Node應該是資料對等的,那麼請求落到任何一個Node上都可以得到相應的結果。如果資料量很大,出現Sharding,就分兩種情況,一種是Query中的條件,能夠符合Sharding的定義條件,那麼落到任何一個Node上以後,通過轉發的方式,總是可以拿到請求,應該有兩種實現方式,一是請求發到某個Node上以後,由Node分析後,將可以導向的Node返回,由請求方再次將指定的Node傳送請求,二是任意Node直接向可以導向的Node轉發請求,並拿到結果後返回給請求方,第二種對客戶端友好,但如果資料量大的話,可能不太合適。還有一種情況就是,如果Query中的條件不能夠符合Sharding定義,那麼就出現類似於資料庫查詢的FullScan,由收到的Node將請求轉發給相應的Node,構成全量搜尋,然後由該Node合併後,返回。如果這樣看,最好的方式還是Node統一處理,對請求方更友好一些,也更一致。

  7. 當BuildIndex的時候,必然是發給一個Node,由其完成Index後,再同步給其它Node,此時同步,是有一個MasterNode還是沒有好呢?感覺設計一個MasterNode可能使得邏輯更簡單。即大的Group裡,MasterNode主要負責協作和BuildIndex同步,而Query則可以儘可能地落到DataNode側。

  8. 雖然有了MasterNode,但仍然是可以將BuildIndex請求發給DataNode,由DataNode轉發給MasterNode,這樣會更加簡單和友好。

  9. 考慮到BuildIndex和Query會有不同步的情況,那麼怎麼減少這種不一致性呢?如果由MasterNode或者指定的一個DataNode進行BuildIndex的時候,對其它Node的Query都會產生資料不一致性問題。假設由MasterNode給其它DataNode全部上鎖,此時查詢效能急速下降,這種方法不是非常建議,容易形成堵塞,不過如果資料很少更新,而且對資料一致性有較高要求,也可以支援,那裡可能得在這個地方允許使用者配置一致性優先還是效能優先了。如果是後者的話,按照我對Lucene的瞭解,此時每個DataNode最好有一個DiskStore和一個MemoryStore,查詢時將兩者合併查詢,這樣在保證高效能的情況下可以減少不一致性。或者更靈活一點,允許在BuildIndex的時候允許指定是否加鎖,但這樣可能會增加複雜度,需要再思考一下。

  10. 同樣是資料不一致問題,除了上面的內容以外,還需要使用Log,這樣MasterNode先記錄Log,然後進行Index,同時分發給DataNode,DataNode也是先記錄Log,這樣一旦出現問題,可以隨時在啟動時從Log處Redo。

  11. 維護和管理功能:動態擴容,Reindex(擴容時肯定要用到),啟動時先與多個DataNode同步Log,再根據Log進行Redo,保證資料的一致性。

  12. 外掛化設計沒什麼難點,不管是類似於OSGi,還是說直接寫一個Plugin的介面,然後加一個PluginManager都可以解決問題。但關鍵是Plugin需要在哪些情況下呼叫,以便讓開發者可以更多的加入自己的定製。我猜可能有以下幾個點:網路請求的Before和After處理(比如支援不同的資料模型,不同的安全檢查等,記錄日誌,流量控制等),啟動後的After處理(比如對Log進行Check,以便Redo),BuildIndex和Query的Before和After處理(其實就可以通過這個擴充套件來處理資料同步的問題)。

  13. 上面說的外掛化設計並不難,但是否使用統一的Plugin介面,還是分開,需要考慮一下,畢竟可以提供擴充套件點的地方太多了。如果是我設計,大概是三大級繼承,最頂層的有一個Plugin或者Extension的介面,提供Name,Desription,Dependecy等內容的定義,這個和Equinox都類似,其實不帶任何業務支援的,第二層是業務級別的,比如說網路請求的,日誌處理的,第三層就是具體實現了。再多就有點複雜了,有一個最頂層介面的好處是,在Eclipse裡,查下繼承關係,就得到所有實現了,方便分析程式碼,如果只設計二和三層,哈哈,就有得找了。

基於以上分析,可以列出來幾個基本的元素和服務:

  1. Node+Group+MasterNode+DataNode

  2. Table+Field+Key+Condition

  3. BuildIndex+Query

  4. Log

  5. Plugin

下面是大致的架構域圖:

ElasticSearch架構反向思路

還有幾個難點,需要再考慮一下:

  1. Query可能會有Paging的需要,那麼一旦出現Sharding的話,需要將多個DataNode的結果Merge後,進行Sort,再計算Paging後返回。這個對效能的要求比較高,特別是當頁面翻到幾十頁的時候,效能損失非常大,如何處理?還是說技術層面上不做解決,直接讓業務方來自行規劃。

  2. 因為ElasticSearch是基於Lucene的,而Lucene並不提供事務操作,比如先行鎖再Update,因此一旦出現衝突時,因為網路延時等原因,有可能後面的資料覆蓋前面的資料,這種情況怎麼考慮,是加一個時間版本號還是忽略這種情況?

  3. 另外ElasticSearch對資料一致性不可能提供太好的解決方案,因此最好還是將一些非核心業務資料進行查詢,比如日誌,就不會出現修改,再比如電商中的商品表,修改相對並不頻繁,但如果商品表裡包含商品數量,那麼就掛了,所有必須減少將頻繁更新的資料放入搜尋。

  4. 有點記不清楚Lucene的儲存機制了,是否支援類似於資料庫的Update語句,只更新部分資料。如果不支援,那麼ElasticSearch是否需要支援呢?如果是我,應該不會支援,做太多的事情更容易出錯。

  5. 當MasterNode當掉,顯然可以通過選舉或者別的方法找到一個新的MasterNode,但如果一個MasterNode或者DataNode收到一個BuildIndex請求後,再當掉,最好是通知Client失敗,由Client發起重試。由於所有BuildIndex請求都是發給MasterNode來處理的,那麼就相對簡單了,如果MasterNode失敗後重新加入Group,由於此時它不再是Master,就可以丟棄這個日誌,保證資料一致性。這塊的細節會比較多,記錄Log,然後如何Redo,如何Sync,如何拋棄,都需要深入分析。不在這裡折騰了。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31473948/viewspace-2168506/,如需轉載,請註明出處,否則將追究法律責任。

相關文章