<b></b>
這是一個坑系列,會說一些在系統設計、系統架構上的坑,這些都是我想到哪說到哪,有像這篇一樣比較宏觀的坑,後面的文章也會有到具體技術細節的(比如某個函數,某個系統調用)坑。總之,到處都是坑,這些坑有些是我經曆過的,有些是聽說的,你也可以留言說說你遇到的坑。
第一部分,我們從重構這個場景來看看系統架構的設計中過度設計這個坑。首先,我們這裡說的重構,和《重構:改善既有代碼的設計》這本書中的重構不太一樣,這是本好書,他主要說的是代碼級别的重構,這種重構是需要在編碼的時候時時刻刻進行的,更多的是一種程式設計思想的訓練,而下面我們要講的重構主要是說系統設計的重構。
關于架構師
在說之前先聊聊架構師這個職位吧,這個職位最近兩年特别特别火,哪個公司沒個架構師好像都不好意思跟人打招呼,各位架構師打上這個标簽後頭上就頂了一個光環了,本人也認識各個公司的一些架構師,我認識的架構師分成幾種:
系統架構師,這種技術能力是最強的,這也是一般人眼中的架構師了,這種架構師一般屬于領域架構師,對某個領域的技術有比較深入的了解。
業務架構師,我隻是用了這個名字而已啊,有些地方的業務架構師其實和上面的系統架構師沒什麼兩樣,但有些業務架構師有些脫離了技術了,基本上變成了一個項目經理的角色,各種溝通,我覺得不應該算架構師了。
ppt架構師,這個顧名思義,這是"最進階"的架構師了,隻要ppt做得酷炫就ok了,這是我等達不到的程度,呵呵呵呵。
最近還有一種說法就是架構師到底要不要會寫代碼?我的了解是沒什麼可說的,必須要會寫啊,你一個架構師,代碼都不會寫還架構個毛線,就算你是ppt架構師,沒時間寫代碼,但出問題了掄起袖子來解bug的能力得有吧,而且很關鍵的一點,架構師面對的都是一群技術宅,你連個代碼都不會寫,你覺得下面的技術宅會看得起你麼?至少你得顯得很會寫吧。為什麼說架構師呢?因為大部分架構上的坑都是從一個不好的設計開始的,而現在的各個系統的設計都是由架構師來操刀的。
重構過程中的過度設計
1、重構過程中的過度設計
技術人員最喜歡做的一件事就是重構,因為技術宅們都看不上别人的代碼,特别是需要在别人代碼上加新功能的工作更是看不上,架構師們是技術宅的更新版,是以更加看不上别人的架構設計,是以重構是經常做的事情,小的是功能子產品的重構,大的是整個系統的重構,總之,都是不重構不舒服斯基。重構本身并沒有問題,但是需要看的是重構的時機,是不是應該重構了?我們以一個例子來詳細說說重構中的過度設計吧,你也可以想想要是遇到這樣的系統,你是架構師,你怎麼做?歡迎留言讨論。
假如有個初創公司,是幫企業做oa系統的,最開始的時候是由三個程式員小哥開發出來的,系統架構很簡單,是個allinone的設計,開發語言php,就像下圖一樣。
每當客戶有新需求,基本的操作就是加個表---加個邏輯子產品---修改一下界面---上線,而且一般oa系統是部署在客戶内部的,是以每次修改都是針對單獨客戶進行開發。公司發展的越來越好,有了一些大公司買了他們的oa,有錢了,請了個架構師過來優化優化技術架構,架構師叫小明(每次都黑小明),小明來了一看現有設計,我去,這怎麼行?三天後,給出了他的建議:
首先,資料層都在一個庫裡面,以後資料量大的話資料庫效率太低了,首先要分庫分表,把使用者資料表和事件表橫向和縱向的拆分一下,哈希到不同的機器上去,減輕單台機器的壓力。
其次,allinone的設計太臃腫了,要把各個子產品微服務化,把權限子產品,附件管理子產品拆分出去成為一個微服務,提供api給其他子產品使用,友善維護,後續添加新功能做一個微服務就行了,和其他子產品的耦合性急劇降低。
在資料層和業務層之間加一個代理層,用個開源的中間件,以後資料端再有分庫分表操作,對上層屏蔽細節,業務人員不用關心底層資料庫細節,專心寫業務邏輯就行了。
php性能不太行,并且界面做出來不好看,前端分離還不徹底,改成nodejs+angularjs來進行前後端分離,前端人員專注頁面,做出更絢麗的頁面來,後端人員專注業務邏輯,使用restapi進行資料互動。
docker部署,每個服務啟一個docker,對運維人員友好,而且有很多工具可以看各個docker的健康狀況。
于是,整個系統變成下面這個樣子了。
卧槽,好高大上,乍一看,這就是一個目前比較流行的架構圖了,有資料層,有中間件層,有業務層,有前端展示層,資料層支援分庫分表,可以無限擴充,業務層微服務化,也可以無限擴充,docker部署,一個image搞定部署,簡直了!
除了消息隊列沒有用上以外,其他的流行東西用了個遍啊。那麼,開始幹吧,把人員分成兩撥,一撥繼續維護現有系統,接客戶新需求,一撥開始重構,一個月時間,把資料和功能子產品梳理了一遍,然後開始定各個服務中的接口,又用了小一個月,然後全部人員投入進去開始編碼,又忙活了三個月,終于重構完成了,再花一個月時間追上這4個月新來的需求,牛逼的架構上線了!如果小明夠厲害并且開發人員也給力的話,最好的情況就是上線後一切正常,你不是重構麼?客戶完全感覺不到有變化,繼續使用得很high。但這種機率幾乎為零,最有可能的情況是什麼呢?
客戶說,卧槽,少了個功能了,卧槽,數不對了,這都是小事,新系統嘛,總歸有一些問題,解決這些bug就好了。
某天發現登入不上了,如果在之前,跟蹤一下代碼就知道哪裡有問題了,立刻解決了,現在已經微服務了,你說是網絡問題還是權限服務問題還是邏輯問題呢?要跟蹤可沒那麼容易。
某天發現資料寫入和讀取都有問題,最後查問題發現是開源的中間件偶爾抽風了,要修改的話,得看中間件的源碼了,跪了吧……
最關鍵的是,你會發現,上了這個新的架構以後,是耦合性降低了,但開發一個新功能的工作量比以前多了,效率反而降低了。
這就是一個典型的過度設計,過度設計特點:
完全脫離了業務場景來進行技術架構的設計就是過度設計。
這個例子中的業務場景是一個oa系統,oa系統的主要資料是企業的人和人的資料,一個企業能有多少人?一個初創公司,能接入10萬人的大公司做oa已經非常不錯了吧?即便是10萬人的大公司,人的資料也就10萬條,我們在成100,算單個表1000萬條資料吧,單台mysql完全可以hold得住,是以第一條分庫分表的設計在這個場景下就完全沒有必要,企業主最關心的是什麼?
是資料的安全可靠,是以你在這裡把mysql換成orecle,企業主覺得安全也會買單,并且你收入還能更多,而且換成orecle的話,單個表上億問題也不大,何必分庫分表?好,就算你資料量巨大,需要分庫分表,那你整個中間件幹嘛?中間件的作用是屏蔽底層資料沒錯,但還有個場景是資料讀寫分離,一般是在大資料量并且有高并發需求的系統使用,你一oa系統,能有多高的并發?需要用中間件這麼高端的東西麼?
再看看微服務,為了降低系統的耦合度使用了微服務,同樣場景也不對,什麼時候需要把服務拆分出來呢?隻有在目前服務已經耗費了單台機器太多的資源了,單機扛不住了,才會把功能比較獨立的子產品拆分成微服務出去,因為微服務雖然降低了系統的耦合度,但是需要更多的考慮到系統的可用性和網絡因素造成的問題,對開發人員的要求更高,一個oa系統,能有多大的計算量?後面的前後端分離啊,docker部署啊,你想想各自的必要程度如何?一個oa是否需要絢麗的界面?一個oa系統的更新頻率有多高?是否需要docker這樣的東西來幫助部署?成本如何?再看看是否是過度設計?最後,我覺得上面這個系統,比較合理的修改設計是:
把資料庫加上一個主從同步,保證資料的可靠性,别資料庫挂了,那客戶可會跟你拼命,這是最重要的。
把系統的sql語句梳理一遍,看看有沒有什麼慢sql,然後做針對性的優化,比如加索引,改sql之類的。
把後端的服務加上詳細的log資訊,這樣出了問題也好查問題,并且可以把log收集起來做分析,看看系統的瓶頸在什麼地方,然後再在局部做優化也好重構也好,這樣對系統的侵入性最小。
這樣下來,系統的性能應該有提升,資料可靠性也增強了,并且也耗費不了多少資源,通過log分析,一個局部一個局部的優化,直到發現了一個大坑需要拆分服務了,再進行服務的拆分,如果你有更好的建議,歡迎留言讨論啊。
2、重構的理由
我覺得重構得滿足以下幾個條件的大部分,才有重構的必要,第一個條件是必須滿足的。
現有系統的所有功能子產品和對外接口都了解得非常清楚了。如果你沒把對外接口了解得非常清楚,重構完了以後外部的依賴系統必然要跟着改,那就是個無底洞了。
現有系統有明顯的重大bug,并且在現有條件下無法解決或者很難解決。如果僅僅是系統有bug,那麼解決bug就好了,完全沒有必要為了bug來重構,隻有當确實bug已經無法解決或者解決的成本實在太高了,才有重構的必要。
文檔缺失或者維護人員大量離職導緻目前系統的可維護性降低,很難添加新功能。如果大家都很熟悉現有系統,可以很快的在上面疊代新功能,你重新來一個系統幹什麼呢?
現有系統在可預見的未來無法支撐業務的發展了。隻有當業務部門已經跑到了技術部門前面了,可以預見得到業務發展的方向了,再來審視目前的系統,發現已經無法繼續支撐了,這時候才需要重構現有系統。
而重構最忌諱的用以下理由來重構系統
現有代碼太臃腫,實作不完美。難道你重新實作一個就完美了?
這個系統的技術棧太陳舊,沒有使用最新的技術流,以後肯定會落伍。難道用了新技術就不會落伍?
現有系統沒有考慮高可用的情況,要是出問題了就是大問題。
這個語言就不适合做這個系統,得用xxx語言來實作。雖然說每個語言都有他擅長的場景,但是一個既有系統更換實作語言,是一件成本非常高的事情。
總而言之,重構一個系統最需要考慮的就一個詞:成本,需要衡量各方面的成本後,再考慮是否需要重構,這樣的重構才是有意義的重構。
高可用架構的銀彈
上面我舉例子舉的oa系統,并不是說oa一定要這麼設計,隻是一種誇張的手法,為了說明後面的完全脫離了業務場景來進行技術架構的設計就是過度設計,并不是說oa系統太簡單是以不能這麼設計,另外,寫php效率低也隻是打個比方,并非貶低全世界最好的語言,很多人拿這兩個來噴實在沒必要。好了,下面來說說高可用吧。
1、迷信好架構有高可用的銀彈
高可用,我知道一旦帶上這個詞,不管寫什麼都會有人有不同意見,我說說我認為的高可用下的坑吧。我想很多人了解的高可用就是單台機器挂掉了整個服務不會挂掉,是以寫代碼的時候使用叢集的思想去寫代碼,比如做成無狀态的服務,保證在叢集使用的時候無狀态,單機故障不影響服務,進而達到高可用的效果。由這種思想搭建起來的系統很可能長成下面這個樣子,我想很多人都看到過這種架構模式吧。
首先,這種架構模式本身并沒什麼問題,而且也确實很好,有服務發現,有叢集,單台機器挂掉了還有其他機器可使用,在搜尋系統,推薦系統,廣告系統,網站背景系統中都在大量使用。很多人接收到的資訊是有了上圖的那種架構,那麼這個系統就變成了一個高可用的系統了,覺得這種架構模式就是高可用的一顆銀彈了。但實際上,上圖的系統解決的主要是下面的兩個問題。
資料同步,主要是公共配置這種少量資料的在各個機器間的同步。
服務發現,新增或者減少機器以後,讓其他機器能感覺得到有新節點加入或者有老節點下線了。
除了上面兩個問題以外,最後才是解決所謂的高可用的問題,這裡用了所謂兩個字,因為我覺得高可用這種東西不是一個架構的模式能解決的,一個高可用的系統是代碼級别解決的,不是靠幾個開源子產品能解決的。
有些人總認為高可用系統有銀彈,在各種論壇,會議上看到各種架構,而且基本上都用到了一些成熟的開源軟體,是以覺得有了這些以後就可以是一個高可用的系統了,我有zookeeper,那麼服務單機挂了,服務照常跑,但實際上然并卵,zookeeper解決的是外部不可控因素導緻的機器挂了,比如機器硬碟壞了,網絡斷了,這種因素導緻的服務挂了,zookeeper能解決,你代碼出問題導緻機器挂了,zookeeper下挂1000台機器也解決不了啊,一般情況下還是一挂全挂。比如一個分布式的搜尋系統,索引分片了,是以有個叢集,有50台機器,每個分片大概10台機器,并且機器可以動态增加減少,叢集用zookeeper管理,這算高可用系統嗎?
這可是一個标準的搜尋系統的高可用架構,也隻能說,在代碼優秀的前提下,這個系統高可用了,網絡問題和機器硬體問題已經比較難搞挂整個叢集了。但一旦代碼有個小bug,或者索引資料生成的時候出現了點問題,一般情況下,叢集就全挂了,談何高可用。
高可用沒有銀彈,你在各處看到的,聽到的,學習到的各種高可用架構,他們隻會告訴你這個系統架構多麼牛逼,用幾個框框框住某幾個子產品,然後告訴你,這個框框裡的服務各種突發情況都能自适應,流量洪峰來了線性加機器就能解決,對你來說卻是然并卵,他們沒有告訴你他們的代碼有多牛逼,并且隻有在這個前提下才高可用的,想純粹靠幾個框框來架構出一個高可用的系統,那是ppt架構師。
真正的高可用不用糾結架構設計,隻需要代碼的健壯,健壯的代碼加上主備系統設計,不需要其他的,基本上就是一個高可用的系統了,銀行的核心資料進行中心加上異地災備就是這樣子的,你敢說他不是高可用的?是以,寫好代碼吧,才能高可用,學習架構,更多的隻是對提高系統全局性認識的一種補充,高可用的架構不存在,存在的隻有高可用的代碼。
2、一個栗子
我前段時間看到過這樣一個系統,這是一個o2o的創業公司的背景的一個子產品,主要功能是給剛打開app的使用者提供一個個性化的推薦頁面,外部接入了一些其他系統産生的一些資料。資料從其他系統推過來以後,先是接入到一個kafka的消息隊列,資料進來了以後有一個服務的叢集擷取這個資料,不同的服務通過kafka不同的topic擷取,然後二次加工這些資料,生成一個結構化的個性化資料,把生成的資料存到redis叢集中,每個app使用者對應redis中一個key,前面的app調用api以後,直接從redis叢集中擷取資料傳回,這些個叢集都用zookeeper管理的。這麼架構出來,消息隊列是為了解決第三方資料推送太猛,做緩存用的,而redis叢集其實是為了解決前端app的高并發通路的。
我先問了一下,消息隊列這個叢集在其他系統子產品也在用,這沒問題,大家都要用嘛,部署一個叢集也很應該哈。但是這個redis叢集隻有這裡在用,這裡我覺得有點問題了,有必要做個帶zookeeper的叢集嗎?隻是為了打開app的個性化頁面,用個redis叢集?不是大家共用資源的話,我覺得完全沒必要redis叢集,一主一備足矣,還容易維護。如果你覺得單機記憶體不夠大,可以用redis2.0,開啟vm功能,突破實體記憶體的限制,redis還能自己在記憶體保持熱點資料。
你說這樣是為了解決高并發下的高可用,如果redis挂了,還能自動切換,這麼說吧,我覺得一個系統中,排除硬體故障的問題,一般情況下,等你的服務全挂光了,redis也還堅挺着。并且redis的并發能力簡直隻能用恐怖來形容,單機2,3萬的qps(資料大小2,3kb左右)完全沒什麼問題,一個創業公司的日活使用者量一般情況下也沒必要用叢集去抗并發吧?
後來,我建議他們把redis叢集幹掉,換成單機主備的,而且我發現所謂的個性化推薦其實大部分人看到的頁面是一樣的,這也很好了解,初期沒資料的情況下,個性化推薦出來的東西也不夠豐富,redis叢集的記憶體使用率其實很低,于是我進一步建議他們用nginx+lua的本地字典來緩存最熱的資料,後面挂個redis,變成一個三級緩存(redis本地磁盤,redis記憶體,nginx本地字典)。如果真的業務量上來了,換成redis叢集也很容易,現在就沒必要浪費機器資源了,畢竟創業公司嘛。
嗯,最後他們沖我投來鄙夷的目光,這架構,人家看不上,萬一突然一天使用者量暴增怎麼辦,而且最關鍵的是人家不差錢,好吧,呵呵。
3、高可用的銀彈在哪?
瞎扯了這麼多,有沒有高可用的銀彈呢?恩,優秀的代碼就是一切高可用架構的基石和銀彈,優秀的代碼加上合理的架構就是高可用的架構,一個高可用的架構不是靠開源軟體搭積木來得到的,成熟的開源軟體解決的是把一部分本應該你寫的代碼變得更優秀。
經平台及作者同意授權轉載
來源:西加加語言 訂閱号
作者:吳堅
<b>本文來自雲栖社群合作夥伴"dbaplus",原文釋出時間:2016-06-13</b>