新來老大年前開會說各位同學,公司業務越來越重,未來幾年要成倍增長......,要梳理出一套新架構,才能更好的支援N萬使用者.....,以後升職加薪當上....打敗.....
想想還有點小激動呢,於是過年時樓主趁等待相親妹紙無聊的時候,反思了目前系統現狀,構思設計新架構如下。
閱讀目錄:
- 現有系統
- 新架構
2.1 邏輯架構圖
2.2 解釋說明 - 系統實施
3.1 SOA管理中心
3.2 釋出服務
3.3 訂閱服務
3.4 採蘑菇示例 - 設計目標
4.1 儘可能少的侵入
4.2 服務自治&&水平擴充套件
4.3 系統升級降級 - 常見問題
5.1 ClientApi VS ServiceApi
5.2 聚合服務
5.3 服務分級 - 總結心得
現有系統
鄙司業務比較重,系統也有些年頭,各研發團隊、系統都比較穩定了。所以不差也不太好,總之也能滿足現有需求。但近2年O2O,移動網際網路等大行其道,老大們也都心動了,開始磨刀霍霍了。而現有系統應對複雜的變化,在一些地方頗顯不足:
- 介面沒有統一管理
- 很多元件無法複用/重複造輪子
- 模組間職責不清,耦合過深
- 聯調排查問題比較慢
- 開發前規劃不足,形成堆積
- 缺乏API規範/文件較少
新架構
- A開頭:是系統級別,可以獨立部署。即可寄宿在IIS/WindowsService等上面。
- B開頭:是模組級別,不可獨立部署。相對獨立的功能模組,而又不大,所以依附其他系統或者和多個模組組成一個子系統。模組級別在專案中可以分多層,可根據分數升級成子系統(見系統升級降級)。
- C開頭:是元件級別,不可獨立部署。大多數是公共性元件,一般是單獨類庫存在,以DLL提供使用。一些開源元件也歸到這裡,例:Autofac,FluentData。 需要自搭Nuget伺服器,進行統一版本管理。
- 字母后面的數字:是代表服務的級別,見服務分級。
- 系統通訊: 各系統之間儘量走內網Wcf/Tcp,對外合作單位及各移動端走WebApi/Http。
- 傳輸格式: Protobuffer、Json。
- 資料交換: 優先通過資料服務介面,其次SSIS、Job。
- 基礎平臺: 快取Redis,佇列RabbitMq等。依賴抽象,框架可替換。
- DB 層: 每個子系統擁有自己的子DB,原則上不能跨庫讀其他的。
- 高可用 : 子系統自行做負載,服務變更通知使用zookeeper。
- 單向2級:只能訂閱服務,不能釋出服務,2級只能訂閱2級服務。
- 定點:某個客戶端只能訂閱某個服務端提供的服務。
系統實施
SOA管理中心
這是新架構的核心部分,主要功能如下:
- 提供釋出/訂閱/ServiceAdapter元件
- 提供Web管理介面
- 對服務訪問的各種配置
- 在高峰期對服務限流/報警
- 服務訪問授權、描述
釋出服務
各系統通過Web管理頁面進行服務配置釋出。
也可以通過管理中心提供的元件,進行配置釋出:
protected void Application_Start() { ServicePublisher.Pushlish(new ServiceConfig() { ServiceName = "獲取預訂單詳情", Qps = 1000, Level = 1, Key = "xxxx-yyyy", Source = SubscribeSource.All, ServiceAddress = "/Order/GetDetail", //其他 }); } [ServiceFilter] void GetDetail() { }
訂閱訪問服務
各系統通過管理中心提供的元件,去獲取訂閱的服務,然後通過介面卡去訪問介面,服務變更在心跳裡面做:
protected void Application_Start() { //獲取服務列表 var serviceList = ServiceManager.GetServiceList(); GlobalService.ServiceList=serviceList; } void Heartbeat() { var serviceList = ServiceManager.GetServiceList(); GlobalService.ServiceList=serviceList; } ServiceAdapter.Access(GlobalService.ServiceList[0]);
採蘑菇示例
設計目標
儘可能少的侵入
這點是非常重要的,如果不能很好的重用已有的系統或侵入性太強,勢必會導致:
- 新架構週期過長,長期維護二套結構。這種情況下,成本太高,不好推行下去或者還未推行就被砍了。
- 開發人員的抵抗,每個猿類內心都有桀驁的脾氣、造輪子的天賦、重組世界的夢想...。如果太複雜、約束太強,天知道你們這群猿類會幹出什麼事情!
基於這種考慮,才採用服務分散式而不是服務集中式。
- 每個系統在需要時,去訂閱服務,然後拉取服務地址/MyNeedServcie.list。
- 然後通過ServiceAdapter訪問服務,ServiceAdapter中會做許可權等一些校驗。
- 在服務上增加ServiceFilter,Fileter會做許可權校驗及服務被呼叫的資訊採集。
- 然後在管理中心新增服務,文件描述。
好處是:A系統與B系統是直接互動的,服務呼叫不走中轉路由,效能也好。而元件的作用僅是輔助性的約束。
服務自治&&水平擴充套件
由於侵入性較小,所以各個系統之間的服務變更,維護完全由各自研發團隊維護。
本系統之間通訊不走服務,直接內部呼叫。呼叫通過ServiceAdapter元件訪問,ServiceAdapter包含對程式內、WCF、WebApi等訪問的封裝,這樣便於以後替換成其他服務。
各服務在擴充套件上不受管理中心節制,自行做負載、增加伺服器即可。
系統升級降級
當有個新需要過來時,會根據產品是否需要獨立部署,和現有系統耦合性等因素,來評估是模組級還是系統級。
對於舊模組,根據重要程度、訪問量等評估出分數。達標的由模組抽離出子系統,單獨管理。
同樣對於舊系統,不達標的進行降級處理,縮小成模組整合到其他系統裡面。
這塊其實很重要,如果不對專案做好評估的話,往往會導致一個系統越來越沉重。最後的結果就是維護越來越麻煩,經常出問題。最後逼不得已就推到重來,這個成本就較大些,當然成本的事情老大會考慮更多些。處於這種情況下猿類們會一邊吐槽著之前的同類渣渣,一邊躍躍欲試準備大展身手,讓你們瞧瞧什麼叫DDD、TDD、設計模式......。
前提是在需求開發時,按模組進行分小層而不是整個大層,這樣方便協作開發和抽取成子系統。
常見問題
ClientApi VS ServiceApi
ClientApi這個在前期用的比較多的辦法,優點很明顯:簡單快捷,從Nuget上安裝引用即可,但這樣後期引發的問題會越來越多。
就拿快取Redis來說,多個系統都使用客戶端直接訪問Redis伺服器。如果有個系統連線數忘記關閉,就會影響整個大系統,原因就是Client許可權過大,客戶端是可以對redis伺服器直接進行操控。這種情況下redis伺服器本身是暴露在外的,哪怕客戶端封裝的再好也不行,只要研究下通訊協議規範,就可以自己寫個客戶端連(參見:c#實現redis客戶端(一))。 這個通訊期間無法管控,無法做攔截,同樣佇列等其他也是同樣情況。
ServiceApi:
這裡在中間加了一層,在快取系統裡面做管控,同樣依賴抽象,Redis可替換。快取系統以服務的形式釋出給其他系統使用。
避免不了的就是效能有損耗,當然這個損耗可以通過一些手段減小。
聚合服務
服務的顆粒度一直是SOA設計的頭疼事情。太粗了就很難複用,太細了需要多次往返互動,其效能、事務處理都是個問題。
比如下訂單服務,這個過程中包含建立使用者資料,生成預訂單、支付訂單,更新賬務關係,更新庫存等一系列的操作。這是個粗粒度服務,裡面包含若干子系統的提供的細粒度服務。
粗粒度服務下,多個子系統避免不了互相互動,長久下去會讓系統過於沉重,變得職責混亂。
服務設計準則就是讓服務高內聚,服務之間鬆耦合、邊界清晰。所以需要抽離出一個聚合服務系統,它專門負責把各系統提供的細粒度服務進行整合,提供給前端使用。而其他各個系統只做自己職責之內的事情。
在聚合服務系統中,方便我們更合理的把控服務的顆粒度,提高服務複用。
服務分級
多個研發團隊協作時,很難每個人都對全部業務熟悉。所以為了避免服務呼叫混亂,甚至迴圈依賴呼叫,增加了對服務的分級。
按圖中所示,1級服務不能調2級服務,即低階不能調高階,高階可調低階,同級互相可調。
這個級別針對單個服務而分的。比如有個更新庫存服務,它沒有外部依賴的服務,只是更新自己的DB,這樣就可以把它劃分為1級服務。 而聚合服務系統中有個下訂單粗粒度服務,它內部呼叫更新庫存服務,那麼它就是2級服務。很明顯這裡的1級不應該呼叫2級,對服務分級也是這個目的。
總結心得
- 好架構是不斷進化來的
- 儘可能考慮到每個細節
- 注重整體平衡性,而非區域性最優
- 依賴抽象,而不是具體哪個框架技術
- 先考慮人、資源,在考慮用哪個技術
- 跟妹紙相處時不要想程式那點事