這個系列是坑系列,會說一些在系統(tǒng)設(shè)計,系統(tǒng)架構(gòu)上的坑,這些都是我想到哪說到哪,有像這篇一樣比較宏觀的坑,后面的文章也會有到具體技術(shù)細(xì)節(jié)的(比如某個函數(shù),某個系統(tǒng)調(diào)用)坑,總之,到處都是坑,這些坑有些是我經(jīng)歷過的,有些是聽說的,你也可以留言說說你遇到的坑。
這一篇,我們從重構(gòu)這個場景來看看系統(tǒng)架構(gòu)的設(shè)計中過度設(shè)計這個坑。
首先,我們這里說的重構(gòu),和《重構(gòu):改善既有代碼的設(shè)計》這本書中的重構(gòu)不太一樣,這是本好書,他主要說的是代碼級別的重構(gòu),這種重構(gòu)是需要在編碼的時候時時刻刻進(jìn)行的,更多的是一種編程思想的訓(xùn)練,而我們這篇的重構(gòu)主要是說系統(tǒng)設(shè)計的重構(gòu)。
0. 關(guān)于架構(gòu)師
在說之前先聊聊架構(gòu)師這個職位吧,這個職位最近兩年特別特別火,哪個公司沒個架構(gòu)師好像都不好意思跟人打招呼,各位架構(gòu)師打上這個標(biāo)簽后頭上就頂了一個光環(huán)了,本人也認(rèn)識各個公司的一些架構(gòu)師,我認(rèn)識的架構(gòu)師分成幾種:
系統(tǒng)架構(gòu)師,這種技術(shù)能力是最強(qiáng)的,這也是一般人眼中的架構(gòu)師了,這種架構(gòu)師一般屬于領(lǐng)域架構(gòu)師,對某個領(lǐng)域的技術(shù)有比較深入的了解
業(yè)務(wù)架構(gòu)師,我只是用了這個名字而已啊,有些地方的業(yè)務(wù)架構(gòu)師其實和上面的系統(tǒng)架構(gòu)師沒什么兩樣,但有些業(yè)務(wù)架構(gòu)師有些脫離了技術(shù)了,基本上變成了一個項目經(jīng)理的角色,各種溝通,我覺得不應(yīng)該算架構(gòu)師了。
PPT架構(gòu)師,這個顧名思義,這是"最高級"的架構(gòu)師了,只要PPT做得酷炫就OK了,這是我等達(dá)不到的程度,呵呵呵呵。
最近還有一種說法就是架構(gòu)師到底要不要會寫代碼?我的理解是沒什么可說的,必須要會寫啊,你一個架構(gòu)師,代碼都不會寫還架構(gòu)個屁啊,就算你是PPT架構(gòu)師,沒時間寫代碼,但出問題了掄起袖子來解BUG的能力得有吧,而且很關(guān)鍵的一點,架構(gòu)師面對的都是一群技術(shù)宅,你連個代碼都不會寫,你覺得下面的技術(shù)宅會看得起你么?至少你得顯得很會寫吧。
為什么說架構(gòu)師呢?因為大部分架構(gòu)上的坑都是從一個不好的設(shè)計開始的,而現(xiàn)在的各個系統(tǒng)的設(shè)計都是由架構(gòu)師來操刀的。
1. 重構(gòu)中的過度設(shè)計
技術(shù)人員最喜歡做的一件事就是重構(gòu),因為技術(shù)宅們都看不上別人的代碼,特別是需要在別人代碼上加新功能的工作更是看不上,架構(gòu)師們是技術(shù)宅的升級版,所以更加看不上別人的架構(gòu)設(shè)計,所以重構(gòu)是經(jīng)常做的事情,小的是功能模塊的重構(gòu),大的是整個系統(tǒng)的重構(gòu),總之,都是不重構(gòu)不舒服斯基。
重構(gòu)本身并沒有問題,但是需要看的是重構(gòu)的時機(jī),是不是應(yīng)該重構(gòu)了?
我們以一個例子來詳細(xì)說說重構(gòu)中的過度設(shè)計吧,你也可以想想要是遇到這樣的系統(tǒng),你是架構(gòu)師,你怎么做?歡迎留言討論。
假如有個初創(chuàng)公司,是幫企業(yè)做OA系統(tǒng)的,最開始的時候是由三個程序員小哥開發(fā)出來的,系統(tǒng)架構(gòu)很簡單,是個AllInOne的設(shè)計,開發(fā)語言PHP,就像下圖一樣。
每當(dāng)客戶有新需求,基本的操作就是加個表 --- 加個邏輯模塊 --- 修改一下界面 --- 上線,而且一般OA系統(tǒng)是部署在客戶內(nèi)部的,所以每次修改都是針對單獨客戶進(jìn)行開發(fā)。
公司發(fā)展的越來越好,有了一些大公司買了他們的OA,有錢了,請了個架構(gòu)師過來優(yōu)化優(yōu)化技術(shù)架構(gòu),架構(gòu)師叫小明(每次都黑小明),小明來了一看現(xiàn)有設(shè)計,我去,這怎么行?三天后,給出了他的建議
首先,數(shù)據(jù)層都在一個庫里面,以后數(shù)據(jù)量大的話數(shù)據(jù)庫效率太低了,首先要分庫分表,把用戶數(shù)據(jù)表和事件表橫向和縱向的拆分一下,哈希到不同的機(jī)器上去,減輕單臺機(jī)器的壓力。
其次,AllInOne的設(shè)計太臃腫了,要把各個模塊微服務(wù)化,把權(quán)限模塊,附件管理模塊拆分出去成為一個微服務(wù),提供API給其他模塊使用,方便維護(hù),后續(xù)添加新功能做一個微服務(wù)就行了,和其他模塊的耦合性急劇降低。
在數(shù)據(jù)層和業(yè)務(wù)層之間加一個代理層,用個開源的中間件,以后數(shù)據(jù)端再有分庫分表操作,對上層屏蔽細(xì)節(jié),業(yè)務(wù)人員不用關(guān)心底層數(shù)據(jù)庫細(xì)節(jié),專心寫業(yè)務(wù)邏輯就行了。
PHP性能不太行,并且界面做出來不好看,前端分離還不徹底,改成Nodejs+Angularjs來進(jìn)行前后端分離,前端人員專注頁面,做出更絢麗的頁面來,后端人員專注業(yè)務(wù)邏輯,使用RESTAPI進(jìn)行數(shù)據(jù)交互。
docker部署,每個服務(wù)啟一個docker,對運維人員友好,而且有很多工具可以看各個docker的健康狀況。
于是,整個系統(tǒng)變成下面這個樣子了。
臥槽,好高大上,乍一看,這就是一個目前比較流行的架構(gòu)圖了,有數(shù)據(jù)層,有中間件層,有業(yè)務(wù)層,有前端展示層,數(shù)據(jù)層支持分庫分表,可以無限擴(kuò)展,業(yè)務(wù)層微服務(wù)化,也可以無限擴(kuò)展,docker部署,一個image搞定部署,簡直了!除了消息隊列沒有用上以外,其他的流行東西用了個遍啊。
那么,開始干吧,把人員分成兩撥,一撥繼續(xù)維護(hù)現(xiàn)有系統(tǒng),接客戶新需求,一撥開始重構(gòu),一個月時間,把數(shù)據(jù)和功能模塊梳理了一遍,然后開始定各個服務(wù)中的接口,又用了小一個月,然后全部人員投入進(jìn)去開始編碼,又忙活了三個月,終于重構(gòu)完成了,再花一個月時間追上這4個月新來的需求,牛逼的架構(gòu)上線了!
如果小明夠厲害并且開發(fā)人員也給力的話,最好的情況就是上線后一切正常,你不是重構(gòu)么?客戶完全感覺不到有變化,繼續(xù)使用得很high。但這種概率幾乎為零,最有可能的情況是什么呢?
客戶說,臥槽,少了個功能了,臥槽,數(shù)不對了,這都是小事,新系統(tǒng)嘛,總歸有一些問題,解決這些bug就好了。
某天發(fā)現(xiàn)登錄不上了,如果在之前,跟蹤一下代碼就知道哪里有問題了,立刻解決了,現(xiàn)在已經(jīng)微服務(wù)了,你說是網(wǎng)絡(luò)問題還是權(quán)限服務(wù)問題還是邏輯問題呢?要跟蹤可沒那么容易。
某天發(fā)現(xiàn)數(shù)據(jù)寫入和讀取都有問題,最后查問題發(fā)現(xiàn)是開源的中間件偶爾抽風(fēng)了,要修改的話,得看中間件的源碼了,跪了吧。。。。
最關(guān)鍵的是,你會發(fā)現(xiàn),上了這個新的架構(gòu)以后,是耦合性降低了,但開發(fā)一個新功能的工作量比以前多了,效率反而降低了。
這就是一個典型的過度設(shè)計,過度設(shè)計特點:
完全脫離了業(yè)務(wù)場景來進(jìn)行技術(shù)架構(gòu)的設(shè)計就是過度設(shè)計。
這個例子中的業(yè)務(wù)場景是一個OA系統(tǒng),OA系統(tǒng)的主要數(shù)據(jù)是企業(yè)的人和人的數(shù)據(jù),一個企業(yè)能有多少人?一個初創(chuàng)公司,能接入10萬人的大公司做OA已經(jīng)非常不錯了吧?即便是10萬人的大公司,人的數(shù)據(jù)也就10萬條,我們在成100,算單個表1000萬條數(shù)據(jù)吧,單臺MySql完全可以hold得住,所以第一條分庫分表的設(shè)計在這個場景下就完全沒有必要,企業(yè)主最關(guān)心的是什么?是數(shù)據(jù)的安全可靠,所以你在這里把MySql換成Orecle,企業(yè)主覺得安全也會買單,并且你收入還能更多,而且換成Orecle的話,單個表上億問題也不大,何必分庫分表?
好,就算你數(shù)據(jù)量巨大,需要分庫分表,那你整個中間件干嘛?中間件的作用是屏蔽底層數(shù)據(jù)沒錯,但還有個場景是數(shù)據(jù)讀寫分離,一般是在大數(shù)據(jù)量并且有高并發(fā)需求的系統(tǒng)使用,你一OA系統(tǒng),能有多高的并發(fā)?需要用中間件這么高端的東西么?
再看看微服務(wù),為了降低系統(tǒng)的耦合度使用了微服務(wù),同樣場景也不對,什么時候需要把服務(wù)拆分出來呢?只有在當(dāng)前服務(wù)已經(jīng)耗費了單臺機(jī)器太多的資源了,單機(jī)扛不住了,才會把功能比較獨立的模塊拆分成微服務(wù)出去,因為微服務(wù)雖然降低了系統(tǒng)的耦合度,但是需要更多的考慮到系統(tǒng)的可用性和網(wǎng)絡(luò)因素造成的問題,對開發(fā)人員的要求更高,一個OA系統(tǒng),能有多大的計算量?
后面的前后端分離啊,docker部署啊,你想想各自的必要程度如何?一個OA是否需要絢麗的界面?一個OA系統(tǒng)的更新頻率有多高?是否需要docker這樣的東西來幫助部署?成本如何?再看看是否是過度設(shè)計?
最后,我覺得上面這個系統(tǒng),比較合理的修改設(shè)計是:
把數(shù)據(jù)庫加上一個主從同步,保證數(shù)據(jù)的可靠性,別數(shù)據(jù)庫掛了,那客戶可會跟你拼命,這是最重要的。
把系統(tǒng)的SQL語句梳理一遍,看看有沒有什么慢SQL,然后做針對性的優(yōu)化,比如加索引,改SQL之類的。
把后端的服務(wù)加上詳細(xì)的Log信息,這樣出了問題也好查問題,并且可以把Log收集起來做分析,看看系統(tǒng)的瓶頸在什么地方,然后再在局部做優(yōu)化也好重構(gòu)也好,這樣對系統(tǒng)的侵入性最小。
這樣下來,系統(tǒng)的性能應(yīng)該有提升,數(shù)據(jù)可靠性也增強(qiáng)了,并且也耗費不了多少資源,通過Log分析,一個局部一個局部的優(yōu)化,直到發(fā)現(xiàn)了一個大坑需要拆分服務(wù)了,再進(jìn)行服務(wù)的拆分,如果你有更好的建議,歡迎留言討論啊。
2. 重構(gòu)的理由
我覺得重構(gòu)得滿足以下幾個條件的大部分,才有重構(gòu)的必要,第一個條件是必須滿足的。
現(xiàn)有系統(tǒng)的所有功能模塊和對外接口都了解得非常清楚了。如果你沒把對外接口了解得非常清楚,重構(gòu)完了以后外部的依賴系統(tǒng)必然要跟著改,那就是個無底洞了。
現(xiàn)有系統(tǒng)有明顯的重大BUG,并且在現(xiàn)有條件下無法解決或者很難解決。如果僅僅是系統(tǒng)有BUG,那么解決BUG就好了,完全沒有必要為了BUG來重構(gòu),只有當(dāng)確實BUG已經(jīng)無法解決或者解決的成本實在太高了,才有重構(gòu)的必要。
文檔缺失或者維護(hù)人員大量離職導(dǎo)致目前系統(tǒng)的可維護(hù)性降低,很難添加新功能。如果大家都很熟悉現(xiàn)有系統(tǒng),可以很快的在上面迭代新功能,你重新來一個系統(tǒng)干什么呢?
現(xiàn)有系統(tǒng)在可預(yù)見的未來無法支撐業(yè)務(wù)的發(fā)展了。只有當(dāng)業(yè)務(wù)部門已經(jīng)跑到了技術(shù)部門前面了,可以預(yù)見得到業(yè)務(wù)發(fā)展的方向了,再來審視目前的系統(tǒng),發(fā)現(xiàn)已經(jīng)無法繼續(xù)支撐了,這時候才需要重構(gòu)現(xiàn)有系統(tǒng)。
而重構(gòu)最忌諱的用以下理由來重構(gòu)系統(tǒng)
現(xiàn)有代碼太臃腫,實現(xiàn)不完美。難道你重新實現(xiàn)一個就完美了?
這個系統(tǒng)的技術(shù)棧太陳舊,沒有使用最新的技術(shù)流,以后肯定會落伍。難道用了新技術(shù)就不會落伍?
現(xiàn)有系統(tǒng)沒有考慮高可用的情況,要是出問題了就是大問題。
這個語言就不適合做這個系統(tǒng),得用XXX語言來實現(xiàn)。雖然說每個語言都有他擅長的場景,但是一個既有系統(tǒng)更換實現(xiàn)語言,是一件成本非常高的事情。
總而言之,重構(gòu)一個系統(tǒng)最需要考慮的就一個詞:成本,需要衡量各方面的成本后,再考慮是否需要重構(gòu),這樣的重構(gòu)才是有意義的重構(gòu)。
OK,這一篇簡單的說了一下重構(gòu)場景下的過度設(shè)計的問題,后面還會有和過度設(shè)計相關(guān)的,你也可以說說你遇到的過度設(shè)計,歡迎留言給我哈。