標籤:選取器 需求變化 持久化 tty 架構師 2.3 rocketmq 服務 產品
1、通用架構概述
創業之初,我們往往會為了快速迭代出產品,而選擇最簡單的技術架構,比如LAMP架構,SSH三層架構。這些架構可以適應初期業務的快速發展,但是,隨著業務變得越來越複雜,我們會發現這些架構越來越難支撐業務的發展,出現在一個類中寫好幾千行代碼,一個方法中到處都是if else語句,如果中間遇到主程式猿離職,後面介入的程式猿幾乎無法理解這些代碼,到最後,產品越來越難迭代,只能推翻重做。如果我們在創業初始就以一種適應性較強的架構去寫代碼,後面就會少走很多彎路。下面的文章是我自己總結出來的一套架構,經過實踐,適應性還算不錯。
2、通用架構實現
總的來說我的通用架構還是以三層架構為基礎進行演變的,在經典的三層架構中,最上層的是controller,中間是service,下層是dao。在我的架構中,最上層是網關層,controller只是網關的一種,中間是業務層,service只是業務層的入口,最下層是基礎層,dao只是基礎層中的資料存放區組件。
2.1、網關層
網關層本質上是對不同的網路通訊協定的請求進行處理,比如HTTP協議,TCP協議,當然,也可以對其他協議進行處理。具體見:
2.1.1、HTTP請求
一般來自PC端和APP端的請求都是基於HTTP協議的,對於處理HTTP請求的方案,業內已經非常成熟了。首先,tomcat容器本身已經把HTTP請求處理的複雜性封裝掉了,其次,spring mvc對請求處理提供了RESTful風格的編碼方式,大大降低了開發的複雜度。我們要做的就是對controller按照業務領域劃分,比如按照訂單、會員去劃分大的領域,裡面的各種方法就是這個領域內的操作。這裡的controller就是統一網關處理層,對於每個controller的方法只做三件事,第一,將請求參數解析出來並組裝成內部參數,第二調用下層服務執行商務邏輯,第三組裝返回結果,對於異常情況,需要記錄異常堆棧日誌並轉換錯誤碼,堆棧資訊不要暴露到調用方。
2.1.2、TCP請求
對於處理TCP請求的方案,業內也已經很成熟了,比如Netty。但是,TCP請求畢竟太底層,我們往往會基於TCP協議去開發自己的協議。另外,很多分布式架構都是基於TCP協議的,比如RPC架構Dubbo,訊息架構RocketMQ等等。從單機系統到分布式系統,無非就是網關層多了處理TCP請求的邏輯,理論上底層的業務是無需感知自己到底是出於單機環境還是分布式環境,網關層的作用就是要屏蔽這種不同外部調用源的細節。在Dubbo服務端中,我們需要實現遠程介面,並對遠程服務調用進行內部的轉寄,轉寄的邏輯也很簡單,首先是解析參數並組裝內部參數,然後調用業務層的介面執行商務邏輯,最後裝配返回結果,對於異常處理也需要在這裡做掉,防止異常暴露給外部應用。
2.1.3、小結
網關層本質是對協議進行處理,同時將商務邏輯收斂到網關層,而不是暴露給外部,當內部商務邏輯進行重構的時候,外部調用方就不需要感知這些變化,當外部調用源增加時,內部商務邏輯不需要感知這種變化,從而將外部調用方和內部商務邏輯進行瞭解耦。
2.2、業務層
業務層是一個系統,無論是單機系統還是分布式系統群中的某個業務系統,業務層都是承載商務程序和規則的地方。業務層從外到內包含三層:第一層是商務服務,第二層是商務程序,第三層是業務組件。具體如:
2.2.1、商務服務
商務服務是業務層對外的統一門面,它由三方面組成:業務介面、入參、出參。
a) 業務介面
一個業務介面代表一個領域的商務服務,比如訂單域的商務服務就由介面OrderService表示,會員域的商務服務就由介面MemberService表示。介面可以按照執行性質分為讀介面和寫介面,比如OrderReadService和OrderWriteService。讀寫分離的好處是可以對叢集進行讀寫分組,從而管理流量,當然,單機系統讀寫分離意義不是太大。領域內的操作則以業務介面中的方法的形式體現,比如訂單域有下單createOrder,取消訂單cancelOrder等等操作。對於這些操作,盡量設計出有業務含義的方法,而不是增刪改查,當然,對於一些簡單的業務,也只能增刪改查。
b)入參
接下來,是入參的設計。入參對於讀方法,比較簡單,不做討論。對於寫方法,我們將入參設計成有層次的資料模型。首先需要設計出公用的資料模型,比如訂單資料模型,商家資料模型,商品資料模型等,然後將這些資料模型和一些特定業務下的個性資料結合,組成Request對象,這個request對象按照不同業務操作不同而不同,對應的返回結果就是response,它也是隨著不同業務返回的參數不同。
舉個例子,拿下餐飲訂單來說,首先,我們應該識別出這些商務程序中一些比較基礎的資料模型,比如餐飲領域的菜品、桌位等,這些模型之所以說是基本模型,是因為,不管下什麼餐飲訂單,菜品和桌位肯定是逃不了的,它們是可以被複用的!因此,我們分別為這些基本模型設計相對於的DO(Domian Object):DishDO(菜品)、BoardDO(桌位)等等,接下來,我們為下餐飲訂單設計一個請求對象DishOrderCreateRequest其中DishOrderCreateRequest內部包含了DishDO和BoardDO,另外會包含一些特定的屬性,比如人數啊,折扣啊等等,這樣一來就能做到通用和靈活兼顧,DishOrderCreateRequest代表的個人化的靈活的業務入參,而DishDO和BoardDO等則代表了不易變化的基本模型。
c) 出參
最後,是出參的設計。對於寫方法,一般出參比較簡單。對於讀方法,出參往往是一個結構與層次比較複雜的組合對象。比如查詢一個訂單,這個訂單有訂單基本資料,還有商品資訊,收貨人地址資訊等。在設計出參的時候,結構上要設計成組合對象,但是真正查詢的時候,通過查詢選取器,去查詢不同的組合對象。比如查詢選取器設定商品查詢為true,地址查詢為false,那麼這次查詢出的訂單就只包含商品,而不包含地址。
2.2.2、商務程序
商務程序其實就是對商務規則的解釋,只是這種解釋使用代碼去實現的,我們要做的其實就是準確翻譯這些商務規則,並維護好這些商務規則。
商務程序中可以大致分為三種動作節點,1、組裝參數節點 2、規則判斷節點 3、執行動作節點,其中每個動作節點都是一些業務代碼的片段。舉個例子,下餐飲訂單,我們第一步就是將上層傳入的參數組裝出一個基礎的DishOrderDO(組裝參數節點),然後按照特定的規則去填充這個DishOrderDO(規則判斷節點),然後就是調用DAO去建立DishOrderDO(執行動作節點)。
商務程序是最容易變化的地方,要想維護好商務程序並不容易,總的思想是將大的商務程序拆分成小的商務程序,抽出每個商務程序中共有的程式碼片段,變成可維護的業務組件。
2.2.2、業務組件
a) 基礎組件
業務組件其實是將一些內聚的可複用的程式碼片段進行封裝。和商務程序中的三種業務節點相對應,業務組件也分為三種:組裝參數組件 、規則判斷組件 、動作執行業務組件。業務組件的抽象往往是對業務有了深刻理解之後才進行的,盲目地進行業務組件的抽象,往往到頭來白忙活。
b) 能力
對業務組件進行進一步抽象,可以得到能力。業務能力是具有一定複用性的組件的組合,比如發簡訊能力=組裝簡訊參數組件+發簡訊組件。對於發簡訊能力,可以被不同的商務程序複用,比如訂單下單成功發簡訊,支付成功發簡訊,邏輯都是相似的,只有內容不同。能力是一種粒度比較大的組件,粒度越大,往往複用性就越小,對能力的抽取,也是基於對特定業務深刻的理解,沒有一勞永逸的銀彈。
c)更高緯度的抽象
經過本人的實踐,對於互連網這樣的需求變化極快的情境,更高緯度的組件抽象往往性價比很低,不建議大家去做。
2.3、基礎層
基礎層包含兩個部分,第一是介面定義,第二是技術組件。
2.3.1、介面定義
介面定義是按照不同的技術架構,同時結合業務需要,設計出合理的介面,對於業務組件來說,它們只會感知技術介面,而不會去感知技術實現,我們也不應該將具體的技術細節向上暴露,這也就是所謂的面向介面編程。技術介面往往是業務與技術之間的橋樑,介面本身是含有業務含義的,最常見的就是DAO介面,我們設計DAO介面的時候,不會設計成insert、update、query這樣業務無關的介面,而是設計成insertUser,updateUserById等等和業務相關的介面,同樣的道理,設計緩衝介面的時候,也不能設計成put、get這樣的介面,而應該設計成cacheUser,deprecateUser這樣的介面。
2.3.2、技術組件
單機系統的技術組件一般來說分兩種,一種是通用的技術組件,比如:資料存放區、緩衝、訊息和調度任務、事務、鎖。一種是基礎設施,比如spring容器,tomcat容器。下面稍微談談通用技術組件。
資料存放區:資料存放區包括關係型資料庫、非關係型資料庫以及檔案儲存體系統。關係型資料庫,比如MySQL,適合存放絕大部分業務資料。非關係型資料庫,比如hbase,可以存放曆史日誌,也可以對曆史的MySQL資料進行歸檔。檔案儲存體系統,一般都是基於Linux檔案系統,比片、html檔案等等,也有基於HDFS的,用於大資料分析。
緩衝:緩衝按回應時間分,可以分為納秒級緩衝,毫秒級緩衝和百毫秒級緩衝。納秒級緩衝就是一般的基於本地記憶體的緩衝,比如encache,毫秒級緩衝一般是集中式的記憶體緩衝,比如memcache,由於訪問時遠程調用,因此回應時間會延長到幾毫秒,百毫秒級緩衝一般是集中式可持久化的緩衝,比如redis,由於存在遠端存取以及緩衝擊穿導致的讀取持久化記錄,它的回應時間會更長些,到幾十甚至上百毫秒。單機系統一般用本地記憶體緩衝就夠了,當緩衝被擊穿的時候,直接存取資料庫。
訊息和調度任務:訊息和調度任務本質都是一種非同步化的手段,區別在於訊息無法控制非同步時間,而調度任務可以。一般,訊息發送出去後,監聽訊息的系統會立即收到訊息,從而立即觸發商務邏輯的執行,而調度任務則會按照調度規則,一次或者多次的執行商務邏輯。單機系統中訊息和調度任務用到的比較少,在做日誌監控的時候可能會用到訊息,在進行資料報表統計的時候可能會用到調度任務。
事務:事務本質都是基於資料庫去實現的,單機系統的事務就是依賴資料庫的事務,我們可以使用spring-tx的事務模板進行事務操作,在商務邏輯開發中,一定要把握事務的大小,建議把業務比較緊密的一堆資料庫操作放在一個事務裡,不要隨意的為每個方法都開啟事務。
鎖:單機系統中主要用到兩種鎖:樂觀鎖和悲觀鎖。樂觀鎖依靠在資料庫的業務表加版本欄位來實現,每次更新都會去判斷版本是否變化,如果變化則需要重試,這種鎖的粒度比較小。悲觀鎖是基於JDK的Lock介面的,對一個商務程序進行加鎖和釋放鎖的操作,鎖的粒度比較粗。
3、總結
以上是我經過很長一段時間的實踐後摸索出來的業務技術架構,自認為還算通用,而且能夠在一定程度上支撐易變的業務。當然這套架構肯定不是銀彈,不可能解決所有業務情境,所以最終還是需要圍繞到具體的情境加以借鑒。
關於作者
吳極心,目前在杭州旅居星球擔任架構師,專註於技術架構治和產品架構。
構建一個較為通用的業務技術架構