Otto.de:我為什么選擇分布式垂直架構(gòu)
【譯者的話】otto.de是德國(guó)的一家網(wǎng)上購(gòu)物網(wǎng)站,本篇前半部分主要介紹了幾個(gè)系統(tǒng)架構(gòu)以及它們的優(yōu)缺點(diǎn),后半部分主要講解otto.de的微服務(wù)架構(gòu)。
在我們開(kāi)始開(kāi)發(fā)otto.de網(wǎng)上商店時(shí),我們選擇了分布式垂直架構(gòu)。之前的工作經(jīng)驗(yàn)告訴我們,一體化架構(gòu)(monolithic architecture)不能夠滿足不斷增長(zhǎng)的需求。爆發(fā)式增長(zhǎng)的數(shù)據(jù),持續(xù)提高的負(fù)載和對(duì)系統(tǒng)的擴(kuò)展,所有的這些強(qiáng)迫我們?nèi)ブ匦滤伎季W(wǎng)站的架構(gòu)。
這篇文章將會(huì)描述我們的解決辦法,還有我們這么做的原因。
一體化(Monoliths)
在項(xiàng)目剛開(kāi)始的時(shí)候,團(tuán)隊(duì)通常會(huì)考慮使用什么編程語(yǔ)言和合適的架構(gòu)。當(dāng)談到服務(wù)端應(yīng)用時(shí),Java和Spring框架,Ruby on rails或者類似的框架通常會(huì)成為團(tuán)隊(duì)的選擇。
選擇了語(yǔ)言和框架后,經(jīng)過(guò)一段時(shí)間的開(kāi)發(fā),一個(gè)簡(jiǎn)單的應(yīng)用誕生了。與此同時(shí),一體式架構(gòu)(macro-architecture)毫無(wú)爭(zhēng)議的成為了團(tuán)隊(duì)的選擇。但是,這種架構(gòu)的缺點(diǎn)也漸漸地浮出了水面:
- 它導(dǎo)致了重量級(jí)微架構(gòu)(a heavyweight Micro Architecture)
- 負(fù)載均衡限制了應(yīng)用的可擴(kuò)展性
- 系統(tǒng)的可維護(hù)性受到影響,尤其是那些大型應(yīng)用
- 零停機(jī)部署(Zero downtime deployment)變得非常的困難,尤其是那些有狀態(tài)的應(yīng)用(stateful application)
- 多個(gè)團(tuán)隊(duì)開(kāi)發(fā)效率低,并且需要額外的協(xié)調(diào)
當(dāng)然,這并不是說(shuō)一個(gè)新的應(yīng)用一開(kāi)始就變得巨大而混亂。在最開(kāi)始的時(shí)候,新應(yīng)用結(jié)構(gòu)清晰,簡(jiǎn)單易懂,可擴(kuò)展性也高,它能夠很輕松的解決需求問(wèn)題。在接下來(lái)的一段時(shí)間中,越來(lái)越多的代碼被編寫。為了應(yīng)對(duì)日益增長(zhǎng)的復(fù)雜度,系統(tǒng)被分層,抽象,模塊,服務(wù)和框架被引入到系統(tǒng)中,最終變成了我們看到的樣子。
即便是中型的應(yīng)用(比如說(shuō)一個(gè)50000的Java應(yīng)用),一體化架構(gòu)也會(huì)漸漸地變得令人討厭,更不用說(shuō)那些對(duì)擴(kuò)展性要求較高的應(yīng)用了。
最終,曾經(jīng)輕量簡(jiǎn)潔的應(yīng)用將會(huì)變成下一代開(kāi)發(fā)者的噩夢(mèng)。
分而治之
問(wèn)題的關(guān)鍵在于,如何避免這種類型的開(kāi)發(fā),并且將輕量應(yīng)用好的那部分保留下來(lái)。換句話說(shuō),我們?nèi)绾文軌颢@得一個(gè)可持續(xù)發(fā)展的架構(gòu),這個(gè)架構(gòu)在多年之后依舊能夠讓開(kāi)發(fā)者保持高效開(kāi)發(fā)。
在軟件的開(kāi)發(fā)過(guò)程中,有許多關(guān)于結(jié)構(gòu)化代碼的概念:函數(shù),方法,類,庫(kù),框架等等。這些概念并不是程序運(yùn)行所必須的,發(fā)明他們的原因是為了幫助開(kāi)發(fā)者更好的理解他們的應(yīng)用。
目前軟件開(kāi)發(fā)者已經(jīng)理解了這些概念,一個(gè)問(wèn)題接踵而來(lái):為什么這些概念僅僅被應(yīng)用于一個(gè)軟件?是什么阻礙我們將應(yīng)用拆分成多個(gè)低耦合的部分?
有三件事情我們需要牢記在心:
康威定律:軟件開(kāi)發(fā)最開(kāi)始時(shí)僅有一個(gè)團(tuán)隊(duì),根據(jù)康威定律,因此會(huì)產(chǎn)生一個(gè)應(yīng)用。(譯者注:可以參考圖片理解)
初始消耗:部署應(yīng)用,并讓它運(yùn)行起來(lái)似乎是一個(gè)非常簡(jiǎn)單的任務(wù)。實(shí)際上,你需要建立并管理VCS代碼庫(kù),編譯文件,構(gòu)建管道,用于部署的程序,硬件,虛擬機(jī),日志文件,監(jiān)控軟件等等。所有的這些都需要花費(fèi)一定的時(shí)間去處理。
操作的復(fù)雜性:大型分布式系統(tǒng)比一個(gè)小型的負(fù)載均衡集群更難操作。
如果我們放手不管,由多個(gè)小型模塊組成的系統(tǒng)并不會(huì)出現(xiàn),一個(gè)巨大而混亂的系統(tǒng)將會(huì)取而代之。這時(shí)候,致命的問(wèn)題已然出現(xiàn),然而悔之晚矣。
正常情況下,一個(gè)系統(tǒng)是否需要被擴(kuò)展,是否需要處理巨大的代碼庫(kù)在初期是非常清楚的。然后當(dāng)你遇到以上提到的障礙,你要么沒(méi)嘗試解決他們,要么只能沉淪在無(wú)盡的苦果中。
在OTTO,在最開(kāi)始的時(shí)候就花費(fèi)了大量時(shí)間去建立4個(gè)跨職能的團(tuán)隊(duì),根據(jù)前面提到的康威定律,一個(gè)項(xiàng)目屬于4個(gè)團(tuán)隊(duì),最終就能產(chǎn)生一個(gè)由4模塊組成的應(yīng)用,這樣就避免了一體化應(yīng)用的誕生。
因?yàn)槲覀冎安僮鬟^(guò)大型的一體化應(yīng)用,操作的復(fù)雜性對(duì)于我們似乎是一個(gè)可以被解決的問(wèn)題---操作200個(gè)一體化應(yīng)用和操作200個(gè)更小型的系統(tǒng)沒(méi)有太大的區(qū)別。
初始消耗可以通過(guò)標(biāo)準(zhǔn)化和自動(dòng)化來(lái)克服。因?yàn)槲覀儧](méi)有提到云服務(wù),你還需要做相關(guān)的基礎(chǔ)操作來(lái)啟動(dòng)自動(dòng)化服務(wù)。雖然有些麻煩,但做過(guò)一次后,一切將自動(dòng)化,你會(huì)獲得巨大的便利。
可擴(kuò)展性
如何將一體化應(yīng)用轉(zhuǎn)變?yōu)橛啥鄠€(gè)小模塊組成的應(yīng)用?首先,讓我們仔細(xì)想想一個(gè)應(yīng)用能夠從哪些維度進(jìn)行擴(kuò)展。
縱向分解(Vertical Decomposition)
縱向分解是一個(gè)非常自然而通用的方法,以至于常常被開(kāi)發(fā)者所忽略。相比于把所有的功能集中到單一的應(yīng)用中,我們將應(yīng)用分解成了多個(gè)小模塊,它們相互獨(dú)立,互不影響。
我們可以根據(jù)業(yè)務(wù)域來(lái)分解系統(tǒng)。舉個(gè)例子來(lái)說(shuō),在otto.de我們就講網(wǎng)上商城分解成了11個(gè)不同的垂直模塊:后勤辦公室,產(chǎn)品,訂單等等。
每一個(gè)垂直模塊屬于一個(gè)單一團(tuán)隊(duì),它們有獨(dú)立的前端,后端和數(shù)據(jù)存儲(chǔ)。在模塊之間共享代碼是嚴(yán)令禁止的。當(dāng)然,在特殊的情況下,如果我們需要分享代碼,我們會(huì)建立一個(gè)開(kāi)源的項(xiàng)目來(lái)解決該問(wèn)題。
因此,每個(gè)垂直模塊是一個(gè)獨(dú)立自主的系統(tǒng),就像Stefan Tikov在Substainable Architecture中提到的那樣。
#p#
分布式計(jì)算
一個(gè)垂直模塊依舊可能成為一個(gè)相對(duì)大型的一體化應(yīng)用,因此我們需要繼續(xù)對(duì)垂直模塊進(jìn)行拆分。一種方法是將一個(gè)垂直模塊分解成更多的垂直模塊,另外一種方法是通過(guò)分布式計(jì)算將系統(tǒng)分解成多個(gè)模塊,不同的是這些模塊運(yùn)行在他們自己的進(jìn)程中,并且通過(guò)REST來(lái)傳遞信息。
在這種情況下,應(yīng)用不僅僅被垂直分解,同時(shí)還會(huì)被水平分解。這種架構(gòu)中,請(qǐng)求到達(dá)應(yīng)用后,對(duì)請(qǐng)求的處理會(huì)被分布于多個(gè)模塊中,然后每一模塊產(chǎn)生的結(jié)果匯總成一個(gè)響應(yīng),發(fā)送回請(qǐng)求者。
這些模塊并不會(huì)共享一個(gè)數(shù)據(jù)庫(kù)架構(gòu),因?yàn)檫@樣做會(huì)導(dǎo)致模塊間的緊密耦合:數(shù)據(jù)結(jié)構(gòu)的改變會(huì)使得一個(gè)模塊不能夠被獨(dú)立的部署。
分片
當(dāng)系統(tǒng)需要處理大量的數(shù)據(jù),或者當(dāng)一個(gè)分布式的應(yīng)用被操作時(shí),分片是很恰當(dāng)?shù)倪x擇。比如說(shuō),分片非常適合向全球范圍提供服務(wù)的應(yīng)用。
因?yàn)槲覀儠簳r(shí)沒(méi)有利用到分片這個(gè)概念,在這篇文章就不再詳細(xì)描述了。
負(fù)載均衡
每當(dāng)服務(wù)器承受不了巨大的負(fù)載壓力時(shí),負(fù)載均衡就會(huì)容重登場(chǎng)。通過(guò)對(duì)一個(gè)應(yīng)用拷貝多次,同時(shí)利用負(fù)載均衡器將負(fù)載分解來(lái)緩解壓力。
在負(fù)載均衡中,不同實(shí)例的應(yīng)用通常會(huì)分時(shí)使用同一個(gè)數(shù)據(jù)庫(kù),數(shù)據(jù)庫(kù)因此成為了系統(tǒng)的瓶頸,但是我們可以通過(guò)制定良好的擴(kuò)展策略來(lái)避免這個(gè)問(wèn)題。相比于關(guān)系數(shù)據(jù)庫(kù),NoSQL能夠很好的處理擴(kuò)展性的問(wèn)題,這也就是NoSQL能夠在軟件世界中占據(jù)一畝三分地的原因之一。
最大的可擴(kuò)展性
所有以上提到的辦法能夠組合在一起,能夠達(dá)到任何級(jí)別的可擴(kuò)展性。
當(dāng)你沒(méi)有相應(yīng)的需求時(shí),組合的結(jié)果會(huì)變得有點(diǎn)太復(fù)雜。幸運(yùn)地是,開(kāi)發(fā)者并不需要在一開(kāi)始的時(shí)候就制定龐大的計(jì)劃,相反,他們可以循序漸進(jìn),一步步朝著目標(biāo)架構(gòu)前進(jìn)。
舉個(gè)例子來(lái)說(shuō),在otto.de,架構(gòu)一開(kāi)始是4個(gè)垂直模塊加上負(fù)載均衡,在過(guò)去的三年里,產(chǎn)生了更多的垂直模塊。在此期間,某些模塊變得非常的巨大笨重。因此,我們引入了微服務(wù)架構(gòu),同時(shí)通過(guò)擴(kuò)展垂直模塊來(lái)建立分布式計(jì)算。
#p#
微服務(wù)(Microservices)
微服務(wù)最近變得非常的流行。微服務(wù)是一種架構(gòu)風(fēng)格,它能夠根據(jù)業(yè)務(wù)域?qū)⑾到y(tǒng)分解成多個(gè)細(xì)粒度,獨(dú)立的模塊。
在這種情況下,微服務(wù)可以是一個(gè)小的垂直模塊,或者是分布式計(jì)算機(jī)構(gòu)中的一個(gè)服務(wù)。與傳統(tǒng)方法的不同之處在于應(yīng)用的大?。阂粋€(gè)微服務(wù)僅僅實(shí)現(xiàn)了一個(gè)業(yè)務(wù)域中的幾個(gè)功能,它結(jié)構(gòu)清晰,一個(gè)開(kāi)發(fā)者能夠很輕松的掌握它。
一個(gè)微服務(wù)非常的小,因此多個(gè)微服務(wù)能夠運(yùn)行在單一的服務(wù)器上。我們對(duì)“Fat JARs”有豐富的經(jīng)驗(yàn),通常能通過(guò)執(zhí)行java –jar 來(lái)執(zhí)行它們。如果需要的話,也能開(kāi)啟一個(gè)Jetty或者類似的服務(wù)器。
為了簡(jiǎn)化不同微服務(wù)的部署和操作問(wèn)題,每一個(gè)服務(wù)器運(yùn)行在獨(dú)立的Docker容器中。
REST和微服務(wù)架構(gòu)是一個(gè)很好的組合,它適合于構(gòu)建大型的系統(tǒng)。一個(gè)微服務(wù)可以負(fù)責(zé)提供REST資源,超媒體(hypermedia)可以用來(lái)解決服務(wù)發(fā)現(xiàn)的問(wèn)題,在涉及到接口的版本控制,服務(wù)部署獨(dú)立性的情況下,媒體類型(media type)有很大的幫助。
總而言之,微服務(wù)架構(gòu)有許多的好處,比如說(shuō):
- 在微服務(wù)架構(gòu)下進(jìn)行開(kāi)發(fā)是非常有趣的:每幾周或者幾個(gè)月,你就可能開(kāi)始一個(gè)新的開(kāi)發(fā)項(xiàng)目。
- 由于微服務(wù)非常小,微服務(wù)架構(gòu)不需要重量級(jí)框架和過(guò)多的模板代碼。
- 他們能夠被獨(dú)立的部署。因此持續(xù)交付或者持續(xù)部署變得非常的簡(jiǎn)單。
- 微服務(wù)架構(gòu)能夠支持多個(gè)獨(dú)立的團(tuán)體同時(shí)開(kāi)發(fā)。
- 開(kāi)發(fā)者能夠?yàn)槊恳粋€(gè)服務(wù)選擇最恰當(dāng)?shù)拈_(kāi)發(fā)語(yǔ)言。不用擔(dān)心對(duì)項(xiàng)目產(chǎn)生影響,開(kāi)發(fā)者可以嘗試新的語(yǔ)言或者框架。但是需要注意,這并不意味這你能夠隨意行使這項(xiàng)權(quán)利。
- 因?yàn)槲⒎?wù)足夠小,只需消耗較少的資源就能將他們替換成新的項(xiàng)目。
- 這種架構(gòu)的可擴(kuò)展性相比于一體化架構(gòu)顯得非常的好,每一個(gè)服務(wù)都能被獨(dú)立的擴(kuò)展。
微服務(wù)架構(gòu)遵守敏捷開(kāi)發(fā)的原則。一個(gè)不能完全滿足用戶的新特性不僅可以被迅速的創(chuàng)建,而且還能夠被快速的銷毀。
宏架構(gòu)和微架構(gòu)(Macro- and Micro-Architecture)
在微服務(wù)架構(gòu)中,哪一部分將難以改變?內(nèi)部模塊的擴(kuò)展已經(jīng)不再是關(guān)鍵問(wèn)題,最難以改變的事情是有關(guān)微服務(wù)架構(gòu)的決定,比如說(shuō)如何將微服務(wù)整合到系統(tǒng)的方法,或者模塊間傳輸信息協(xié)議的選擇。
因此,otto.de嚴(yán)格區(qū)分了微架構(gòu)(micro-architecture)和宏架構(gòu)(macro-architecture)。微架構(gòu)都是關(guān)于垂直架構(gòu)或者微服務(wù)架構(gòu)的內(nèi)部結(jié)構(gòu),并且全部交由各自的團(tuán)隊(duì)全權(quán)處理。
但是,明確宏架構(gòu)的大體方向是有價(jià)值的:
- 垂直分解:系統(tǒng)被分割成多個(gè)垂直模塊,每一個(gè)模塊完全屬于一個(gè)特定的團(tuán)隊(duì)。模塊與模塊之間的信息傳遞禁止在用戶請(qǐng)求的過(guò)程中進(jìn)行,而必須在后臺(tái)執(zhí)行。
- RESTful架構(gòu):不同服務(wù)之間的信息傳遞和整合只通過(guò)REST來(lái)執(zhí)行。
- 零分享架構(gòu):服務(wù)間不會(huì)通過(guò)共享可變狀態(tài)(mutable state)來(lái)進(jìn)行信息交換或者分享信息。沒(méi)有HTTP sessions,沒(méi)有中央數(shù)據(jù)存儲(chǔ)中心,沒(méi)有共享代碼。但是多個(gè)服務(wù)的實(shí)例之間有可能共享一個(gè)數(shù)據(jù)庫(kù)。
- 數(shù)據(jù)管理:對(duì)于每一個(gè)數(shù)據(jù)節(jié)點(diǎn),只有一個(gè)系統(tǒng)負(fù)責(zé)管理。其他的系統(tǒng)只能通過(guò)REST API讀取數(shù)據(jù),然后將需要的數(shù)據(jù)拷貝回自己的數(shù)據(jù)庫(kù)。
我們的架構(gòu)已經(jīng)熬過(guò)了一輪軟件開(kāi)發(fā)周期,與此同時(shí),我們開(kāi)始標(biāo)準(zhǔn)化微服務(wù)使用的方式。
集成
目前為止,我已經(jīng)詳細(xì)說(shuō)明了許多有關(guān)系統(tǒng)分解的內(nèi)容。但是,用戶體驗(yàn)是我們的系統(tǒng)的最終目標(biāo),我們希望我們的應(yīng)用保持一致性,感覺(jué)就像是一個(gè)整體。
因此,問(wèn)題來(lái)了:我們?nèi)绾文軌蚣梢粋€(gè)分布式的系統(tǒng),同時(shí)讓用戶意識(shí)不到我們架構(gòu)的分布特性。
超鏈接
對(duì)于前端集成,最簡(jiǎn)單的辦法就是使用超鏈接。
每一個(gè)服務(wù)負(fù)責(zé)不同的頁(yè)面,頁(yè)面的導(dǎo)航通過(guò)鏈接來(lái)實(shí)現(xiàn)。
AJAX
使用AJAX的目的也很明顯,它能夠通過(guò)Javascript重新加載頁(yè)面的不同內(nèi)容,并且將他們整合在特定的頁(yè)面。
主要注意的是,服務(wù)之間涉及到的依賴非常的小,服務(wù)互相之間需要對(duì)使用的URL和媒體類型(media type)保持一致性。
資源服務(wù)器(Asset Server)
當(dāng)然,圖片在不同頁(yè)面的顯示也需要保持一致性。除此之外,分布式的服務(wù)需要對(duì)他們各自的Javascript庫(kù)和版本保持一致。
為了保持一致性,在我們的系統(tǒng)中,靜態(tài)資源,比如說(shuō)CSS,JS和圖片都是通過(guò)一個(gè)中央資源服務(wù)器來(lái)進(jìn)行傳輸。
在垂直架構(gòu),共享資源的部署和版本控制是一個(gè)完全不同的話題,這需要一篇獨(dú)立的文章來(lái)詳細(xì)解釋。在這里我們不做過(guò)多解釋,我們只需要記住,共享資源的同時(shí)保持服務(wù)獨(dú)立是具有非常大的挑戰(zhàn)性。
Edge-Side Includes
有一種不太知名的方法,它能整合不同服務(wù)的資源到同一個(gè)站點(diǎn)。這種方法我們稱之為Server-Side Includes或者是Edge-Side Includes。大多數(shù)的Web服務(wù)器或者反向代理都只支持這個(gè)功能。
這項(xiàng)技術(shù)非常的簡(jiǎn)單:一個(gè)服務(wù)插入插入一條帶有URL的包含語(yǔ)句(include statement)。然后這個(gè)URL會(huì)被web服務(wù)器或者反向代理解析,代理根據(jù)URL獲取到一個(gè)響應(yīng),然后用這個(gè)響應(yīng)代替頁(yè)面中的包含語(yǔ)句。
在我們的商城中,每一個(gè)頁(yè)面都包含了來(lái)自搜索&導(dǎo)航服務(wù)(SAN)的導(dǎo)航信息:
...
...
反向代理(我們使用了Varnish)解析了頁(yè)面,然后將URL分解出來(lái),SAN根據(jù)URL提供相應(yīng)的HTML片段。
然后Varnish代理用這個(gè)HTML片段取代包含語(yǔ)句,并將重新生成的頁(yè)面發(fā)送回用戶。
在這種方式下,用戶根本意識(shí)不到頁(yè)面是由多個(gè)來(lái)自不同服務(wù)的片段組成的。
數(shù)據(jù)拷貝(Data Replication)
以上提到的技術(shù)僅僅解決了前端集成問(wèn)題,現(xiàn)在我們來(lái)談?wù)劮?wù)端集成。不同的服務(wù)之間需要共同的數(shù)據(jù),但是他們又不能共享同一個(gè)數(shù)據(jù)庫(kù),因此我們想了一些辦法來(lái)處理這個(gè)問(wèn)題。
數(shù)據(jù)拷貝就是一個(gè)辦法。比如說(shuō),其他的服務(wù)需要關(guān)于產(chǎn)品的數(shù)據(jù),它們就會(huì)定期的向負(fù)責(zé)產(chǎn)品數(shù)據(jù)的垂直模塊(Product)請(qǐng)求數(shù)據(jù),這樣產(chǎn)品數(shù)據(jù)的更新能夠很迅速的被其他服務(wù)檢測(cè)到。
我們沒(méi)有使用任何的消息隊(duì)列來(lái)向客戶端推送(push)數(shù)據(jù)。相反的,每當(dāng)服務(wù)需要更新數(shù)據(jù)時(shí),它們會(huì)輪詢(poll)Atom Feed。
值得一提的是,某些不好的事情必須犧牲服務(wù)的可用性來(lái)避免,我們?cè)陂_(kāi)發(fā)過(guò)程中不得不面對(duì)這個(gè)矛盾。
沒(méi)有遠(yuǎn)程服務(wù)調(diào)用(NO Remote Service Calls)
理論上來(lái)說(shuō),在某些情況下,我們可以避免數(shù)據(jù)的拷貝,這樣服務(wù)就能夠同步使用其他的服務(wù)了。一個(gè)購(gòu)物籃并不需要保存額外的產(chǎn)品信息,相反它可以直接向產(chǎn)品模塊請(qǐng)求數(shù)據(jù)。
我們并沒(méi)有這么做,為什么呢?
當(dāng)一個(gè)系統(tǒng)的主要功能依賴于其它系統(tǒng)時(shí),系統(tǒng)的可測(cè)試性受到影響。
一個(gè)緩慢的服務(wù)會(huì)影響到其它系統(tǒng)的請(qǐng)求,當(dāng)請(qǐng)求越多,雪球越滾越大,最終影響到了整個(gè)系統(tǒng)的可用性。
系統(tǒng)的可擴(kuò)展性受到了限制。
獨(dú)立的服務(wù)部署變得非常的困難。
我們長(zhǎng)期和垂直架構(gòu)打交道,我們非常明確的知道,在早期的時(shí)候,嚴(yán)格的界限會(huì)使得微服務(wù)的開(kāi)發(fā),測(cè)試,遷移變得更加獨(dú)立,方便。
經(jīng)驗(yàn)教訓(xùn)
按照以上羅列的辦法,經(jīng)過(guò)三年的工作后,我們變得經(jīng)驗(yàn)豐富。
回顧往事,如果我早點(diǎn)做這些事情,我們的一體化系統(tǒng)會(huì)變得更加的精細(xì)?,F(xiàn)在,otto.de的未來(lái)屬于微服務(wù)。
原文鏈接: