十年網(wǎng)站開(kāi)發(fā)經(jīng)驗(yàn) + 多家企業(yè)客戶(hù) + 靠譜的建站團(tuán)隊(duì)
量身定制 + 運(yùn)營(yíng)維護(hù)+專(zhuān)業(yè)推廣+無(wú)憂(yōu)售后,網(wǎng)站問(wèn)題一站解決
作者:豬兒笨笨 來(lái)源:云棲社區(qū)
公司主營(yíng)業(yè)務(wù):網(wǎng)站設(shè)計(jì)制作、網(wǎng)站建設(shè)、移動(dòng)網(wǎng)站開(kāi)發(fā)等業(yè)務(wù)。幫助企業(yè)客戶(hù)真正實(shí)現(xiàn)互聯(lián)網(wǎng)宣傳,提高企業(yè)的競(jìng)爭(zhēng)能力。成都創(chuàng)新互聯(lián)是一支青春激揚(yáng)、勤奮敬業(yè)、活力青春激揚(yáng)、勤奮敬業(yè)、活力澎湃、和諧高效的團(tuán)隊(duì)。公司秉承以“開(kāi)放、自由、嚴(yán)謹(jǐn)、自律”為核心的企業(yè)文化,感謝他們對(duì)我們的高要求,感謝他們從不同領(lǐng)域給我們帶來(lái)的挑戰(zhàn),讓我們激情的團(tuán)隊(duì)有機(jī)會(huì)用頭腦與智慧不斷的給客戶(hù)帶來(lái)驚喜。成都創(chuàng)新互聯(lián)推出靈川免費(fèi)做網(wǎng)站回饋大家。
原文鏈接: https://yq.aliyun.com/articles/617479?spm=a2c4e.11153940.bloghomeflow.526.162d291aRoS8gf
我曾經(jīng)在多個(gè)場(chǎng)合說(shuō)過(guò),我分析一個(gè)系統(tǒng)的設(shè)計(jì)思路,往往不是一開(kāi)始就去看看這個(gè)系統(tǒng)的設(shè)計(jì)文檔或者源代碼,而是去看系統(tǒng)的基本介紹,特別是框架類(lèi)的功能詳細(xì)介紹,然后根據(jù)介紹可以大概了解這樣一個(gè)系統(tǒng)用來(lái)解決什么問(wèn)題,有哪些特色,然后基于自己對(duì)這些問(wèn)題的想法,根據(jù)自己的經(jīng)驗(yàn)來(lái)同樣設(shè)計(jì)一個(gè)系統(tǒng),看包含哪些內(nèi)容,使用哪些架構(gòu)模式和思路,然后帶著自己設(shè)計(jì)的東西再去看另一個(gè)系統(tǒng)的設(shè)計(jì)思路,可能再更加清楚,也會(huì)反思自己的設(shè)計(jì)是否哪些地方存在問(wèn)題,可以加以改進(jìn)。
最近正好準(zhǔn)備玩ElasticSearch,本來(lái)在2013年就想玩這個(gè),但由于工作原因耽誤了,現(xiàn)在又翻出來(lái)看看有什么好玩的,下面就詳細(xì)地記錄了我對(duì)ElasticSearch的反向架構(gòu)思考。順便補(bǔ)充一句,目前用來(lái)研究的ElasticSearch的版本號(hào)是6.3
先來(lái)看看一份對(duì)ElasticSearch比較典型的介紹:
Elasticsearch是一個(gè)基于Apache Lucene(TM)的開(kāi)源搜索引擎。無(wú)論在開(kāi)源還是專(zhuān)有領(lǐng)域,Lucene可以被認(rèn)為是迄今為止最先進(jìn)、性能最好的、功能最全的搜索引擎庫(kù)。 但是,Lucene只是一個(gè)庫(kù)。想要使用它,你必須使用Java來(lái)作為開(kāi)發(fā)語(yǔ)言并將其直接集成到你的應(yīng)用中,更糟糕的是,Lucene非常復(fù)雜,你需要深入了解檢索的相關(guān)知識(shí)來(lái)理解它是如何工作的。 Elasticsearch也使用Java開(kāi)發(fā)并使用Lucene作為其核心來(lái)實(shí)現(xiàn)所有索引和搜索的功能,但是它的目的是通過(guò)簡(jiǎn)單的RESTful API來(lái)隱藏Lucene的復(fù)雜性,從而讓全文搜索變得簡(jiǎn)單。 不過(guò),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)用可以通過(guò)簡(jiǎn)單的RESTful API、各種語(yǔ)言的客戶(hù)端甚至命令行與之交互。 上手Elasticsearch非常容易。它提供了許多合理的缺省值,并對(duì)初學(xué)者隱藏了復(fù)雜的搜索引擎理論。它開(kāi)箱即用(安裝即可使用),只需很少的學(xué)習(xí)既可在生產(chǎn)環(huán)境中使用。 隨著你對(duì)Elasticsearch的理解加深,你可以根據(jù)不同的問(wèn)題領(lǐng)域定制Elasticsearch的高級(jí)特性,這一切都是可配置的,并且配置非常靈活。
幸虧以前使用過(guò)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é)作等問(wèn)題,比如使用ZooKeeper之類(lèi)的系統(tǒng)進(jìn)行注冊(cè)和協(xié)同,當(dāng)然也保不齊他自己玩一套。
既然是分布式系統(tǒng),那么數(shù)據(jù)存儲(chǔ)就不可能完全單機(jī)化,也就是存在Sharding的情況,如何Sharding,如何同步,在查找結(jié)果的時(shí)候,如何聚合。
分布式系統(tǒng),只要涉及到數(shù)據(jù)更新,必然存在數(shù)據(jù)不一致問(wèn)題,怎么解決。
由于索引本身原因,一旦出現(xiàn)Sharding,就很難做聯(lián)合的查詢(xún),這個(gè)應(yīng)該不能實(shí)現(xiàn)的,至少說(shuō)不可能很簡(jiǎn)單得實(shí)現(xiàn)。
有一個(gè)網(wǎng)絡(luò)層或者說(shuō)對(duì)外服務(wù)接口層,用來(lái)進(jìn)行交互,看介紹,支持多種協(xié)議,比如Client直接調(diào)用,或者是Restful風(fēng)格。
參考服務(wù)接口層,還允許很多地方進(jìn)行配置,那么很顯然,應(yīng)該是使用了類(lèi)似于插件的技術(shù)來(lái)支持很多功能。
我的習(xí)慣是從使用者角度來(lái)倒推系統(tǒng)架構(gòu)
對(duì)外服務(wù),稱(chēng)為Interface,這個(gè)其實(shí)還相對(duì)簡(jiǎn)單,應(yīng)該提供兩個(gè)基本功能,即BuildIndex(不一定要區(qū)分Create和Update,但Delete肯定要有)和Query(應(yīng)該基于主Key和Condition兩種查詢(xún)),把這兩個(gè)基本接口設(shè)計(jì)好,然后在上面加不同的封裝或者通過(guò)Netty之類(lèi)網(wǎng)絡(luò)架構(gòu)提供Rest服務(wù),也可能基于Stub類(lèi)似的機(jī)制提供RPC調(diào)用。
查詢(xún)功能,是采用SQL還是Query模型的方式,我更傾向于后者,因?yàn)殛P(guān)聯(lián)查詢(xún)等很多功能是無(wú)法提供的,SQL校驗(yàn)會(huì)是比較麻煩的事情。
不管是BuildIndex還是Query,肯定要找到一臺(tái)機(jī)器或者多臺(tái)機(jī)器進(jìn)行處理,由于這是一個(gè)分布式系統(tǒng),而且還支持Sharding,那么可以肯定,需要分組,即Group,一個(gè)Group中包括若干個(gè)Node,用來(lái)支持服務(wù)。
怎么分組,正??赡苁欠謨杉?jí),一種是基于模型定義的,比如對(duì)于某一些數(shù)據(jù),象商品,用戶(hù)這些數(shù)據(jù)可能分成一類(lèi)數(shù)據(jù)對(duì)應(yīng)一個(gè)Group來(lái)處理,這種處理比較直觀(guān),也簡(jiǎn)單。也就是說(shuō)每一類(lèi)模型會(huì)對(duì)應(yīng)一個(gè)Group,而一個(gè)Group可能對(duì)著多個(gè)模型,特別是數(shù)據(jù)相對(duì)較少的時(shí)候。還有一種就是Sharding,通常來(lái)說(shuō),是對(duì)一類(lèi)數(shù)據(jù),根據(jù)某一個(gè)或者幾個(gè)字段(Field),進(jìn)行條件分組,也就說(shuō)在這種分組情況下,每個(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ù)來(lái)的時(shí)候,得找到相應(yīng)的Group(應(yīng)該是Group下的Node),因?yàn)長(zhǎng)ucene中的Document和Term特性,應(yīng)該需要設(shè)計(jì)一個(gè)類(lèi)似于數(shù)據(jù)庫(kù)中的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ù)量不大的話(huà),一個(gè)Group中的Node應(yīng)該是數(shù)據(jù)對(duì)等的,那么請(qǐng)求落到任何一個(gè)Node上都可以得到相應(yīng)的結(jié)果。如果數(shù)據(jù)量很大,出現(xiàn)Sharding,就分兩種情況,一種是Query中的條件,能夠符合Sharding的定義條件,那么落到任何一個(gè)Node上以后,通過(guò)轉(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ì)客戶(hù)端友好,但如果數(shù)據(jù)量大的話(huà),可能不太合適。還有一種情況就是,如果Query中的條件不能夠符合Sharding定義,那么就出現(xiàn)類(lèi)似于數(shù)據(jù)庫(kù)查詢(xún)的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還是沒(méi)有好呢?感覺(jué)設(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ù)不一致性問(wèn)題。假設(shè)由MasterNode給其它DataNode全部上鎖,此時(shí)查詢(xún)性能急速下降,這種方法不是非常建議,容易形成堵塞,不過(guò)如果數(shù)據(jù)很少更新,而且對(duì)數(shù)據(jù)一致性有較高要求,也可以支持,那里可能得在這個(gè)地方允許用戶(hù)配置一致性?xún)?yōu)先還是性能優(yōu)先了。如果是后者的話(huà),按照我對(duì)Lucene的了解,此時(shí)每個(gè)DataNode最好有一個(gè)DiskStore和一個(gè)MemoryStore,查詢(xún)時(shí)將兩者合并查詢(xún),這樣在保證高性能的情況下可以減少不一致性。或者更靈活一點(diǎn),允許在BuildIndex的時(shí)候允許指定是否加鎖,但這樣可能會(huì)增加復(fù)雜度,需要再思考一下。
同樣是數(shù)據(jù)不一致問(wèn)題,除了上面的內(nèi)容以外,還需要使用Log,這樣MasterNode先記錄Log,然后進(jìn)行Index,同時(shí)分發(fā)給DataNode,DataNode也是先記錄Log,這樣一旦出現(xiàn)問(wè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ì)沒(méi)什么難點(diǎn),不管是類(lèi)似于OSGi,還是說(shuō)直接寫(xiě)一個(gè)Plugin的接口,然后加一個(gè)PluginManager都可以解決問(wèn)題。但關(guān)鍵是Plugin需要在哪些情況下調(diào)用,以便讓開(kāi)發(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í)就可以通過(guò)這個(gè)擴(kuò)展來(lái)處理數(shù)據(jù)同步的問(wèn)題)。
上面說(shuō)的插件化設(shè)計(jì)并不難,但是否使用統(tǒng)一的Plugin接口,還是分開(kāi),需要考慮一下,畢竟可以提供擴(kuò)展點(diǎn)的地方太多了。如果是我設(shè)計(jì),大概是三大級(jí)繼承,最頂層的有一個(gè)Plugin或者Extension的接口,提供Name,Desription,Dependecy等內(nèi)容的定義,這個(gè)和Equinox都類(lèi)似,其實(shí)不帶任何業(yè)務(wù)支持的,第二層是業(yè)務(wù)級(jí)別的,比如說(shuō)網(wǎng)絡(luò)請(qǐng)求的,日志處理的,第三層就是具體實(shí)現(xiàn)了。再多就有點(diǎn)復(fù)雜了,有一個(gè)最頂層接口的好處是,在Eclipse里,查下繼承關(guān)系,就得到所有實(shí)現(xiàn)了,方便分析代碼,如果只設(shè)計(jì)二和三層,哈哈,就有得找了。
基于以上分析,可以列出來(lái)幾個(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的話(huà),需要將多個(gè)DataNode的結(jié)果Merge后,進(jìn)行Sort,再計(jì)算Paging后返回。這個(gè)對(duì)性能的要求比較高,特別是當(dāng)頁(yè)面翻到幾十頁(yè)的時(shí)候,性能損失非常大,如何處理?還是說(shuō)技術(shù)層面上不做解決,直接讓業(yè)務(wù)方來(lái)自行規(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)行查詢(xún),比如日志,就不會(huì)出現(xiàn)修改,再比如電商中的商品表,修改相對(duì)并不頻繁,但如果商品表里包含商品數(shù)量,那么就掛了,所有必須減少將頻繁更新的數(shù)據(jù)放入搜索。
有點(diǎn)記不清楚Lucene的存儲(chǔ)機(jī)制了,是否支持類(lèi)似于數(shù)據(jù)庫(kù)的Update語(yǔ)句,只更新部分?jǐn)?shù)據(jù)。如果不支持,那么ElasticSearch是否需要支持呢?如果是我,應(yīng)該不會(huì)支持,做太多的事情更容易出錯(cuò)。
當(dāng)MasterNode當(dāng)?shù)?,顯然可以通過(guò)選舉或者別的方法找到一個(gè)新的MasterNode,但如果一個(gè)MasterNode或者DataNode收到一個(gè)BuildIndex請(qǐng)求后,再當(dāng)?shù)簦詈檬峭ㄖ狢lient失敗,由Client發(fā)起重試。由于所有BuildIndex請(qǐng)求都是發(fā)給MasterNode來(lái)處理的,那么就相對(duì)簡(jiǎn)單了,如果MasterNode失敗后重新加入Group,由于此時(shí)它不再是Master,就可以丟棄這個(gè)日志,保證數(shù)據(jù)一致性。這塊的細(xì)節(jié)會(huì)比較多,記錄Log,然后如何Redo,如何Sync,如何拋棄,都需要深入分析。不在這里折騰了。