您好,登錄后才能下訂單哦!
作者:豬兒笨笨 來源:云棲社區(qū)
原文鏈接: https://yq.aliyun.com/articles/617479?spm=a2c4e.11153940.bloghomeflow.526.162d291aRoS8gf
我曾經(jīng)在多個(gè)場(chǎng)合說過,我分析一個(gè)系統(tǒng)的設(shè)計(jì)思路,往往不是一開始就去看看這個(gè)系統(tǒng)的設(shè)計(jì)文檔或者源代碼,而是去看系統(tǒng)的基本介紹,特別是框架類的功能詳細(xì)介紹,然后根據(jù)介紹可以大概了解這樣一個(gè)系統(tǒng)用來解決什么問題,有哪些特色,然后基于自己對(duì)這些問題的想法,根據(jù)自己的經(jīng)驗(yàn)來同樣設(shè)計(jì)一個(gè)系統(tǒng),看包含哪些內(nèi)容,使用哪些架構(gòu)模式和思路,然后帶著自己設(shè)計(jì)的東西再去看另一個(gè)系統(tǒng)的設(shè)計(jì)思路,可能再更加清楚,也會(huì)反思自己的設(shè)計(jì)是否哪些地方存在問題,可以加以改進(jìn)。
最近正好準(zhǔn)備玩ElasticSearch,本來在2013年就想玩這個(gè),但由于工作原因耽誤了,現(xiàn)在又翻出來看看有什么好玩的,下面就詳細(xì)地記錄了我對(duì)ElasticSearch的反向架構(gòu)思考。順便補(bǔ)充一句,目前用來研究的ElasticSearch的版本號(hào)是6.3
先來看看一份對(duì)ElasticSearch比較典型的介紹:
Elasticsearch是一個(gè)基于Apache Lucene(TM)的開源搜索引擎。無論在開源還是專有領(lǐng)域,Lucene可以被認(rèn)為是迄今為止最先進(jìn)、性能最好的、功能最全的搜索引擎庫。 但是,Lucene只是一個(gè)庫。想要使用它,你必須使用Java來作為開發(fā)語言并將其直接集成到你的應(yīng)用中,更糟糕的是,Lucene非常復(fù)雜,你需要深入了解檢索的相關(guān)知識(shí)來理解它是如何工作的。 Elasticsearch也使用Java開發(fā)并使用Lucene作為其核心來實(shí)現(xiàn)所有索引和搜索的功能,但是它的目的是通過簡(jiǎn)單的RESTful API來隱藏Lucene的復(fù)雜性,從而讓全文搜索變得簡(jiǎn)單。 不過,Elasticsearch不僅僅是Lucene和全文搜索,我們還能這樣去描述它: 分布式的實(shí)時(shí)文件存儲(chǔ),每個(gè)字段都被索引并可被搜索 分布式的實(shí)時(shí)分析搜索引擎 可以擴(kuò)展到上百臺(tái)服務(wù)器,處理PB級(jí)結(jié)構(gòu)化或非結(jié)構(gòu)化數(shù)據(jù) 而且,所有的這些功能被集成到一個(gè)服務(wù)里面,你的應(yīng)用可以通過簡(jiǎn)單的RESTful API、各種語言的客戶端甚至命令行與之交互。 上手Elasticsearch非常容易。它提供了許多合理的缺省值,并對(duì)初學(xué)者隱藏了復(fù)雜的搜索引擎理論。它開箱即用(安裝即可使用),只需很少的學(xué)習(xí)既可在生產(chǎn)環(huán)境中使用。 隨著你對(duì)Elasticsearch的理解加深,你可以根據(jù)不同的問題領(lǐng)域定制Elasticsearch的高級(jí)特性,這一切都是可配置的,并且配置非常靈活。
幸虧以前使用過Lucene做IDE底層項(xiàng)目模型關(guān)系的管理,對(duì)Lucene還算比較熟悉,否則還得先去看看Lucene的功能和用法。
從上面的介紹可以看出幾個(gè)關(guān)鍵內(nèi)容:
Lucene在做索引的時(shí)候本身就有存儲(chǔ)功能,所以存儲(chǔ)這個(gè)東西是天然就有的,反而不用花時(shí)間考慮。
性能是一個(gè)比較關(guān)鍵的東西,特別是要做實(shí)時(shí)引擎,怎么保證高性能。
ElasticSearch是一個(gè)分布式的系統(tǒng),那么必然存在多結(jié)點(diǎn)通訊,協(xié)作等問題,比如使用ZooKeeper之類的系統(tǒng)進(jìn)行注冊(cè)和協(xié)同,當(dāng)然也保不齊他自己玩一套。
既然是分布式系統(tǒng),那么數(shù)據(jù)存儲(chǔ)就不可能完全單機(jī)化,也就是存在Sharding的情況,如何Sharding,如何同步,在查找結(jié)果的時(shí)候,如何聚合。
分布式系統(tǒng),只要涉及到數(shù)據(jù)更新,必然存在數(shù)據(jù)不一致問題,怎么解決。
由于索引本身原因,一旦出現(xiàn)Sharding,就很難做聯(lián)合的查詢,這個(gè)應(yīng)該不能實(shí)現(xiàn)的,至少說不可能很簡(jiǎn)單得實(shí)現(xiàn)。
有一個(gè)網(wǎng)絡(luò)層或者說對(duì)外服務(wù)接口層,用來進(jìn)行交互,看介紹,支持多種協(xié)議,比如Client直接調(diào)用,或者是Restful風(fēng)格。
參考服務(wù)接口層,還允許很多地方進(jìn)行配置,那么很顯然,應(yīng)該是使用了類似于插件的技術(shù)來支持很多功能。
我的習(xí)慣是從使用者角度來倒推系統(tǒng)架構(gòu)
對(duì)外服務(wù),稱為Interface,這個(gè)其實(shí)還相對(duì)簡(jiǎn)單,應(yīng)該提供兩個(gè)基本功能,即BuildIndex(不一定要區(qū)分Create和Update,但Delete肯定要有)和Query(應(yīng)該基于主Key和Condition兩種查詢),把這兩個(gè)基本接口設(shè)計(jì)好,然后在上面加不同的封裝或者通過Netty之類網(wǎng)絡(luò)架構(gòu)提供Rest服務(wù),也可能基于Stub類似的機(jī)制提供RPC調(diào)用。
查詢功能,是采用SQL還是Query模型的方式,我更傾向于后者,因?yàn)殛P(guān)聯(lián)查詢等很多功能是無法提供的,SQL校驗(yàn)會(huì)是比較麻煩的事情。
不管是BuildIndex還是Query,肯定要找到一臺(tái)機(jī)器或者多臺(tái)機(jī)器進(jìn)行處理,由于這是一個(gè)分布式系統(tǒng),而且還支持Sharding,那么可以肯定,需要分組,即Group,一個(gè)Group中包括若干個(gè)Node,用來支持服務(wù)。
怎么分組,正常可能是分兩級(jí),一種是基于模型定義的,比如對(duì)于某一些數(shù)據(jù),象商品,用戶這些數(shù)據(jù)可能分成一類數(shù)據(jù)對(duì)應(yīng)一個(gè)Group來處理,這種處理比較直觀,也簡(jiǎn)單。也就是說每一類模型會(huì)對(duì)應(yīng)一個(gè)Group,而一個(gè)Group可能對(duì)著多個(gè)模型,特別是數(shù)據(jù)相對(duì)較少的時(shí)候。還有一種就是Sharding,通常來說,是對(duì)一類數(shù)據(jù),根據(jù)某一個(gè)或者幾個(gè)字段(Field),進(jìn)行條件分組,也就說在這種分組情況下,每個(gè)Node的數(shù)據(jù)都是不全的,需要將多個(gè)Node合并在一起,才會(huì)形成完整的數(shù)據(jù)集。這兩種分組都需要支持的。
對(duì)于BuildIndex和Query,當(dāng)系統(tǒng)分成多個(gè)Group的時(shí)候,肯定要有一個(gè)Router的概念,即一個(gè)BuildIndex或者Query服務(wù)來的時(shí)候,得找到相應(yīng)的Group(應(yīng)該是Group下的Node),因?yàn)長(zhǎng)ucene中的Document和Term特性,應(yīng)該需要設(shè)計(jì)一個(gè)類似于數(shù)據(jù)庫中的Table模型,一個(gè)Group負(fù)責(zé)處理多個(gè)Table。在BuildIndex和Query請(qǐng)求里,1. 必須帶有Table的準(zhǔn)確定義,比如User,Item等。
按照前面的思考,Group是肯定應(yīng)該存在的,但是每個(gè)Group否需要一個(gè)MasterNode呢?
當(dāng)一個(gè)Query請(qǐng)求定義清楚后,會(huì)以路由的方式找到一個(gè)Group,如果數(shù)據(jù)量不大的話,一個(gè)Group中的Node應(yīng)該是數(shù)據(jù)對(duì)等的,那么請(qǐng)求落到任何一個(gè)Node上都可以得到相應(yīng)的結(jié)果。如果數(shù)據(jù)量很大,出現(xiàn)Sharding,就分兩種情況,一種是Query中的條件,能夠符合Sharding的定義條件,那么落到任何一個(gè)Node上以后,通過轉(zhuǎn)發(fā)的方式,總是可以拿到請(qǐng)求,應(yīng)該有兩種實(shí)現(xiàn)方式,一是請(qǐng)求發(fā)到某個(gè)Node上以后,由Node分析后,將可以導(dǎo)向的Node返回,由請(qǐng)求方再次將指定的Node發(fā)送請(qǐng)求,二是任意Node直接向可以導(dǎo)向的Node轉(zhuǎn)發(fā)請(qǐng)求,并拿到結(jié)果后返回給請(qǐng)求方,第二種對(duì)客戶端友好,但如果數(shù)據(jù)量大的話,可能不太合適。還有一種情況就是,如果Query中的條件不能夠符合Sharding定義,那么就出現(xiàn)類似于數(shù)據(jù)庫查詢的FullScan,由收到的Node將請(qǐng)求轉(zhuǎn)發(fā)給相應(yīng)的Node,構(gòu)成全量搜索,然后由該Node合并后,返回。如果這樣看,最好的方式還是Node統(tǒng)一處理,對(duì)請(qǐng)求方更友好一些,也更一致。
當(dāng)BuildIndex的時(shí)候,必然是發(fā)給一個(gè)Node,由其完成Index后,再同步給其它Node,此時(shí)同步,是有一個(gè)MasterNode還是沒有好呢?感覺設(shè)計(jì)一個(gè)MasterNode可能使得邏輯更簡(jiǎn)單。即大的Group里,MasterNode主要負(fù)責(zé)協(xié)作和BuildIndex同步,而Query則可以盡可能地落到DataNode側(cè)。
雖然有了MasterNode,但仍然是可以將BuildIndex請(qǐng)求發(fā)給DataNode,由DataNode轉(zhuǎn)發(fā)給MasterNode,這樣會(huì)更加簡(jiǎn)單和友好。
考慮到BuildIndex和Query會(huì)有不同步的情況,那么怎么減少這種不一致性呢?如果由MasterNode或者指定的一個(gè)DataNode進(jìn)行BuildIndex的時(shí)候,對(duì)其它Node的Query都會(huì)產(chǎn)生數(shù)據(jù)不一致性問題。假設(shè)由MasterNode給其它DataNode全部上鎖,此時(shí)查詢性能急速下降,這種方法不是非常建議,容易形成堵塞,不過如果數(shù)據(jù)很少更新,而且對(duì)數(shù)據(jù)一致性有較高要求,也可以支持,那里可能得在這個(gè)地方允許用戶配置一致性優(yōu)先還是性能優(yōu)先了。如果是后者的話,按照我對(duì)Lucene的了解,此時(shí)每個(gè)DataNode最好有一個(gè)DiskStore和一個(gè)MemoryStore,查詢時(shí)將兩者合并查詢,這樣在保證高性能的情況下可以減少不一致性?;蛘吒`活一點(diǎn),允許在BuildIndex的時(shí)候允許指定是否加鎖,但這樣可能會(huì)增加復(fù)雜度,需要再思考一下。
同樣是數(shù)據(jù)不一致問題,除了上面的內(nèi)容以外,還需要使用Log,這樣MasterNode先記錄Log,然后進(jìn)行Index,同時(shí)分發(fā)給DataNode,DataNode也是先記錄Log,這樣一旦出現(xiàn)問題,可以隨時(shí)在啟動(dòng)時(shí)從Log處Redo。
維護(hù)和管理功能:動(dòng)態(tài)擴(kuò)容,Reindex(擴(kuò)容時(shí)肯定要用到),啟動(dòng)時(shí)先與多個(gè)DataNode同步Log,再根據(jù)Log進(jìn)行Redo,保證數(shù)據(jù)的一致性。
插件化設(shè)計(jì)沒什么難點(diǎn),不管是類似于OSGi,還是說直接寫一個(gè)Plugin的接口,然后加一個(gè)PluginManager都可以解決問題。但關(guān)鍵是Plugin需要在哪些情況下調(diào)用,以便讓開發(fā)者可以更多的加入自己的定制。我猜可能有以下幾個(gè)點(diǎn):網(wǎng)絡(luò)請(qǐng)求的Before和After處理(比如支持不同的數(shù)據(jù)模型,不同的安全檢查等,記錄日志,流量控制等),啟動(dòng)后的After處理(比如對(duì)Log進(jìn)行Check,以便Redo),BuildIndex和Query的Before和After處理(其實(shí)就可以通過這個(gè)擴(kuò)展來處理數(shù)據(jù)同步的問題)。
上面說的插件化設(shè)計(jì)并不難,但是否使用統(tǒng)一的Plugin接口,還是分開,需要考慮一下,畢竟可以提供擴(kuò)展點(diǎn)的地方太多了。如果是我設(shè)計(jì),大概是三大級(jí)繼承,最頂層的有一個(gè)Plugin或者Extension的接口,提供Name,Desription,Dependecy等內(nèi)容的定義,這個(gè)和Equinox都類似,其實(shí)不帶任何業(yè)務(wù)支持的,第二層是業(yè)務(wù)級(jí)別的,比如說網(wǎng)絡(luò)請(qǐng)求的,日志處理的,第三層就是具體實(shí)現(xiàn)了。再多就有點(diǎn)復(fù)雜了,有一個(gè)最頂層接口的好處是,在Eclipse里,查下繼承關(guān)系,就得到所有實(shí)現(xiàn)了,方便分析代碼,如果只設(shè)計(jì)二和三層,哈哈,就有得找了。
基于以上分析,可以列出來幾個(gè)基本的元素和服務(wù):
Node+Group+MasterNode+DataNode
Table+Field+Key+Condition
BuildIndex+Query
Log
Plugin
下面是大致的架構(gòu)域圖:
還有幾個(gè)難點(diǎn),需要再考慮一下:
Query可能會(huì)有Paging的需要,那么一旦出現(xiàn)Sharding的話,需要將多個(gè)DataNode的結(jié)果Merge后,進(jìn)行Sort,再計(jì)算Paging后返回。這個(gè)對(duì)性能的要求比較高,特別是當(dāng)頁面翻到幾十頁的時(shí)候,性能損失非常大,如何處理?還是說技術(shù)層面上不做解決,直接讓業(yè)務(wù)方來自行規(guī)劃。
因?yàn)镋lasticSearch是基于Lucene的,而Lucene并不提供事務(wù)操作,比如先行鎖再Update,因此一旦出現(xiàn)沖突時(shí),因?yàn)榫W(wǎng)絡(luò)延時(shí)等原因,有可能后面的數(shù)據(jù)覆蓋前面的數(shù)據(jù),這種情況怎么考慮,是加一個(gè)時(shí)間版本號(hào)還是忽略這種情況?
另外ElasticSearch對(duì)數(shù)據(jù)一致性不可能提供太好的解決方案,因此最好還是將一些非核心業(yè)務(wù)數(shù)據(jù)進(jìn)行查詢,比如日志,就不會(huì)出現(xiàn)修改,再比如電商中的商品表,修改相對(duì)并不頻繁,但如果商品表里包含商品數(shù)量,那么就掛了,所有必須減少將頻繁更新的數(shù)據(jù)放入搜索。
有點(diǎn)記不清楚Lucene的存儲(chǔ)機(jī)制了,是否支持類似于數(shù)據(jù)庫的Update語句,只更新部分?jǐn)?shù)據(jù)。如果不支持,那么ElasticSearch是否需要支持呢?如果是我,應(yīng)該不會(huì)支持,做太多的事情更容易出錯(cuò)。
當(dāng)MasterNode當(dāng)?shù)?,顯然可以通過選舉或者別的方法找到一個(gè)新的MasterNode,但如果一個(gè)MasterNode或者DataNode收到一個(gè)BuildIndex請(qǐng)求后,再當(dāng)?shù)?,最好是通知Client失敗,由Client發(fā)起重試。由于所有BuildIndex請(qǐng)求都是發(fā)給MasterNode來處理的,那么就相對(duì)簡(jiǎn)單了,如果MasterNode失敗后重新加入Group,由于此時(shí)它不再是Master,就可以丟棄這個(gè)日志,保證數(shù)據(jù)一致性。這塊的細(xì)節(jié)會(huì)比較多,記錄Log,然后如何Redo,如何Sync,如何拋棄,都需要深入分析。不在這里折騰了。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。