1、單機系統(tǒng)應(yīng)用概述
? ? ? ?幾乎所有的互聯(lián)網(wǎng)企業(yè),在一開始都是從單機系統(tǒng)開始的,最早的淘寶就是基于LAMP的單機系統(tǒng),數(shù)據(jù)庫和應(yīng)用服務(wù)器部署在一臺物理機器上。對于大部分的人來說,單機系統(tǒng)往往很簡單,幾乎就是對數(shù)據(jù)庫的增刪改查,并沒有什么架構(gòu)可言,但我并不這么認為,即使是簡單的單機應(yīng)用系統(tǒng),也可以有架構(gòu),而且,好的架構(gòu)一定可以減少業(yè)務(wù)對系統(tǒng)的沖擊,減少不必要的返工量。
? ? ? ?對于單機系統(tǒng)來說,本質(zhì)上它就干了一件事:處理外部系統(tǒng)來的請求,將必要的數(shù)據(jù)變更保存到存儲系統(tǒng)或者根據(jù)一定的規(guī)則查詢數(shù)據(jù),然后對外部系統(tǒng)進行響應(yīng),返回響應(yīng)結(jié)果。將這件事大致的延展一些,就涉及到三部分內(nèi)容,第一部分類似于入口,這個入口是需要處理請求,而且肯定不是一種來源的請求,第二是要按照真實業(yè)務(wù)規(guī)則處理請求,第三是必要的數(shù)據(jù)存取?;谶@三部分,我們很容易想到目前比較流行的controller-service-dao的三層架構(gòu)。其中controller用于處理Web端的HTTP請求,service用于處理業(yè)務(wù)邏輯,dao用于處理數(shù)據(jù)庫操作。這種三層架構(gòu)雖然流行,但并非通用的一般架構(gòu),就拿controller來說,無論基于spring mvc還是Struts2,它都是處理HTTP請求的,而不是處理TCP請求的。這些MVC框架都是基于tomcat跑的,而tomcat恰恰是用于處理HTTP請求的。如果這時候我們需要處理TCP,這些框架就失效了。下面就說一下我眼中的一般架構(gòu):
? ? ? ?總的來說還是三層架構(gòu),不過不是上面的那三層,而是更具擴展性的三層架構(gòu)。第一層是網(wǎng)關(guān)層,用于處理不同類型的請求。第二層是業(yè)務(wù)層,用于處理業(yè)務(wù)邏輯,第三層是基礎(chǔ)層,它不僅僅包含DAO這樣的數(shù)據(jù)操作,還包括其他無關(guān)業(yè)務(wù)的技術(shù)組件,比如緩存,調(diào)度任務(wù),事務(wù),消息等等。
2、單機應(yīng)用系統(tǒng)三層架構(gòu)
2.1、網(wǎng)關(guān)層
網(wǎng)關(guān)層本質(zhì)上是對不同的網(wǎng)絡(luò)協(xié)議的請求進行處理,比如HTTP協(xié)議,TCP協(xié)議,當(dāng)然,也可以對其他協(xié)議進行處理。
2.1.1、HTTP請求
對于處理HTTP請求的方案,業(yè)內(nèi)已經(jīng)非常成熟了。首先,tomcat容器本身已經(jīng)把HTTP請求處理的復(fù)雜性封裝掉了,其次,spring mvc對請求處理提供了RESTful風(fēng)格的編碼方式,大大降低了開發(fā)的復(fù)雜度。我們要做的就是對controller按照業(yè)務(wù)領(lǐng)域劃分,比如按照訂單、會員去劃分大的領(lǐng)域,里面的各種方法就是這個領(lǐng)域內(nèi)的操作。每個controller的方法只做三件事,第一,將請求參數(shù)解析出來并組裝成內(nèi)部參數(shù),第二調(diào)用下層服務(wù)執(zhí)行業(yè)務(wù)邏輯,第三組裝返回結(jié)果,對于異常情況,需要記錄異常堆棧日志并轉(zhuǎn)換錯誤碼,堆棧信息不要暴露到調(diào)用方。
2.1.2、TCP請求
對于處理TCP請求的方案,業(yè)內(nèi)也已經(jīng)很成熟了,比如Netty。但是,TCP請求畢竟太底層,我們往往會基于TCP協(xié)議去開發(fā)自己的協(xié)議。另外,很多分布式框架都是基于TCP協(xié)議的,比如RPC框架Dubbo,消息框架RocketMQ等等。從單機系統(tǒng)到分布式系統(tǒng),無非就是網(wǎng)關(guān)層多了處理TCP請求的邏輯,理論上底層的業(yè)務(wù)是無需感知自己到底是出于單機環(huán)境還是分布式環(huán)境,網(wǎng)關(guān)層的作用就是要屏蔽這種不同外部調(diào)用源的細節(jié)。
2.1.3、小結(jié)
網(wǎng)關(guān)層本質(zhì)是對協(xié)議進行處理,同時將業(yè)務(wù)邏輯收斂到網(wǎng)關(guān)層,而不是暴露給外部,當(dāng)內(nèi)部業(yè)務(wù)邏輯進行重構(gòu)的時候,外部調(diào)用方就不需要感知這些變化,當(dāng)外部調(diào)用源增加時,內(nèi)部業(yè)務(wù)邏輯不需要感知這種變化,從而將外部調(diào)用方和內(nèi)部業(yè)務(wù)邏輯進行了解耦。
2.2、業(yè)務(wù)層
業(yè)務(wù)層是一個系統(tǒng),無論是單機系統(tǒng)還是分布式系統(tǒng)群中的某個業(yè)務(wù)系統(tǒng),業(yè)務(wù)層都是承載業(yè)務(wù)流程和規(guī)則的地方。業(yè)務(wù)層從外到內(nèi)包含三層:第一層是業(yè)務(wù)服務(wù),第二層是業(yè)務(wù)流程,第三層是業(yè)務(wù)組件。
2.2.1、業(yè)務(wù)服務(wù)
業(yè)務(wù)服務(wù)是業(yè)務(wù)層對外的統(tǒng)一門面,它由三方面組成:業(yè)務(wù)接口、入?yún)?、出參?/p>
a) 業(yè)務(wù)接口
? ? ?一個業(yè)務(wù)接口代表一個領(lǐng)域的業(yè)務(wù)服務(wù),比如訂單域的業(yè)務(wù)服務(wù)就由接口OrderService表示,會員域的業(yè)務(wù)服務(wù)就由接口MemberService表示。接口可以按照執(zhí)行性質(zhì)分為讀接口和寫接口,比如OrderReadService和OrderWriteService。讀寫分離的好處是可以對集群進行讀寫分組,從而管理流量,當(dāng)然,單機系統(tǒng)讀寫分離意義不是太大。領(lǐng)域內(nèi)的操作則以業(yè)務(wù)接口中的方法的形式體現(xiàn),比如訂單域有下單createOrder,取消訂單cancelOrder等等操作。對于這些操作,盡量設(shè)計出有業(yè)務(wù)含義的方法,而不是增刪改查,當(dāng)然,對于一些簡單的業(yè)務(wù),也只能增刪改查。
b)入?yún)?/p>
接下來,是入?yún)⒌脑O(shè)計。入?yún)τ谧x方法,比較簡單,不做討論。對于寫方法,我們將入?yún)⒃O(shè)計成有層次的數(shù)據(jù)模型。首先需要設(shè)計出公共的數(shù)據(jù)模型,比如訂單數(shù)據(jù)模型,商家數(shù)據(jù)模型,商品數(shù)據(jù)模型等,然后將這些數(shù)據(jù)模型和一些特定業(yè)務(wù)下的個性數(shù)據(jù)結(jié)合,組成Request對象,這個request對象按照不同業(yè)務(wù)操作不同而不同,對應(yīng)的返回結(jié)果就是response,它也是隨著不同業(yè)務(wù)返回的參數(shù)不同。
舉個例子,拿下餐飲訂單來說,首先,我們應(yīng)該識別出這些業(yè)務(wù)流程中一些比較基礎(chǔ)的數(shù)據(jù)模型,比如餐飲領(lǐng)域的菜品、桌位等,這些模型之所以說是基礎(chǔ)模型,是因為,不管下什么餐飲訂單,菜品和桌位肯定是逃不了的,它們是可以被復(fù)用的!因此,我們分別為這些基礎(chǔ)模型設(shè)計相對于的DO(Domian Object):DishDO(菜品)、BoardDO(桌位)等等,接下來,我們?yōu)橄虏惋嬘唵卧O(shè)計一個請求對象DishOrderCreateRequest其中DishOrderCreateRequest內(nèi)部包含了DishDO和BoardDO,另外會包含一些特定的屬性,比如人數(shù)啊,折扣啊等等,這樣一來就能做到通用和靈活兼顧,DishOrderCreateRequest代表的個性化的靈活的業(yè)務(wù)入?yún)ⅲ鳧ishDO和BoardDO等則代表了不易變化的基礎(chǔ)模型。
c) 出參
最后,是出參的設(shè)計。對于寫方法,一般出參比較簡單。對于讀方法,出參往往是一個結(jié)構(gòu)與層次比較復(fù)雜的組合對象。比如查詢一個訂單,這個訂單有訂單基本信息,還有商品信息,收貨人地址信息等。在設(shè)計出參的時候,結(jié)構(gòu)上要設(shè)計成組合對象,但是真正查詢的時候,通過查詢選擇器,去查詢不同的組合對象。比如查詢選擇器設(shè)置商品查詢?yōu)閠rue,地址查詢?yōu)閒alse,那么這次查詢出的訂單就只包含商品,而不包含地址。
2.2.2、業(yè)務(wù)流程
? ? ?業(yè)務(wù)流程其實就是對業(yè)務(wù)規(guī)則的解釋,只是這種解釋使用代碼去實現(xiàn)的,我們要做的其實就是準確翻譯這些業(yè)務(wù)規(guī)則,并維護好這些業(yè)務(wù)規(guī)則。
? ? ?業(yè)務(wù)流程中可以大致分為三種動作節(jié)點,1、組裝參數(shù)節(jié)點? 2、規(guī)則判斷節(jié)點? 3、執(zhí)行動作節(jié)點,其中每個動作節(jié)點都是一些業(yè)務(wù)代碼的片段。舉個例子,下餐飲訂單,我們第一步就是將上層傳入的參數(shù)組裝出一個基礎(chǔ)的DishOrderDO(組裝參數(shù)節(jié)點),然后按照特定的規(guī)則去填充這個DishOrderDO(規(guī)則判斷節(jié)點),然后就是調(diào)用DAO去創(chuàng)建DishOrderDO(執(zhí)行動作節(jié)點)。
? ? ?業(yè)務(wù)流程是最容易變化的地方,要想維護好業(yè)務(wù)流程并不容易,總的思想是將大的業(yè)務(wù)流程拆分成小的業(yè)務(wù)流程,抽出每個業(yè)務(wù)流程中共有的代碼片段,變成可維護的業(yè)務(wù)組件。
2.2.2、業(yè)務(wù)組件
a) 基礎(chǔ)組件
業(yè)務(wù)組件其實是將一些內(nèi)聚的可復(fù)用的代碼片段進行封裝。和業(yè)務(wù)流程中的三種業(yè)務(wù)節(jié)點相對應(yīng),業(yè)務(wù)組件也分為三種:組裝參數(shù)組件 、規(guī)則判斷組件 、動作執(zhí)行業(yè)務(wù)組件。業(yè)務(wù)組件的抽象往往是對業(yè)務(wù)有了深刻理解之后才進行的,盲目地進行業(yè)務(wù)組件的抽象,往往到頭來白忙活。
b) 能力
對業(yè)務(wù)組件進行進一步抽象,可以得到能力。業(yè)務(wù)能力是具有一定復(fù)用性的組件的組合,比如發(fā)短信能力=組裝短信參數(shù)組件+發(fā)短信組件。對于發(fā)短信能力,可以被不同的業(yè)務(wù)流程復(fù)用,比如訂單下單成功發(fā)短信,支付成功發(fā)短信,邏輯都是相似的,只有內(nèi)容不同。能力是一種粒度比較大的組件,粒度越大,往往復(fù)用性就越小,對能力的抽取,也是基于對特定業(yè)務(wù)深刻的理解,沒有一勞永逸的銀彈。
c)更高緯度的抽象
經(jīng)過本人的實踐,對于互聯(lián)網(wǎng)這樣的需求變化極快的場景,更高緯度的組件抽象往往性價比很低,不建議大家去做。
2.3、基礎(chǔ)層
基礎(chǔ)層包含兩個部分,第一是接口定義,第二是技術(shù)組件。
2.3.1、接口定義
接口定義是按照不同的技術(shù)框架,同時結(jié)合業(yè)務(wù)需要,設(shè)計出合理的接口,對于業(yè)務(wù)組件來說,它們只會感知技術(shù)接口,而不會去感知技術(shù)實現(xiàn),我們也不應(yīng)該將具體的技術(shù)細節(jié)向上暴露,這也就是所謂的面向接口編程。技術(shù)接口往往是業(yè)務(wù)與技術(shù)之間的橋梁,接口本身是含有業(yè)務(wù)含義的,最常見的就是DAO接口,我們設(shè)計DAO接口的時候,不會設(shè)計成insert、update、query這樣業(yè)務(wù)無關(guān)的接口,而是設(shè)計成insertUser,updateUserById等等和業(yè)務(wù)相關(guān)的接口,同樣的道理,設(shè)計緩存接口的時候,也不能設(shè)計成put、get這樣的接口,而應(yīng)該設(shè)計成cacheUser,deprecateUser這樣的接口。
2.3.2、技術(shù)組件
單機系統(tǒng)的技術(shù)組件一般來說分兩種,一種是通用的技術(shù)組件,比如:數(shù)據(jù)存儲、緩存、消息和調(diào)度任務(wù)、事務(wù)、鎖。一種是面向特定領(lǐng)域的技術(shù)組件,比如使用個推進行短信推送,使用七牛sdk進行云存儲。下面稍微談?wù)勍ㄓ眉夹g(shù)組件。
數(shù)據(jù)存儲:數(shù)據(jù)存儲包括關(guān)系型數(shù)據(jù)庫、非關(guān)系型數(shù)據(jù)庫以及文件存儲系統(tǒng)。關(guān)系型數(shù)據(jù)庫,比如MySQL,適合存放絕大部分業(yè)務(wù)數(shù)據(jù)。非關(guān)系型數(shù)據(jù)庫,比如hbase,可以存放歷史日志,也可以對歷史的MySQL數(shù)據(jù)進行歸檔。文件存儲系統(tǒng),一般都是基于Linux文件系統(tǒng),比如圖片、html文件等等,也有基于HDFS的,用于大數(shù)據(jù)分析。
緩存:緩存按響應(yīng)時間分,可以分為納秒級緩存,毫秒級緩存和百毫秒級緩存。納秒級緩存就是一般的基于本地內(nèi)存的緩存,比如encache,毫秒級緩存一般是集中式的內(nèi)存緩存,比如memcache,由于訪問時遠程調(diào)用,因此響應(yīng)時間會延長到幾毫秒,百毫秒級緩存一般是集中式可持久化的緩存,比如redis,由于存在遠程訪問以及緩存擊穿導(dǎo)致的讀取持久化記錄,它的響應(yīng)時間會更長些,到幾十甚至上百毫秒。單機系統(tǒng)一般用本地內(nèi)存緩存就夠了,當(dāng)緩存被擊穿的時候,直接訪問數(shù)據(jù)庫。
消息和調(diào)度任務(wù):消息和調(diào)度任務(wù)本質(zhì)都是一種異步化的手段,區(qū)別在于消息無法控制異步的時間,而調(diào)度任務(wù)可以。一般,消息發(fā)送出去后,監(jiān)聽消息的系統(tǒng)會立即收到消息,從而立即觸發(fā)業(yè)務(wù)邏輯的執(zhí)行,而調(diào)度任務(wù)則會按照調(diào)度規(guī)則,一次或者多次的執(zhí)行業(yè)務(wù)邏輯。單機系統(tǒng)中消息和調(diào)度任務(wù)用到的比較少,在做日志監(jiān)控的時候可能會用到消息,在進行數(shù)據(jù)報表統(tǒng)計的時候可能會用到調(diào)度任務(wù)。
事務(wù):事務(wù)本質(zhì)都是基于數(shù)據(jù)庫去實現(xiàn)的,單機系統(tǒng)的事務(wù)就是依賴數(shù)據(jù)庫的事務(wù),我們可以使用spring-tx的事務(wù)模板進行事務(wù)操作,在業(yè)務(wù)邏輯開發(fā)中,一定要把握事務(wù)的大小,建議把業(yè)務(wù)比較緊密的一堆數(shù)據(jù)庫操作放在一個事務(wù)里,不要隨意的為每個方法都開啟事務(wù)。
鎖:單機系統(tǒng)中主要用到兩種鎖:樂觀鎖和悲觀鎖。樂觀鎖依靠在數(shù)據(jù)庫的業(yè)務(wù)表加版本字段來實現(xiàn),每次更新都會去判斷版本是否變化,如果變化則需要重試,這種鎖的粒度比較小。悲觀鎖是基于JDK的Lock接口的,對一個業(yè)務(wù)流程進行加鎖和釋放鎖的操作,鎖的粒度比較粗。
3、當(dāng)單機變成分布式,上面的架構(gòu)會變成什么樣?
從單機系統(tǒng)到分布式改造,最常見的改造就是服務(wù)化、消息異步化和數(shù)據(jù)庫分庫分表,下面看看從單機系統(tǒng)到分布式系統(tǒng)演變過程中,上面的架構(gòu)會有什么變化。
3.1、服務(wù)化
服務(wù)化簡單的說就是,原來是單機系統(tǒng)A,現(xiàn)在從A分離出一個B,B承載了A中的一部分功能,原來只要在A內(nèi)部完成的功能,這時候就需要一次遠程調(diào)用去調(diào)B完成。當(dāng)進行了服務(wù)化改造以后,單機系統(tǒng)的架構(gòu)會怎么變化呢?
3.1.1、網(wǎng)關(guān)層改造
首先B系統(tǒng)也會有網(wǎng)關(guān)層,B會在網(wǎng)關(guān)層提供Dubbo服務(wù),并注冊到zk,A會在基礎(chǔ)層啟動一個Dubbo客戶端,A在業(yè)務(wù)流程中調(diào)用基礎(chǔ)層的Dubbo客戶端進行B服務(wù)的調(diào)用。
3.1.2、業(yè)務(wù)層改造
A在業(yè)務(wù)流程中,原來是本地操作,這時候需要調(diào)用基礎(chǔ)層的Dubbo客戶端進行遠程操作,其他地方不會變化。
3.1.3、基礎(chǔ)層改造
A會在基礎(chǔ)層新增Dubbo客戶端的技術(shù)組件,這個技術(shù)組件主要是對Dubbo client進行封裝,處理一些異常,對內(nèi)暴露合理的接口。
從上面來看,從單機架構(gòu)變成SOA架構(gòu),變化不是太多,原有的架構(gòu)受到的沖擊沒有想象中的大,A中的業(yè)務(wù)邏輯對SOA化感知不大。當(dāng)然,以上只是架構(gòu)變化不大,開發(fā)、運維和測試的改變其實挺大的,在此不談這些。
3.2、消息異步化
消息異步化是在SOA化基礎(chǔ)上做的,SOA化后,一次調(diào)用后臺可能會經(jīng)過很多系統(tǒng),這些系統(tǒng)如果都是遠程調(diào)用的話就會有問題,一個問題是調(diào)用耗時太長,容易超時,第二個是系統(tǒng)調(diào)用中的強弱依賴倒置,一條鏈路經(jīng)過A-B-C-D,如果C是弱依賴(比如調(diào)用C就是記錄一下流水日志),其他都是強依賴,那么只要C掛了,整條鏈路就掛了,因此,我們需要把C解耦出去,解耦方法就是消息異步化,鏈路就變成了A-B-D----C,其中虛線表示異步解耦。下面看看消息異步化后架構(gòu)的改造
3.2.1、網(wǎng)關(guān)層改造
網(wǎng)關(guān)層主要是對消息的接受,C需要在網(wǎng)關(guān)層引入消息監(jiān)聽器,監(jiān)聽主鏈路發(fā)出的消息。
3.2.2、業(yè)務(wù)層改造
D需要在業(yè)務(wù)層調(diào)用基礎(chǔ)層的消息發(fā)送組件,進行消息的廣播,其實對于D來說,就是多了一個動作執(zhí)行節(jié)點,其他業(yè)務(wù)流程不會受到影響。
3.2.3、基礎(chǔ)層改造
D需要在基礎(chǔ)層對構(gòu)建發(fā)消息的技術(shù)組件,對業(yè)務(wù)層暴露發(fā)消息的接口。
和SOA化一樣,消息異步化對原有架構(gòu)有一定影響,但影響不大。當(dāng)然,對開發(fā)測試運維影響還是挺大的。。。
3.3、數(shù)據(jù)庫分庫分表
數(shù)據(jù)庫分庫分表主要是數(shù)據(jù)庫壓力太大,撐不住應(yīng)用集群的調(diào)用。一般中前期都是進行數(shù)據(jù)庫分庫,也就是把原來一個庫中的幾十張表,分成多個庫,每個庫中的表都是內(nèi)聚的,這些內(nèi)聚的表單獨部署一個數(shù)據(jù)庫。當(dāng)需要分表的時候,往往業(yè)務(wù)量已經(jīng)很大了(至少也要C輪了吧。。)。數(shù)據(jù)庫分庫分表本質(zhì)是數(shù)據(jù)存取方式的改造,因此不涉及網(wǎng)關(guān)層和業(yè)務(wù)層,只涉及基礎(chǔ)層,基礎(chǔ)層可能會引入類似TDDL這樣的中間件,不過改動也不是太大,業(yè)務(wù)往往感受不到,當(dāng)然,從單機數(shù)據(jù)庫到分庫分表,DB運維一定會有質(zhì)的變化,這時候就會有專門的DBA來維護DB了。。。
4、總結(jié)
扯了這么多,最后只想說一句話,系統(tǒng)架構(gòu)這東西,其實很多時候都是面向業(yè)務(wù)的,和技術(shù)關(guān)系不是太大。。。因此,喜歡研究架構(gòu)的同學(xué),可以更往前走一步,去研究一下產(chǎn)品架構(gòu),你會發(fā)現(xiàn),平日里吵的不可開交的產(chǎn)品和技術(shù),在架構(gòu)這條路上,其實是穿一條褲子的,所謂的相愛相殺,大抵如此。。。
版權(quán)印為您的作品印上版權(quán)