《指數(shù)級(jí)增長(zhǎng)背后,滴滴出行業(yè)務(wù)系統(tǒng)的架構(gòu)升級(jí)》要點(diǎn):
本文介紹了指數(shù)級(jí)增長(zhǎng)背后,滴滴出行業(yè)務(wù)系統(tǒng)的架構(gòu)升級(jí),希望對(duì)您有用。如果有疑問,可以聯(lián)系我們。
成立四年,估值已超260億美元,公司指數(shù)級(jí)發(fā)展、業(yè)務(wù)爆炸式增長(zhǎng),在此背景下,滴滴出行業(yè)務(wù)系統(tǒng)的架構(gòu)升級(jí)是怎樣進(jìn)行的?本文根據(jù)滴滴出行平臺(tái)產(chǎn)品中心技術(shù)總監(jiān)——杜歡在2016ArchSummit全球架構(gòu)師(深圳)峰會(huì)上的演講整理而成.
老司機(jī)簡(jiǎn)介
杜歡,滴滴平臺(tái)產(chǎn)品中心技術(shù)總監(jiān).2015年加入滴滴,負(fù)責(zé)公司公共業(yè)務(wù)、客戶端/前端架構(gòu)和新業(yè)務(wù)孵化,致力于用技術(shù)手段解決業(yè)務(wù)痛點(diǎn)和提升研發(fā)效率,曾作為技術(shù)負(fù)責(zé)人主導(dǎo)公司技術(shù)架構(gòu)升級(jí)以支撐公司業(yè)務(wù)快速迭代的需求.在加入滴滴前有長(zhǎng)達(dá)五年的創(chuàng)業(yè)經(jīng)歷,具有豐富的團(tuán)隊(duì)管理經(jīng)驗(yàn),熟悉移動(dòng)互聯(lián)網(wǎng)應(yīng)用的整個(gè)技術(shù)棧.
今天給大家主要介紹的是去年滴滴內(nèi)部做的一次重大架構(gòu)升級(jí),滴滴快速發(fā)展的過程中,系統(tǒng)的迭代速度和其他方面的設(shè)計(jì)遇到了很多困難,這次升級(jí)就是為了解決這些困難.
去年我們做了一次非常大的重構(gòu).上面圖中是今天要講的大綱,我會(huì)從問題本身出發(fā),回顧一下整個(gè)過程,包括如何發(fā)現(xiàn)問題、分析問題和解決方案.最后,我也會(huì)提出一些想法,如何規(guī)避重蹈這樣的覆轍.
挑戰(zhàn)在哪里?
首先,我們看一下挑戰(zhàn)在哪里.滴滴在出行領(lǐng)域是非常獨(dú)特的公司,它的獨(dú)特不在于業(yè)務(wù)模式多復(fù)雜,而在于它的發(fā)展非常快.滴滴的成立時(shí)間是2012年的6月,到現(xiàn)在為止才經(jīng)過了四年的時(shí)間.
滴滴的成長(zhǎng)速度十分驚人,到今天它的估值已經(jīng)超過260億美元,融資輪次非常多.如果不是因?yàn)楦?jìng)爭(zhēng)非常惡劣,滴滴也不會(huì)一直用融資的方法為自己開路.在這樣的壓力之下,滴滴所有的動(dòng)作可能都會(huì)走形,所有的想法可能因?yàn)楝F(xiàn)在一些短期利益不得不進(jìn)行一些權(quán)衡.
同時(shí),公司的業(yè)務(wù)也在爆炸式地增長(zhǎng).如果滴滴只做一個(gè)業(yè)務(wù),原本可以做得非常深入.滴滴從2014年開始加入了專車業(yè)務(wù),2015年業(yè)務(wù)數(shù)量增加到七條,2016年已經(jīng)超過十條.業(yè)務(wù)急速發(fā)展之中大家會(huì)思考,到底怎么做才能使這些還不穩(wěn)定或者還沒有想清楚的業(yè)務(wù)很好地迭代起來.
想到最簡(jiǎn)單的方法是,如果新業(yè)務(wù)跟某個(gè)舊業(yè)務(wù)非常類似但又不完全一樣,我們就把舊業(yè)務(wù)的舊代碼復(fù)制并修改,這樣新業(yè)務(wù)就做出來了.之前,這種情況經(jīng)常發(fā)生,就造成了很大的問題.
在2015年上半年,滴滴整個(gè)系統(tǒng)已經(jīng)積累了很多問題,分布在乘客App、服務(wù)端、Web App之中.特別值得一提的是,服務(wù)端的問題并不是性能,而是在于巨大的耦合導(dǎo)致數(shù)據(jù)紊亂和迭代速度越來越慢.
滴滴的獨(dú)特性迫使我們獨(dú)立思考這些問題,所有的解法都要針對(duì)滴滴現(xiàn)狀,而不是看哪個(gè)大公司是怎么做的,然后直接復(fù)制過來.
現(xiàn)狀是什么?
在解決問題之前,我們需要了解現(xiàn)狀是怎樣的.如圖所示,在2015年下半年,滴滴的系統(tǒng)架構(gòu)分為四層.最頂層是用戶應(yīng)用,每一個(gè)用戶應(yīng)用就是一個(gè)端,也就是用戶所能看到的入口.然后是接入層,這是非常傳統(tǒng)的結(jié)構(gòu),我們用了Nginx,還專門做了TCP接入層.
在業(yè)務(wù)層,Web是非常大的集群,有非常大的代碼量,我們只對(duì)業(yè)務(wù)做了分割,有策略引擎、司機(jī)調(diào)度.在數(shù)據(jù)層,有KV集群、MySQL集群、任務(wù)隊(duì)列、特征存儲(chǔ).這是任何一個(gè)初創(chuàng)公司應(yīng)該有的架構(gòu),我們對(duì)這個(gè)架構(gòu)并沒有做特殊的策劃,僅僅在這個(gè)技術(shù)體系里面把業(yè)務(wù)邏輯實(shí)現(xiàn)出來.
上面這張圖可能會(huì)比較有趣.右邊這個(gè)紅色的球,代表的是重構(gòu)之前App依賴的關(guān)系.當(dāng)時(shí)我很想梳理一下App在模塊之間是如何進(jìn)行依賴的,然后我就寫了一個(gè)腳本運(yùn)行了一下,得到的結(jié)果讓我很驚訝.我用藍(lán)色的線表示正常的依賴,就是模塊A依賴于模塊B,A是B的上一層,B不會(huì)反過來依賴A,用紅色的線表示異常的依賴,即A依賴B、B通過各種手段反過來依賴A,最后發(fā)現(xiàn)基本上都是紅色的.
做任何模塊的拆分,發(fā)現(xiàn)不得不面臨這樣的問題:把任何一個(gè)模塊取出來就等于把所有模塊都取出來,實(shí)際上沒有做拆分.所以,關(guān)鍵是需要解耦模塊結(jié)果.這是iOS的情況.安卓的情況更糟糕.
對(duì)于Web App來講,最大的問題在于耦合性.以前滴滴只有出租車這個(gè)業(yè)務(wù),最開始的Web App只有出租車,后來專車上線了,就在出租車?yán)锩婕恿藢\嚾肟?只是業(yè)務(wù)名不同界面會(huì)有小區(qū)別,后來加入了快車、代駕,都跟出租車差不多,沒遇到太大問題.
再后來有了順風(fēng)車,順風(fēng)車跟其他功能不一樣,整體界面是預(yù)約型的,有乘客和車主兩種模式.如果在老首頁(yè)里面開發(fā)順風(fēng)車成本太大了,需要和出租車業(yè)務(wù)線的人一起開發(fā)業(yè)務(wù)模塊,如果未來做迭代,這種開發(fā)模式將非常痛苦.老首頁(yè)的模塊也沒有做拆分,代碼散落各地,只是通過打包工具拼接在一起,沒有做模塊化,所以整體情況也比較糟糕.
相比端,API稍好一點(diǎn)的是,API至少在業(yè)務(wù)維度上是分開的,出租車與專車、快車是分開的兩個(gè)系統(tǒng),放在兩個(gè)倉(cāng)庫(kù)里面.不過API也有一個(gè)很大的問題,業(yè)務(wù)代碼沒有做服務(wù)化拆分,沒有model 封裝,業(yè)務(wù)所有的API和后臺(tái)MIS都在一個(gè)倉(cāng)庫(kù)里,這對(duì)系統(tǒng)來說是非常大的一個(gè)隱患.
該如何入手?
做到這一點(diǎn)之后,所有的業(yè)務(wù)迭代問題就迎刃而解了,因?yàn)闃I(yè)務(wù)間已經(jīng)沒有依賴和耦合了.這一步完成之后做的就是重新梳理業(yè)務(wù),讓業(yè)務(wù)根據(jù)自己模型特點(diǎn)進(jìn)行一些重構(gòu).
最開始的時(shí)候,我們考慮的是怎么做代碼治理和模塊下沉.代碼治理本質(zhì)上就是把各種模塊進(jìn)行染色、再把它們歸類的過程.代碼治理最難的事情在于消除錯(cuò)綜復(fù)雜的依賴.到底怎么做才對(duì)呢?
模塊下沉與代碼治理息息相關(guān).如果只是要求把所有代碼拆分,而沒有合適的拆分方法,這件事情是無法推進(jìn)下去的.對(duì)于程序員來說,他們內(nèi)心總有一種沖動(dòng)想做有意思的事情,比如封裝一個(gè)很有意思的模塊給更多程序員用.大家并非不想做封裝,只是如果封裝并共享出來的代價(jià)太大,就會(huì)影響大家的熱情.
模塊下沉是一種機(jī)制,一方面我們應(yīng)該鼓勵(lì),另一方面還應(yīng)該讓大家發(fā)現(xiàn)這是一件不得不做的事情.如果僅僅對(duì)內(nèi)公開模塊列表讓大家自由選擇,達(dá)不到模塊下沉的目的.因?yàn)槿硕己軕?不想思考太多,只想盡快把事情完成,大家往往傾向于復(fù)制粘貼,也不愿意額外花時(shí)間做下沉.
怎么辦呢?我們會(huì)給所有業(yè)務(wù)提供一個(gè)統(tǒng)一的SDK,里面包含所有能用的組件,大家必須使用它進(jìn)行開發(fā).如果業(yè)務(wù)模塊穩(wěn)定了并且比較通用,我們有工具和相應(yīng)的簡(jiǎn)單機(jī)制把業(yè)務(wù)模塊下沉下來,變成SDK的一部分,長(zhǎng)期下去SDK會(huì)越來越大,只要SDK里做好分類和規(guī)劃,上層就會(huì)越來越輕,我們可以真正專注于業(yè)務(wù)邏輯開發(fā).
除了上面這些,最核心的一點(diǎn)在于,一定要把所有業(yè)務(wù)都做到“無狀態(tài)”和“異步化”.
“無狀態(tài)”這個(gè)概念在服務(wù)端比較容易理解.一般我們傾向于把各種業(yè)務(wù)做到無狀態(tài),這樣容易做水平擴(kuò)展.在客戶端也是一個(gè)道理,也要考慮橫向擴(kuò)展性.一個(gè)簡(jiǎn)單的框架往往提供一些最基礎(chǔ)的控件,比如按紐、列表,這些都不會(huì)耦合任何業(yè)務(wù)邏輯,所以很容易使用.
但是當(dāng)業(yè)務(wù)做起來,大家習(xí)慣將一些狀態(tài)放到業(yè)務(wù)控件里面,這在一定程度上方便了,但是一旦需要將業(yè)務(wù)進(jìn)行重構(gòu)或者進(jìn)行模塊化下沉的時(shí)候,就造成了非常大的困難.例如,一個(gè)模塊如果大量通過全局變量或單例跟上下游耦合,那么這個(gè)模塊就很難復(fù)用和重構(gòu),這些全局變量或單例就是狀態(tài).
所以,我們?cè)诳蛻舳艘蔡岢鍪褂谩盁o狀態(tài)”的方式,把存儲(chǔ)的信息都放到外面.后面我會(huì)提到到底應(yīng)該怎么樣去做.
“異步化”也是解耦的方式.服務(wù)端的RPC類似于函數(shù)調(diào)用,如果參數(shù)變了,實(shí)現(xiàn)和調(diào)用的雙方都要做改變,這很不透明,也不能夠漸進(jìn)式上線.我們用訂閱/發(fā)布的模式對(duì) RPC進(jìn)行解耦,要求所有接口都要異步返回.
在客戶端也是這樣,比如做數(shù)據(jù)的緩存,想優(yōu)化網(wǎng)絡(luò),我們不能夠期待這個(gè)函數(shù)是一個(gè)同步函數(shù),一定用回調(diào)的方式接受所有參數(shù).所以做設(shè)計(jì)的時(shí)候,只要是有可能發(fā)生網(wǎng)絡(luò)請(qǐng)求或者訪問磁盤,在客戶端也盡量異步請(qǐng)求數(shù)據(jù).
剛剛講的都是相對(duì)比較抽象的內(nèi)容,接下來會(huì)說一下滴滴的業(yè)務(wù)形態(tài)本身.
滴滴是一個(gè)出行的平臺(tái),涵蓋的是整個(gè)出行領(lǐng)域所有的出行需求.大家出行到底想要什么?就是到達(dá)自己想去的地方.實(shí)際上,我們的模型可以做得非常抽象和簡(jiǎn)單.比如,我想要打快車去機(jī)場(chǎng),我就是一個(gè)需求方,我的需求會(huì)發(fā)到很多服務(wù)者那里去,服務(wù)者會(huì)根據(jù)特征進(jìn)行一些匹配.
最基本的特征是服務(wù)能力,如果服務(wù)者能夠開快車并通過了能力驗(yàn)證,這個(gè)需求就有可能發(fā)給他.如果開出租車的也有能力開快車,但是他還沒有在平臺(tái)上驗(yàn)證這個(gè)能力,就只能開出租車.一個(gè)人可以驗(yàn)證很多服務(wù),白天可以開快車,晚上可以做代駕,做不同的事.
服務(wù)和需求的匹配是通過計(jì)價(jià)模型和匹配策略來實(shí)現(xiàn)的.發(fā)送需求的時(shí)候需要選擇計(jì)價(jià)模型和車的類型.快車和專車服務(wù)過程大同小異,但是價(jià)格差別很明顯,專車價(jià)格會(huì)貴很多.通過匹配策略可以實(shí)現(xiàn)各種需求的匹配.
例如,選擇了拼車,這個(gè)需求會(huì)盡量匹配已經(jīng)有拼友和順路的車.如果選擇專車,可以要求這輛車在指定時(shí)間來接人,這時(shí)候匹配策略會(huì)優(yōu)化傾向這種方式.
滴滴所有的業(yè)務(wù)基本上都是以這種模式運(yùn)轉(zhuǎn)的,所有功能都是核心主干或者旁路,只要把業(yè)務(wù)模型抽象出來,基本上就能夠滿足大部分的業(yè)務(wù)了.
基于這樣的想法,我們就思考如何設(shè)計(jì)真正高度抽象的工具.簡(jiǎn)單起見,我們把滴滴出行的過程抽象成一個(gè)框架(見上圖),這并不是完整的框架.有顏色的地方表示出租車、快車、專車、代駕共同的流程,只要組合各種流程就可以實(shí)現(xiàn)整個(gè)業(yè)務(wù)形態(tài)的能力.在這個(gè)框架里可以定制所有業(yè)務(wù)形態(tài)的車標(biāo)、提示語(yǔ)、匹配的模型、計(jì)價(jià)模型等功能.
當(dāng)時(shí)梳理這個(gè)抽象的時(shí)候,我們感覺非常興奮,因?yàn)檫@意味著在這個(gè)基礎(chǔ)之上就可以簡(jiǎn)易擴(kuò)展出滴滴未來的業(yè)務(wù)形態(tài).只要滴滴還是在做需求和服務(wù)的匹配,基本上就離不開這樣一種套路.
客戶端怎么拆?
首先就是客戶端,最重要的是需要將業(yè)務(wù)拆出來.以前所有業(yè)務(wù)放在同一個(gè)倉(cāng)庫(kù)里,如果不小心提交了一段錯(cuò)誤代碼就會(huì)帶來災(zāi)難性的后果,所有業(yè)務(wù)工作可能都會(huì)受到影響.以前編譯速度也很糟糕,大家可以想象,每次下載代碼都會(huì)有幾個(gè)頭文件發(fā)生改變,由于循環(huán)依賴的緣故幾乎所有文件都要重編,二三十分鐘后才能重新調(diào)試,這個(gè)過程讓人極度崩潰.
對(duì)于iOS,我們用cocoapods把業(yè)務(wù)拆到不同的pod里面;對(duì)于安卓,我們把業(yè)務(wù)拆分打包并用Maven管理起來.我們拆分方法如下圖所示,其中虛線框部分展示的是公共框架,最開始沒有很細(xì)致分割,只是把它放在一個(gè)獨(dú)立倉(cāng)庫(kù)里,保證依賴關(guān)系充分清楚,后面就可以隨時(shí)把代碼獨(dú)立出來,使其變成單獨(dú)的模塊.
同時(shí),我們也在開發(fā)構(gòu)建系統(tǒng).原生的構(gòu)建系統(tǒng)使用起來會(huì)有很多問題,它并不支持多人并行開發(fā),如果要實(shí)現(xiàn)一個(gè)舒適的工作流就需要定制.我們還做了網(wǎng)絡(luò)和日志的封裝,將其放在下層.還有一個(gè)業(yè)務(wù)整合的基礎(chǔ)框架,包括滴滴出行的App界面框架、首頁(yè)導(dǎo)航欄,各種業(yè)務(wù)可以注冊(cè)自己的入口,并在導(dǎo)航欄里進(jìn)行切換.
業(yè)務(wù)之間沒有任何代碼耦合,比如出租車和專車業(yè)務(wù)沒有關(guān)聯(lián)性,那么代碼也沒有任何相關(guān)的地方,這意味著開發(fā)出租車業(yè)務(wù)的時(shí)候,完全沒有必要實(shí)時(shí)更新專車代碼,集成的時(shí)候也不會(huì)因?yàn)閷\嚧a而造成問題.
最頂層的One Travel可以通過簡(jiǎn)單的配置分業(yè)務(wù)包,比如可以輸出只有出租車業(yè)務(wù)的包,在這上面開發(fā)測(cè)試速度比較快,整體也會(huì)比較靈活.One Travel里面只有極少的代碼,未來會(huì)改成沒有代碼、通過腳本就可以生成的項(xiàng)目.
怎么做頁(yè)面的解耦?上圖中是一種類似數(shù)據(jù)庫(kù)緩存的設(shè)計(jì).從客戶端角度來看,如果把服務(wù)器當(dāng)做一個(gè)數(shù)據(jù)庫(kù),最終狀態(tài)存儲(chǔ)在服務(wù)器,而客戶端里存著的是跟服務(wù)器同步過的最新狀態(tài)的緩存.客戶端不太可能做到精確的數(shù)據(jù)同步,一定是每隔一段時(shí)間同步一次,或者是在關(guān)鍵節(jié)點(diǎn)上靠服務(wù)器推送得到訂單狀態(tài)變化.
客戶端的業(yè)務(wù)代碼其實(shí)不關(guān)心究竟是如何同步狀態(tài)的,所以我們專門寫了一個(gè)緩存服務(wù)器狀態(tài)的Store層,它是熱數(shù)據(jù).如果不需要最新狀態(tài)的數(shù)據(jù),業(yè)務(wù)讀取Store時(shí)可以讀到上次同步的數(shù)據(jù),假設(shè)此時(shí)Store從未同步過狀態(tài)就會(huì)自動(dòng)讀取最新狀態(tài);如果業(yè)務(wù)一定要最新狀態(tài)的數(shù)據(jù),那么就顯示要求緩存失效,這樣Store就會(huì)再讀取一次獲取最新的信息.
Store還可以自動(dòng)設(shè)置失效時(shí)間長(zhǎng)度,這個(gè)機(jī)制跟跟做數(shù)據(jù)庫(kù)緩存是一樣的,為了性能的平衡,要保證讀出準(zhǔn)確的數(shù)據(jù),同時(shí)性能也要最優(yōu).同時(shí),Store也有責(zé)任負(fù)責(zé)數(shù)據(jù)更新,當(dāng)客戶端變化可能會(huì)讓服務(wù)器狀態(tài)變化時(shí),Store可以自動(dòng)讓相關(guān)狀態(tài)失效,這也是管理緩存的一般做法.
做了這樣一些解耦之后,令人驚喜的是,我們發(fā)現(xiàn)所有界面是可以隨意跳轉(zhuǎn)的,雖然沒有從發(fā)單直接跳到評(píng)價(jià)的必要性,但實(shí)際上只要有這個(gè)架構(gòu),就可以從界面A跳到界面B,不會(huì)有任何問題.
如果跳到另外一個(gè)界面,沒有發(fā)現(xiàn)必要的數(shù)據(jù),就從服務(wù)器讀取,它自己也會(huì)報(bào)錯(cuò),整個(gè)邏輯非常清晰.如果需要在流程A和流程B之間再增加一個(gè)流程C,我們可以把流程C直接加進(jìn)去,流程C沒有破壞A和B之間的依賴,因?yàn)樵続和B之間也沒有什么依賴.
我們也做一些App的組件化,把從服務(wù)端API到客戶端邏輯打包在一起,引用客戶端組件就可以實(shí)現(xiàn)完整功能.實(shí)際封裝方法略微有點(diǎn)復(fù)雜(注:可以閱讀另外一篇文章支撐滴滴高速發(fā)展的引擎:滴滴的組件化實(shí)踐與優(yōu)化).
圖中所示是做平滑移動(dòng)組件,地圖上有很多車在移動(dòng),這些車就是地圖上的額外信息,把這些車掛在地圖上.如果這個(gè)控件不存在,地圖上就沒有車,控件存在,地圖上就有車,只要在上面啟動(dòng)控件就好了.
App集成也采用了異步和無障礙的做法,每個(gè)業(yè)務(wù)只需要在倉(cāng)庫(kù)里面測(cè)試完之后直接打tag,之后就能自動(dòng)生成整個(gè)所有業(yè)務(wù)的ipa/apk包.
Web App怎么拆?
接下來講Web App的拆解,這實(shí)際上是純工程的解耦.
首先,我們需要實(shí)現(xiàn)一個(gè)簡(jiǎn)單的公共框架,這跟業(yè)務(wù)是無關(guān)的.我們使用scrat和webpack來實(shí)現(xiàn)工程化,將首頁(yè)拆分成了許多組件,所有的業(yè)務(wù)可以根據(jù)不同配置選擇使用哪些組件,同時(shí)也保證頁(yè)面風(fēng)格的統(tǒng)一、功能的穩(wěn)定.
如果網(wǎng)絡(luò)比較糟糕,我們會(huì)做一系列的降級(jí),首先出來的會(huì)是一些統(tǒng)一的控件,比如上車地點(diǎn)、目的地、廣告等,之后會(huì)根據(jù)定位的結(jié)果得到當(dāng)前開通的業(yè)務(wù)線列表,并加載業(yè)務(wù)代碼,然后默認(rèn)選擇當(dāng)前業(yè)務(wù)線的邏輯.
如果業(yè)務(wù)線代碼加載好了就開始渲染,如果業(yè)務(wù)加載出錯(cuò)或代碼執(zhí)行出錯(cuò),業(yè)務(wù)就會(huì)被隱藏.業(yè)務(wù)線之間也是完全解耦的,大家可以通過公共框架提供的事件機(jī)制來通信,但不允許業(yè)務(wù)之間直接通信.線上的Web App就是如上圖所看到的,每個(gè)業(yè)務(wù)線都有一段獨(dú)立js代碼,第一次加載相對(duì)較慢,會(huì)看到很多請(qǐng)求,如果業(yè)務(wù)線代碼沒有更新,下次打開就完全不走網(wǎng)絡(luò)請(qǐng)求.
我們也做了很多控件,這是內(nèi)網(wǎng)發(fā)布的一些控件(見上圖),每個(gè)業(yè)務(wù)只要關(guān)注自己的業(yè)務(wù)邏輯即可,公共的功能都可以使用控件.特別是選擇地址的控件,它把前端界面交互和后端API都打包在一起,和客戶端一樣,只要引用它,就可以直接在Web App使用,無需任何服務(wù)端的開發(fā).
服務(wù)器API怎么拆?
關(guān)于服務(wù)器API的拆分,我們最開始希望一次性實(shí)現(xiàn)理想方案,但是這個(gè)理想方案遇到一些問題.
我先來談?wù)劺硐敕桨甘鞘裁?首先,滴滴業(yè)務(wù)一般都是基于訂單流轉(zhuǎn)推動(dòng)各種業(yè)務(wù)動(dòng)作.為什么會(huì)發(fā)生訂單流轉(zhuǎn)?是因?yàn)閷?duì)乘客和司機(jī)做了一些操作,如果想象成一個(gè)客戶端系統(tǒng),就有點(diǎn)類似于觸發(fā)各種用戶事件.客戶端動(dòng)作根本上決定了信息該如何流轉(zhuǎn),所有事情都應(yīng)該在客戶端觸發(fā),觸發(fā)之后來到了組件這一層,所有動(dòng)作進(jìn)行消費(fèi),然后進(jìn)行下一步操作.
比如,用戶提出一個(gè)需求,發(fā)單對(duì)需求進(jìn)行過濾,判斷是哪種需求,然后進(jìn)行一些檢查.快車有拼車和不拼車兩種,發(fā)單的時(shí)候就可以知道是拼車還是不拼車,對(duì)于統(tǒng)一訂單系統(tǒng)來說這就是個(gè)標(biāo)志.無論拼不拼,這個(gè)單對(duì)用戶都一樣,無非就是消耗多少人民幣、消耗幾個(gè)座位還是消耗整輛車的問題.
之后分單系統(tǒng)會(huì)進(jìn)行訂單的匹配.一旦匹配成功,客戶端有很多動(dòng)作,司機(jī)確認(rèn)接單,乘客可以看到確認(rèn).如果直接做成消息,客戶端和服務(wù)端用一條總線連接,問題就解決了.
這里有一個(gè)很大的優(yōu)點(diǎn)——可拼接,所有東西都組件化了.但是最大的問題在于抽象程度非常高.這是函數(shù)式的思想,要求所有的Worker都是純函數(shù),純函數(shù)是非常高的要求,上下文狀態(tài)必須要通過參數(shù)才行.我們發(fā)現(xiàn)很難做到這一點(diǎn),因?yàn)樗邢到y(tǒng)必須有狀態(tài),一旦這樣這個(gè)純函數(shù)就不是純函數(shù)了,要依賴外部的變量.
與面向?qū)ο笤O(shè)計(jì)的思路差異非常大,做函數(shù)式設(shè)計(jì)時(shí)很容易陷入一些抉擇當(dāng)中,如何定義輸入、輸出,如何劃分流程.有一些流程劃分成三段式,中間的流程異步調(diào)出去,又異步調(diào)回來繼續(xù)后續(xù)流程,這種設(shè)計(jì)讓人很糾結(jié).
函數(shù)很依賴異步化,異步化會(huì)讓數(shù)據(jù)流變得復(fù)雜.我們思考數(shù)據(jù)流的流向,以及每次數(shù)據(jù)流在流轉(zhuǎn)的時(shí)候都需要設(shè)置的輸入、輸出.最終,這個(gè)方案并沒有實(shí)施,雖然我們開發(fā)了接近半年的時(shí)間.
2016年,我們又重新思考了這個(gè)問題,這次是比較簡(jiǎn)單和現(xiàn)實(shí)的方法.首先我們進(jìn)行了一些代碼的隔離,把代碼分開,之后對(duì)系統(tǒng)按照剛才講的模塊進(jìn)行面向?qū)ο蟮某橄?比如發(fā)單就是單獨(dú)的系統(tǒng),訂單也是一個(gè)單獨(dú)的系統(tǒng),支付的收銀體系是一個(gè)系統(tǒng),評(píng)價(jià)體系是一個(gè)系統(tǒng).每一個(gè)系統(tǒng)變得很簡(jiǎn)單,互相之間用RPC調(diào)用關(guān)聯(lián)起來.
這會(huì)有什么缺點(diǎn)呢?長(zhǎng)期來講缺點(diǎn)還是比較明顯的,就是不容易擴(kuò)展.現(xiàn)在我們?cè)O(shè)計(jì)的模型是來源于當(dāng)前業(yè)務(wù)現(xiàn)狀,如果業(yè)務(wù)發(fā)生改變,比如多了一種車型,就會(huì)遇到該如何擴(kuò)展的抉擇:應(yīng)該提供更多API接口滿足新的業(yè)務(wù)功能,還是在原有API修改上提供更多參數(shù).
兩種方法看起來都可以,但是本質(zhì)上我認(rèn)為無論用哪種方案都會(huì)使模塊本身變得越來越臃腫,其實(shí)都是把很多種東西融合在一起,并不是很理想.當(dāng)一個(gè)服務(wù)臃腫到一定程度之后又會(huì)出現(xiàn)以前的問題,又要再次做拆分和重構(gòu),甚至整個(gè)RPC調(diào)用流程都會(huì)發(fā)生很大震動(dòng).
從項(xiàng)目整體實(shí)施效果上來講,這次重構(gòu)最主要是解決了開發(fā)迭代的問題,能夠讓迭代速度更快.讓我們比較意外的情況是,重構(gòu)前客戶端crash率非常高,重構(gòu)中我們對(duì)代碼進(jìn)行了非常多的修改,同時(shí)還在用戶體驗(yàn)上做了很多優(yōu)化,但最終crash率反而大幅下降,從以前1%降低到0.3%.
重構(gòu)后各個(gè)業(yè)務(wù)團(tuán)隊(duì)的開發(fā)模式發(fā)生了根本的變化,以前是各個(gè)業(yè)務(wù)各耦合在一起進(jìn)行開發(fā),現(xiàn)在各個(gè)業(yè)務(wù)都能獨(dú)立開發(fā),互不干擾,同時(shí)平臺(tái)還會(huì)不斷產(chǎn)出更多的公共組件.
如何避免重蹈覆轍?
最后提一下如何重蹈覆轍.我認(rèn)為,所有的設(shè)計(jì)應(yīng)該是自上而下,先從產(chǎn)品層面上規(guī)劃核心業(yè)務(wù)的模式,然后考慮如何讓產(chǎn)品技術(shù)實(shí)現(xiàn)它.如果把業(yè)務(wù)模式描述成如圖所示的核心循環(huán),會(huì)非常清楚.我們不僅要考慮現(xiàn)在,還要考慮未來.如果讓整個(gè)架構(gòu)保持健康,就要考慮什么功能是真正緊密相關(guān)的.
比如在服務(wù)端,直覺上感覺各種不同的發(fā)單應(yīng)該是在一起的,但實(shí)際上并不是這樣.不同車型的發(fā)單接口互相之間并沒有什么聯(lián)系,每一種發(fā)單都會(huì)有獨(dú)特的個(gè)性化定制,這些定制才是真正應(yīng)該跟發(fā)單緊耦合的東西.
所以我們應(yīng)該從產(chǎn)品角度上考慮,把一種發(fā)單所調(diào)用的所有相關(guān)API放在一起,服務(wù)端發(fā)生變化,調(diào)用的組件也會(huì)發(fā)生變化,做到發(fā)單閉環(huán).剛剛提到的今年服務(wù)端的重構(gòu)的方法,實(shí)際上并沒有讓各個(gè)子系統(tǒng)打通,這是一件很遺憾的事.未來如果開發(fā)一些新需求,肯定還會(huì)涉及多個(gè)模塊、團(tuán)隊(duì),避免不了一些溝通成本.
另外給大家介紹一下,我們專門做了一個(gè)組件平臺(tái),叫做魔方組件庫(kù),是客戶端到服務(wù)端的庫(kù),我們會(huì)繼續(xù)沉淀更多的客戶端到服務(wù)端打通的組件,讓業(yè)務(wù)開發(fā)更快更輕松.
文章出處:InfoQ
轉(zhuǎn)載請(qǐng)注明本頁(yè)網(wǎng)址:
http://www.snjht.com/jiaocheng/4451.html