新聞中心
公司決定將支付業(yè)務(wù)從原來(lái)所在部門(mén)剝離出來(lái),成為一個(gè)獨(dú)立的團(tuán)隊(duì),以應(yīng)付迅速發(fā)展的業(yè)務(wù)需求。原團(tuán)隊(duì)負(fù)責(zé)支付系統(tǒng)開(kāi)發(fā)的幾位同學(xué)轉(zhuǎn)到現(xiàn)團(tuán)隊(duì),形成開(kāi)發(fā)班底。此后開(kāi)始招聘,三個(gè)月團(tuán)隊(duì)擴(kuò)充到10多個(gè)人。與此同時(shí),公司業(yè)務(wù)也在快速發(fā)展,6月份宣布會(huì)員突破2千萬(wàn)。一些熱片上映往往也會(huì)引發(fā)會(huì)員注冊(cè)繳費(fèi)的小高峰。其他業(yè)務(wù),包括直播,閱讀,動(dòng)漫等,也都進(jìn)入了發(fā)展的快車(chē)道。每天訂單量早已經(jīng)超過(guò)百萬(wàn),比去年某片上映時(shí)把系統(tǒng)打垮時(shí)還早高。移動(dòng)端每個(gè)月發(fā)布一個(gè)版本,桌面則是半個(gè)月。產(chǎn)品經(jīng)理們夜以繼日地規(guī)劃各種功能,待開(kāi)發(fā)功能都排到好幾個(gè)月之后。而隨著項(xiàng)目團(tuán)隊(duì)的日益擴(kuò)大,卻出現(xiàn)一些奇怪的事情:

- 開(kāi)發(fā)效率和以前沒(méi)有太大區(qū)別,盡管隊(duì)伍擴(kuò)大了4倍多,人員素質(zhì)則有所提升。
- 大部分開(kāi)發(fā)工作還是幾位老員工在忙,新員工還比較難介入核心開(kāi)發(fā)工作。
除了管理因素,作為工程師,我們還是期待從技術(shù)上找到根源所在,解決問(wèn)題,提高效率。最終的決策,是使用微服務(wù)架構(gòu)來(lái)重構(gòu)現(xiàn)有系統(tǒng)。這一系列博文,描述在這過(guò)程中我們做的選擇、取得的成果、走過(guò)的彎路,以及經(jīng)驗(yàn)教訓(xùn)。
一、原有架構(gòu)
從技術(shù)角度看,原有系統(tǒng)是一個(gè)基于SSH架構(gòu)的傳統(tǒng)實(shí)現(xiàn),軟件架構(gòu)整體上是大家所熟知的多層Java軟件架構(gòu):
代碼讓人看的非常懷舊,雖然開(kāi)發(fā)人員和我說(shuō)是4年前開(kāi)發(fā)的,但這熟悉的SSH架構(gòu),可是妥妥10年前的東西。使用Apache Struts做展示層,對(duì)數(shù)據(jù)訪(fǎng)問(wèn)層做個(gè)簡(jiǎn)單封裝實(shí)現(xiàn)業(yè)務(wù)邏輯層,基于Spring 的AOP以及Hibernate實(shí)現(xiàn)數(shù)據(jù)訪(fǎng)問(wèn)。 數(shù)據(jù)保存在MySQL中,單庫(kù)多表的結(jié)構(gòu)。
二、架構(gòu)問(wèn)題
1. DAO層
使用Hibernate來(lái)封裝數(shù)據(jù)庫(kù)訪(fǎng)問(wèn)操作。其優(yōu)勢(shì)是在面向?qū)ο箢I(lǐng)域,通過(guò)系統(tǒng)自動(dòng)生成數(shù)據(jù)庫(kù)訪(fǎng)問(wèn)語(yǔ)句的方式,使得開(kāi)發(fā)者無(wú)需考慮數(shù)據(jù)庫(kù)的實(shí)現(xiàn)細(xì)節(jié),專(zhuān)注于對(duì)象的設(shè)計(jì)和使用,簡(jiǎn)化了開(kāi)發(fā)工作。另外,使用Hibernate還支持系統(tǒng)可以在不同類(lèi)型的對(duì)象數(shù)據(jù)庫(kù)間無(wú)縫遷移。在業(yè)務(wù)對(duì)象關(guān)系復(fù)雜的管理系統(tǒng)開(kāi)發(fā)中廣泛使用。存在的問(wèn)題是,它隱藏了數(shù)據(jù)庫(kù)的實(shí)現(xiàn)細(xì)節(jié),這導(dǎo)致在大數(shù)據(jù)場(chǎng)景下開(kāi)發(fā)人員很難對(duì)數(shù)據(jù)庫(kù)訪(fǎng)問(wèn)進(jìn)行優(yōu)化,而這卻是互聯(lián)網(wǎng)應(yīng)用開(kāi)發(fā)的重點(diǎn)。
2. Service層
業(yè)務(wù)邏輯層為Controller層提供具體業(yè)務(wù)的實(shí)現(xiàn)。但在實(shí)現(xiàn)上,問(wèn)題還不少。如果是嚴(yán)格按照分層架構(gòu)來(lái)實(shí)現(xiàn),對(duì)業(yè)務(wù)邏輯層進(jìn)行拆分,將本地調(diào)用變成遠(yuǎn)程調(diào)用,即可比較容易實(shí)現(xiàn)拆分。但實(shí)際中往往會(huì)碰到如下問(wèn)題:
- 這個(gè)層往往并未實(shí)現(xiàn)單向依賴(lài),部分業(yè)務(wù)邏輯層實(shí)現(xiàn)被注入了接口層的參數(shù)(request,response),使其耦合到接口層。
- 為了應(yīng)對(duì)不斷變更的需求,不少接口會(huì)使用map作為輸入輸出參數(shù),此類(lèi)接口在維護(hù)時(shí)無(wú)法約束其參數(shù)。
- Service層絕大部分實(shí)現(xiàn)是使用工廠(chǎng)模式來(lái)管理數(shù)據(jù)對(duì)象。僅對(duì)工廠(chǎng)類(lèi)建模,未對(duì)業(yè)務(wù)實(shí)體建模。這個(gè)層的實(shí)現(xiàn)是不完整的。 這使得對(duì)業(yè)務(wù)實(shí)體的操作需要推遲到Controller層來(lái)實(shí)現(xiàn),導(dǎo)致Controller臃腫。
- 當(dāng)服務(wù)之間存在大量依賴(lài)關(guān)系時(shí),開(kāi)發(fā)人員往往會(huì)直接將Spring BeanFactory注入到各個(gè)服務(wù)中,或者簡(jiǎn)單封裝一個(gè)FacadeService,通過(guò)這個(gè)Service可以訪(fǎng)問(wèn)到所有的業(yè)務(wù)邏輯對(duì)象。這個(gè)類(lèi)的使用導(dǎo)致無(wú)法評(píng)估Controller層對(duì)Service業(yè)務(wù)對(duì)象的具體依賴(lài)。
3. Controller層
基于A(yíng)pache Struts來(lái)實(shí)現(xiàn), Apache Struts 漏洞頻繁爆發(fā),修復(fù)慢。當(dāng)前已經(jīng)很少在對(duì)外的應(yīng)用中使用了。由于Service層實(shí)現(xiàn)上的問(wèn)題,Controller層承擔(dān)了部分業(yè)務(wù)邏輯實(shí)現(xiàn),使其臃腫,難以測(cè)試。
三、功能問(wèn)題
從功能模塊上來(lái)看,并沒(méi)有區(qū)分對(duì)端的服務(wù)以及對(duì)運(yùn)營(yíng)管理系統(tǒng)的服務(wù),僅實(shí)現(xiàn)了支付系統(tǒng)的基本功能:
四、實(shí)施問(wèn)題
1. 可擴(kuò)展性差,性能提升困難
web應(yīng)用性能瓶頸基本都在數(shù)據(jù)庫(kù)上。這個(gè)系統(tǒng)使用mysql作為數(shù)據(jù)庫(kù)。三個(gè)應(yīng)用對(duì)應(yīng)三個(gè)數(shù)據(jù)庫(kù)。沒(méi)有讀寫(xiě)分離。讀寫(xiě)都在一個(gè)庫(kù)上操作。數(shù)據(jù)量***的表當(dāng)時(shí)在5000萬(wàn)條數(shù)據(jù)。高峰期數(shù)據(jù)庫(kù)操作的QPS在1000左右,壓測(cè)結(jié)果是可以支撐2000的QPS。這個(gè)指標(biāo)令我詫異。為什么能有這么好的性能?首先是,沒(méi)有復(fù)雜的查詢(xún)邏輯,所有查詢(xún)都在一個(gè)表里操作,沒(méi)有跨表事務(wù)處理,復(fù)雜的處理,分解為多個(gè)語(yǔ)句來(lái)執(zhí)行。最復(fù)雜的一個(gè)action中,執(zhí)行了將近20次數(shù)據(jù)查詢(xún)。其次,也是最重要的因素,這里用的是SSD磁盤(pán)。從目前情況看,撐到年底應(yīng)該是可以的,這也為我們技改爭(zhēng)取了足夠的時(shí)間。盡管這樣,對(duì)mysql還是沒(méi)有把握。每次運(yùn)營(yíng)部門(mén)搞活動(dòng),我們都玩膽戰(zhàn)心驚地盯著,祈禱活動(dòng)不要太有效果。
從應(yīng)用層來(lái)看,目前讀寫(xiě)比在10:1,接口日訪(fǎng)問(wèn)量10億。高峰期訪(fǎng)問(wèn)量在300QPS。公司業(yè)務(wù)增長(zhǎng)迅猛,數(shù)據(jù)量半年翻一番,訪(fǎng)問(wèn)量預(yù)估10倍增長(zhǎng)。還有一個(gè)嚴(yán)峻的挑戰(zhàn),產(chǎn)品同學(xué)揚(yáng)言要搞秒殺,秒殺…每秒十萬(wàn)的量必須支持到。這就超過(guò)MYSQL能承受的壓力范圍,需要把讀操作切到內(nèi)存數(shù)據(jù)庫(kù)上,但是在SSH架構(gòu)下,讀寫(xiě)分離實(shí)現(xiàn)就得傷筋動(dòng)骨了。另外由于Hibernate封裝了對(duì)數(shù)據(jù)庫(kù)的操作,不用寫(xiě)SQL了,精細(xì)優(yōu)化也搞不定了。每次系統(tǒng)變慢,就得求DBA,幫看看有那些SQL被卡住了。每隔一段時(shí)間,還得請(qǐng)DBA導(dǎo)出SQL語(yǔ)句,研究怎么建索引。
2. 系統(tǒng)臃腫,學(xué)習(xí)周期長(zhǎng)
100多個(gè)接口,分為三個(gè)大項(xiàng)目。***項(xiàng)目有1300多個(gè)類(lèi),其次是600多個(gè)和300多個(gè)類(lèi)。SSH架構(gòu),SVN版本控制,resin作為容器,Nginx前置路由。路由這個(gè)讓人欣慰,它是整個(gè)重構(gòu)工作的有力支撐。純后端的項(xiàng)目,為移動(dòng)端app,PCWEB應(yīng)用提供接口。這也使得重構(gòu)工作難度大大降低。如果把前端也耦合進(jìn)來(lái),那就更酸爽了。
龐大的系統(tǒng)規(guī)模為團(tuán)隊(duì)成員接手帶來(lái)困難。 支付業(yè)務(wù)獨(dú)立出來(lái)后,開(kāi)發(fā)人員從原來(lái)的5人,在2個(gè)月內(nèi)擴(kuò)充到10人。與此同時(shí),興奮的產(chǎn)品同學(xué)也都跟打雞血一樣,各種想法紛紛變?yōu)楫a(chǎn)品,開(kāi)發(fā)壓力驟增。但是新增的同學(xué),看著幾百個(gè)類(lèi),往往一片茫然,無(wú)法下手。不知道哪些功能實(shí)現(xiàn)了,哪些功能是待改進(jìn)的。一直到3個(gè)月后,新員工才逐步進(jìn)入角色。盡管如此,還是有不少恐龍級(jí)代碼,無(wú)人敢挑戰(zhàn)。***的一個(gè)類(lèi)的規(guī)模是2000多行, 核心方法超過(guò)500行,大量重復(fù)代碼, 每次調(diào)整都以失敗告終。
3. 合作成本高
隨著項(xiàng)目組人員增加,每次新版本開(kāi)發(fā)都需要多人一起合作,修改同一個(gè)項(xiàng)目代碼。 雖然使用版本控制工具來(lái)對(duì)分支進(jìn)行管理,但是不可避免的,大量的時(shí)間花費(fèi)在代碼沖突處理上。新增功能,增強(qiáng)功能,bug修復(fù),支持各種客戶(hù)端,都在一個(gè)項(xiàng)目上進(jìn)行,需要建立不同的分支,高峰期五六個(gè)分支同時(shí)進(jìn)行都是常見(jiàn)的。這種情況下,代碼沖突的頻率非常高。一個(gè)周的小版本開(kāi)發(fā),1天時(shí)間在解決沖突都是很正常的。
4. 測(cè)試難度大
測(cè)試工作也逐步的惡化了。
- 測(cè)試環(huán)境構(gòu)建難度高。隨著分支的增加,每個(gè)進(jìn)入測(cè)試的分支,都需要準(zhǔn)備獨(dú)立的測(cè)試環(huán)境。環(huán)境構(gòu)建成本高。
- 剛測(cè)試完的功能,由于分支合并沖突處理,又得重新跑一遍。嚴(yán)重影響項(xiàng)目進(jìn)度。
5. 上線(xiàn)風(fēng)險(xiǎn)高
隨著系統(tǒng)復(fù)雜度的增加,上線(xiàn)風(fēng)險(xiǎn)也越來(lái)也大。一個(gè)小功能的修改,打印一個(gè)日志,修復(fù)一個(gè)bug,都需要整體上線(xiàn)。一旦有一個(gè)地方修改錯(cuò)了,這個(gè)系統(tǒng)就崩潰了。上線(xiàn)時(shí)間長(zhǎng),一次上線(xiàn),半個(gè)小時(shí)是必須的。
6. 引入新技術(shù)困難
互聯(lián)網(wǎng)公司對(duì)新技術(shù)的追求和使用顯得特別饑渴,SSH框架降低開(kāi)發(fā)難度和成本同時(shí),也屏蔽了其他技術(shù)的導(dǎo)入。緩存機(jī)制,數(shù)據(jù)庫(kù)優(yōu)化,讀寫(xiě)分離等,SSH有自己的一套邏輯體系,要調(diào)整姿勢(shì),成本相對(duì)高,技術(shù)難度也大,需要對(duì)實(shí)現(xiàn)底層有深入了解。
CONWAY’S LAW
很長(zhǎng)一段時(shí)間,這個(gè)系統(tǒng)是2-3位開(kāi)發(fā)人員在維護(hù),對(duì)外接口、運(yùn)營(yíng)系統(tǒng),都混雜在一起實(shí)現(xiàn),訪(fǎng)問(wèn)量也不算大。3個(gè)獨(dú)立系統(tǒng),對(duì)應(yīng)3個(gè)版本庫(kù),每個(gè)人負(fù)責(zé)1-2個(gè)系統(tǒng)。當(dāng)有新功能添加到系統(tǒng)中的時(shí)候,大家優(yōu)先考慮的是如何對(duì)現(xiàn)有系統(tǒng)進(jìn)行改進(jìn),而不會(huì)考慮是否需要建立新系統(tǒng)。而當(dāng)公司做業(yè)務(wù)調(diào)整,人員迅速增加后,原有的合作方式,就需要變更了。這就應(yīng)了所謂的康威定律:
| Any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization’s communication structure. |
我們需要一套新的機(jī)制來(lái)應(yīng)對(duì)新形式下的系統(tǒng)演化的需求。
五、分層與共享庫(kù)
避免對(duì)原有系統(tǒng)作大規(guī)模調(diào)整,我們首先考慮的是利用原系統(tǒng)分層實(shí)現(xiàn)的特點(diǎn),實(shí)現(xiàn)基于層的分工。在實(shí)踐方面,以前負(fù)責(zé)的管理系統(tǒng)的開(kāi)發(fā)項(xiàng)目,使用SSH架構(gòu)的,大部分是采用基于分層的分工:
- 將業(yè)務(wù)邏輯層、數(shù)據(jù)訪(fǎng)問(wèn)層、數(shù)據(jù)表示層封裝為可以獨(dú)立維護(hù)的庫(kù);
- 將接口層按照業(yè)務(wù)來(lái)拆分,將代碼依賴(lài)調(diào)整為庫(kù)依賴(lài)。
- 為各個(gè)獨(dú)立的庫(kù)和項(xiàng)目建立各自的代碼庫(kù)。
- 層之間通過(guò)接口來(lái)交互,基礎(chǔ)層通過(guò)單元測(cè)試來(lái)保證質(zhì)量,
這種分層的優(yōu)勢(shì)在于能夠很好地解決學(xué)習(xí)周期的問(wèn)題。每個(gè)層的技術(shù)相對(duì)獨(dú)立,開(kāi)發(fā)人員可以快速上手。
- DAO層相對(duì)簡(jiǎn)單,因而對(duì)于團(tuán)隊(duì)中的新手,可以從這個(gè)層入手,熟悉系統(tǒng)架構(gòu)和軟件過(guò)程;
- 業(yè)務(wù)邏輯層是整個(gè)系統(tǒng)的核心,由老員工來(lái)負(fù)責(zé),對(duì)上可以協(xié)助顯示層的開(kāi)發(fā),對(duì)下可以指導(dǎo)DAO層的新同學(xué)。
- 顯示層需要對(duì)HTML,CSS等技術(shù)要有所了解。
這種分工,適合10人以?xún)?nèi)的、一同辦公的團(tuán)隊(duì)。團(tuán)隊(duì)之間是緊耦合的合作關(guān)系。對(duì)于大型項(xiàng)目,首先需要對(duì)項(xiàng)目按照業(yè)務(wù)進(jìn)行切分,每個(gè)子項(xiàng)目分配到10人以?xún)?nèi)的團(tuán)隊(duì)來(lái)完成,之后對(duì)每個(gè)團(tuán)隊(duì),采用分層的分工。但采用這種合作方式,存在的問(wèn)題是:
- 要求有很好的系統(tǒng)架構(gòu)設(shè)計(jì)。需要在編碼啟動(dòng)前,將各層的接口、數(shù)據(jù)庫(kù)結(jié)構(gòu)確定下來(lái)。而這對(duì)輕架構(gòu)的互聯(lián)網(wǎng)應(yīng)用開(kāi)發(fā)來(lái)說(shuō)幾乎是不現(xiàn)實(shí)的。不少互聯(lián)網(wǎng)公司甚至都沒(méi)有架構(gòu)師的角色,有架構(gòu)師的公司,還有不少是形同虛設(shè)的。
- 團(tuán)隊(duì)內(nèi)部溝通成本高。層與層之間是緊耦合的關(guān)系,對(duì)接口的修改必須通知到所有使用方。這要求開(kāi)發(fā)人員之間建立穩(wěn)定的合作關(guān)系,通過(guò)約定俗成的規(guī)則,降低溝通成本。
- 上述各種問(wèn)題仍然存在。基礎(chǔ)庫(kù)的變更,都需要對(duì)線(xiàn)上的系統(tǒng)更新庫(kù)之后重新上線(xiàn)。
六、微服務(wù)
在開(kāi)始支付項(xiàng)目改造之前,我們剛剛完成了公司數(shù)據(jù)倉(cāng)庫(kù)項(xiàng)目的微服務(wù)架構(gòu)改進(jìn)。這個(gè)項(xiàng)目實(shí)施詳細(xì)過(guò)程,在dockone社區(qū)做了分享,詳情參見(jiàn)這里。 我們認(rèn)為調(diào)整為微服務(wù)架構(gòu)可以解決上述問(wèn)題。
1. 性能問(wèn)題
對(duì)于性能要求高的接口,可以通過(guò)建立數(shù)據(jù)緩存的方式進(jìn)行優(yōu)化。
2. 學(xué)習(xí)周期
一個(gè)項(xiàng)目?jī)H包含少數(shù)緊耦合的接口,接口的業(yè)務(wù)邏輯單一,開(kāi)發(fā)人員1-2小時(shí)通讀代碼,即可快速上手。
3. 合作成本
每個(gè)項(xiàng)目相對(duì)獨(dú)立,項(xiàng)目之間僅通過(guò)接口來(lái)交互。確定完接口后,開(kāi)發(fā)、測(cè)試、上線(xiàn),都是獨(dú)立進(jìn)行的,從而降低了溝通成本。
4. 版本控制
由于項(xiàng)目之間是接口依賴(lài)而不是代碼依賴(lài),每個(gè)項(xiàng)目都可以建立獨(dú)立的代碼庫(kù)。同時(shí)項(xiàng)目切分的比較細(xì),每個(gè)項(xiàng)目開(kāi)發(fā)時(shí),僅會(huì)有一個(gè)開(kāi)發(fā)人員對(duì)其做修改。這基本就不存在代碼合并工作,也避免了代碼合并過(guò)程中的各種問(wèn)題。實(shí)際上,基于微服務(wù)架構(gòu)的開(kāi)發(fā),我們并沒(méi)有采用分支策略,而是直接用主干開(kāi)發(fā)。
5. 測(cè)試難度
每個(gè)項(xiàng)目獨(dú)立部署、獨(dú)立測(cè)試。由于消除了代碼分支,沒(méi)有代碼合并的隱患,重復(fù)測(cè)試的工作量減少了。
6. 上線(xiàn)風(fēng)險(xiǎn)
每個(gè)項(xiàng)目獨(dú)立上線(xiàn),就算出現(xiàn)問(wèn)題了,也僅影響到少數(shù)接口。
7. 新技術(shù)
在微服務(wù)改造進(jìn)行一個(gè)季度后,各種新技術(shù)被引入到系統(tǒng)中,開(kāi)發(fā)不再局限于SSH架構(gòu)。Spark, Hadoop, Hbase等大數(shù)據(jù)處理相關(guān)的技術(shù),Couchbase, Redis等緩存系統(tǒng),都開(kāi)始在項(xiàng)目中使用,并有效地解決的業(yè)務(wù)上存在的問(wèn)題。
當(dāng)然,有利必有弊,微服務(wù)帶來(lái)的問(wèn)題,也不少,包括項(xiàng)目多、出問(wèn)題時(shí)排查難等,在實(shí)施過(guò)程中,也積累了不少的經(jīng)驗(yàn)。這些問(wèn)題,將在后續(xù)的分享中逐步做介紹。
【本文為專(zhuān)欄作者“鳳凰牌老熊”的原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)通過(guò)微信公眾號(hào)“鳳凰牌老熊”聯(lián)系作者本人】
文章題目:為什么要重構(gòu)到微服務(wù)架構(gòu)
網(wǎng)頁(yè)路徑:http://fisionsoft.com.cn/article/dhpspgd.html


咨詢(xún)
建站咨詢(xún)
