《餓了么:日訂單量超900萬的架構(gòu)設(shè)計(jì)及演進(jìn)之路》要點(diǎn):
本文介紹了餓了么:日訂單量超900萬的架構(gòu)設(shè)計(jì)及演進(jìn)之路,希望對您有用。如果有疑問,可以聯(lián)系我們。
網(wǎng)站在剛開始的時(shí)候大概只是一個(gè)想法:一個(gè)產(chǎn)業(yè)的模型,快速地將它產(chǎn)生出來.“快”是第一位的,不需要花太多精力在架構(gòu)設(shè)計(jì)上.在網(wǎng)站進(jìn)入擴(kuò)張期才需要對架構(gòu)投入更多的精力來承載網(wǎng)站在爆發(fā)時(shí)的流量.
餓了么成立已經(jīng)8年,現(xiàn)在日訂單量突破900萬,我們也有了較為完善的網(wǎng)站架構(gòu).
初期,我們使用了能夠更容易拓展SOA的框架.我們用SOA的框架解決兩件事情:
網(wǎng)站初期,程序員可能就1~5個(gè),那時(shí)大家忙同一個(gè)事情就可以了.彼此之間的工作都互相了解,往往是通過“吼”的方式就把問題解決了.
但隨著人員的增加,這種方式顯然是不行的,不可能一個(gè)人更新了代碼再把其他人的所有代碼重新上線一遍吧?于是就要考慮分工協(xié)作的問題.
以前訂單量可能從1k到1w,雖然增長了10倍,但是總量并不是很高,對于一個(gè)網(wǎng)站的壓力來說,也不是那么大.真正到了訂單量從10w到100w,從100w到 200w的時(shí)候,可能數(shù)字上只是擴(kuò)大了10倍,但對整個(gè)網(wǎng)站的架構(gòu)上來說卻是一個(gè)巨大的挑戰(zhàn).
我們的背景就是2014年的100萬突破到現(xiàn)在的900萬,技術(shù)團(tuán)隊(duì)由剛開始的30多個(gè)人,到現(xiàn)在已經(jīng)是超過900人的團(tuán)隊(duì).這時(shí)候分工協(xié)作是個(gè)巨大的挑戰(zhàn).服務(wù)的分分合合,團(tuán)隊(duì)的分分合合,這都需要一套框架體系來支撐,這也是SOA框架的一個(gè)作用.
看一下我們的現(xiàn)狀,中間是我們整個(gè)架構(gòu)的體系,右側(cè)是和服務(wù)化相關(guān)的一些基礎(chǔ),包括基礎(chǔ)的組件或者服務(wù).
先說語言,我們原來的網(wǎng)站是在PHP上的,后來慢慢轉(zhuǎn)型.
創(chuàng)始人都是大學(xué)生創(chuàng)業(yè),那么理所當(dāng)然Python是一個(gè)很好的首選.到現(xiàn)在 Python也是很好的選擇,但是我們?yōu)槭裁匆獢U(kuò)展到Java和Go呢?
Python很多人都會(huì)寫,但是真正能把它做得很好的人并不多.隨著業(yè)務(wù)的發(fā)展,需要更多的開發(fā)人員.考慮到Java成熟的生態(tài)環(huán)境,以及新興的Go生態(tài),我們最終選擇了Python、Java、Go多語言共存的一個(gè)生態(tài).
WebAPI主要做一些HTTPS卸載、限流,還有安全校驗(yàn)等一些通用的和業(yè)務(wù)邏輯無關(guān)的操作.
Service Orchestrator是服務(wù)編排層,通過配置的方式實(shí)現(xiàn)內(nèi)外網(wǎng)的協(xié)議轉(zhuǎn)換、服務(wù)的聚合裁剪.
架構(gòu)圖右邊是一些圍繞這些服務(wù)化框架的輔助系統(tǒng),比如說用于定期執(zhí)行一個(gè)任務(wù)的Job系統(tǒng).我們有將近快1000個(gè)服務(wù),這些系統(tǒng)怎么監(jiān)控?所以必須有一套監(jiān)控系統(tǒng).剛開始只有30多個(gè)人時(shí),我們更擅長的是跑到機(jī)器上去搜一下Log,但到了900多人時(shí),你不可能都到機(jī)器上去搜一遍Log,需要有個(gè)集中式的日志系統(tǒng).其它的系統(tǒng)這里就不一一贅述了.
羅馬不是一天建成的,基礎(chǔ)架構(gòu)是個(gè)演進(jìn)的過程.我們精力有限,那先做什么呢?
當(dāng)網(wǎng)站變大了,原來的架構(gòu)跟不上發(fā)展的節(jié)奏了.我們要做的第一件事情就是:
把大Repo拆成一個(gè)小Repo,把大服務(wù)拆成小服務(wù),把我們的集中基礎(chǔ)服務(wù),拆分到不同的物理機(jī)器上去.
光是服務(wù)拆分用了一年多的時(shí)間才做完,這是一個(gè)比較漫長的過程.
這個(gè)過程中,首先要對API做一個(gè)很好的定義.因?yàn)橐坏┠愕腁PI上線之后,再做一些修改的成本是相當(dāng)大的.會(huì)有很多人依賴于你的API,很多時(shí)候你也并不知道有誰依賴于你的API,這是一個(gè)很大的問題.
然后再把一些基礎(chǔ)服務(wù)抽象出來.很多原來的服務(wù)其實(shí)是耦合在原來的業(yè)務(wù)代碼里面的.比如說支付業(yè)務(wù),業(yè)務(wù)很單一時(shí),緊耦合的代碼沒有關(guān)系,但是擴(kuò)展出的越來越多的業(yè)務(wù)都需要支付服務(wù)時(shí),你每一個(gè)業(yè)務(wù)(比如說支付的功能)都要去做一個(gè)嗎?所以我們要把這些基礎(chǔ)服務(wù)抽離出來,比如支付服務(wù)、短信服務(wù)、推送服務(wù)等.
拆服務(wù)看似很簡單、沒什么價(jià)值,但這恰恰是我們剛開始就要做的事情.其實(shí)在這個(gè)時(shí)期,前面所有的那些架構(gòu)都可以往后拖,因?yàn)椴蛔黾軜?gòu)調(diào)整其實(shí)不會(huì)死人,但是拆服務(wù)你不做的話,真的會(huì)死人.
服務(wù)拆分必定是一個(gè)漫長的過程,可這實(shí)際是一個(gè)很痛苦的過程,也需要很多配套系統(tǒng)的系統(tǒng)工程.
發(fā)布是最大的不穩(wěn)定因素.很多公司對發(fā)布的時(shí)間窗口有嚴(yán)格的限定,比如說:
我們發(fā)現(xiàn),發(fā)布的最大問題在于發(fā)布上去之后沒有簡單可執(zhí)行的回退操作.回退操作到底是誰來執(zhí)行,是發(fā)布人員就可以執(zhí)行,還是需要專人來執(zhí)行?如果是發(fā)布人員的話,發(fā)布人員并非24小時(shí)在線工作,出了問題找不到人怎么辦?如果是有專人來執(zhí)行回退,而又沒有簡單、統(tǒng)一的回退操作,那這個(gè)人需要熟悉發(fā)布人員的代碼,這基本上不可行.
所以我們就需要有發(fā)布系統(tǒng),發(fā)布系統(tǒng)定義了統(tǒng)一的回退操作,所有服務(wù)必須遵循發(fā)布系統(tǒng)的定義回退操作.
在餓了么對接發(fā)布系統(tǒng)是對所有人的強(qiáng)制要求,所有的系統(tǒng)必須全部接入發(fā)布系統(tǒng).發(fā)布系統(tǒng)的框架很重要,這個(gè)東西其實(shí)對于公司是很重要的一件事情,需要放到第一優(yōu)先級的隊(duì)列里面去考慮.
緊接著就是餓了么的服務(wù)框架,把一個(gè)大的Repo拆分成一個(gè)小的Repo,把一個(gè)大的服務(wù)拆成一個(gè)小的服務(wù),讓我們的服務(wù)盡量獨(dú)立出去,這需要一套分布式服務(wù)框架來支撐.
分布式服務(wù)框架包含的服務(wù)注冊、發(fā)現(xiàn)、負(fù)載均衡、路由、流控、熔斷、降級等功能,這里就不一一展開了.前面已經(jīng)提及,餓了么是多語言的生態(tài),有 Python的,也有Java的,我們的服務(wù)化框架對應(yīng)也是多語言的.這對我們后來一些中間件的選型是有影響的,比如說DAL層.
當(dāng)業(yè)務(wù)量越來越大的時(shí)候,數(shù)據(jù)庫會(huì)變成一個(gè)瓶頸.
前期可以通過提升硬件的方式來提升數(shù)據(jù)庫的性能.比如:
但硬件提升終歸是有一個(gè)容量限制的.而且很多做業(yè)務(wù)的小伙伴,寫代碼的時(shí)候都直接操作數(shù)據(jù)庫,發(fā)生過很多次服務(wù)一上線數(shù)據(jù)庫就被打爆的情形.數(shù)據(jù)庫被打爆掉了之后,除非等待數(shù)據(jù)庫恢復(fù),沒有任何其它機(jī)會(huì)可以恢復(fù)業(yè)務(wù).
如果數(shù)據(jù)庫里面數(shù)據(jù)是正常的,業(yè)務(wù)其實(shí)都可以補(bǔ)償出來.所以我們做DAL服務(wù)層的時(shí)候,第一件事情是限流,其它的東西可以放一放.然后做連接復(fù)用,我們Python框架用的多進(jìn)程單線程加協(xié)程的模型.
多進(jìn)程之間其實(shí)是不可以共享一個(gè)連接的.比如:一臺機(jī)器上部署了10個(gè) Python進(jìn)程,每個(gè)進(jìn)程10個(gè)數(shù)據(jù)庫連接.再擴(kuò)展到10臺機(jī)器上,就有1000個(gè)數(shù)據(jù)庫連接.對數(shù)據(jù)庫來說,連接是一個(gè)很昂貴的東西,我們DAL層要做一個(gè)連接復(fù)用.
這個(gè)連接復(fù)用講的不是服務(wù)本身的連接復(fù)用,而是說DAL層上的連接復(fù)用,就是服務(wù)有1000個(gè)連接到DAL層,經(jīng)過連接復(fù)用后對數(shù)據(jù)庫可能只是保持著十幾個(gè)連接.一旦發(fā)現(xiàn)某個(gè)數(shù)據(jù)庫請求是一個(gè)事務(wù)的話,那么DAL就幫你保留這個(gè)連接的對應(yīng)關(guān)系.當(dāng)這個(gè)事務(wù)結(jié)束之后,就把數(shù)據(jù)庫的連接,放回到共用池里面去,供其他人使用.
然后做冒煙和熔斷.數(shù)據(jù)庫也可以熔斷的.當(dāng)數(shù)據(jù)庫發(fā)生冒煙時(shí),我們會(huì)殺掉一些數(shù)據(jù)庫的請求,保證數(shù)據(jù)庫不至于崩潰.
服務(wù)框架之后,涉及服務(wù)治理的問題.服務(wù)治理其實(shí)是一個(gè)很大的概念.首先是埋點(diǎn),你要埋很多的監(jiān)控點(diǎn).
比如有一個(gè)請求,請求成功了或者失敗了,請求的響應(yīng)時(shí)間是多少,把所有的監(jiān)控指標(biāo)放到監(jiān)控系統(tǒng)上面去.我們有一個(gè)很大的監(jiān)控屏幕,上面有很多的監(jiān)控指標(biāo).有專門小組72小時(shí)去盯著這個(gè)屏幕,如果有任何曲線波動(dòng)了,就找人去解決.另外是報(bào)警系統(tǒng),一個(gè)監(jiān)控屏幕展示的東西總是有限的,只能放那些很重要的關(guān)鍵指標(biāo).這個(gè)時(shí)候就需要有報(bào)警系統(tǒng).
羅馬不是一天建成的,基礎(chǔ)架構(gòu)更是一個(gè)演進(jìn)的過程.我們的資源和時(shí)間總是有限的,作為架構(gòu)師和 CTO 來說,如何在這種有限的資源下,產(chǎn)出更重要的東西?
我們做了很多系統(tǒng),覺得自己做得很不錯(cuò)了,但實(shí)則不是,我感覺我們又回到了石器時(shí)代,因?yàn)閱栴}越來越多,需求也越來越多,總感覺你的系統(tǒng)里還缺點(diǎn)什么東西,想做的功能也一大堆.
比如對于流控系統(tǒng),現(xiàn)在我們還是需要用戶去配一個(gè)并發(fā)數(shù),那么這個(gè)并發(fā)數(shù),是不是根本不需要用戶去配?是不是可以基于我們服務(wù)本身的一個(gè)狀態(tài)自動(dòng)去控制并發(fā)數(shù)?
然后是升級方式,SDK升級是個(gè)很痛苦的事情.比如說我們服務(wù)框架2.0發(fā)布的時(shí)候是去年12月份,到現(xiàn)在還有人用的是1.0.是不是可以做到SDK的無損感升級,我們自己來控制升級的時(shí)間和節(jié)奏.
還有,我們現(xiàn)在的監(jiān)控只支持同一個(gè)服務(wù)上的匯聚,是不分集群、不分機(jī)器的,那是不是以后的指標(biāo)可以分集群、分機(jī)器?舉一個(gè)最簡單的例子,比如一個(gè)服務(wù)上有10臺機(jī)器,那么可能只是某一個(gè)機(jī)器上出了問題,但它所有的指標(biāo)都會(huì)平均分?jǐn)偟狡渌?臺機(jī)器上去.你只是看到了整個(gè)服務(wù)延時(shí)增加了,但有可能只是某一臺機(jī)器拖慢了整個(gè)服務(wù)集群.但我們現(xiàn)在還做不到更多維度的監(jiān)控.
還有智能化的報(bào)警,這個(gè)報(bào)警,就是要快、全、準(zhǔn),我們現(xiàn)在做到更快了,做到更全了,怎么才能做到更準(zhǔn)?每天的報(bào)警量高峰時(shí)間一分鐘一千多個(gè)報(bào)警發(fā)出去.所有的一千報(bào)警都是有用的嗎?報(bào)警多了之后,就相當(dāng)于沒有報(bào)警.大家都疲勞了,就不去看了.我怎么能夠把這個(gè)報(bào)警更準(zhǔn)確地區(qū)分出來?還有更智能化的鏈路分析?以后是不是我們的監(jiān)控不要放監(jiān)控指標(biāo),而是放鏈路分析,這樣就能夠很清晰地知道,這個(gè)問題對應(yīng)的是哪一個(gè)結(jié)點(diǎn)上出了問題.
這些問題涉及我們做事的一個(gè)原則:東西夠用就好,但是要能夠未雨綢繆,做一定的超前規(guī)劃.
文章來自微信公眾號:DBAplus社群
轉(zhuǎn)載請注明本頁網(wǎng)址:
http://www.fzlkiss.com/jiaocheng/4158.html