早想着要寫一篇博客,但由於各種原因(其實因為懶),遲遲沒有動筆。今日下決心,寫寫關於軟件服務架構的一點感悟。
三層架構
從讀大學開始,老師就講三層架構。后來的項目實施基本上也都是三層架構。對於小型項目,業務邏輯相對簡單的項目,三層架構是快速迭代的利器。隨着項目的迭代,功能越來越多,業務邏輯越來越復雜,業務開發團隊越來越龐大,單體的三層架構就逐漸暴露出它的不足。因為這樣的項目是一個高內聚、高耦合的項目,一個類、一個方法可能被多處引用,給維護帶來了極大的不方便,要修改一個方法、修改一個字段,可能會影響到所有引用它的方法。如果項目中還存在包引用、dll引用,可能還會導致包名沖突、命名空間沖突。這種情況下,我們會去想,如何降低業務復雜度?答案是拆分服務,微服務化。開發團隊的壯大對於高效的管理也是一個問題,微服務化后,原先業務團隊被拆分成多個微服務團隊,也降低了管理的難度。
微服務架構
![]() |
![]() |
(圖1) | (圖2) |
對於微服務如何划分,粒度多粗多細,每個團隊有每個團隊的划分法。從我個人的經歷來看,無外乎一種細粒度划分,一種粗粒度划分。
圖1,展示了細粒度的服務划分通常的一個結構。對於一個復雜的業務,如果服務划分過細(這樣的服務通常絕對禁止跨庫訪問),業務邏輯層必然要對服務進行組裝,不管是RPC的調用方式,還是Rest的方式,此時的業務邏輯層仍然是一個高內聚、高耦合的模塊。對於一個需要快速迭代的產品,這樣的架構快速不起來。比如一個下單服務,業務邏輯層的負責團隊需要等待商品服務相應的接口、訂單服務相應的接口、庫存服務相應的接口等下單涉及到的接口准備就緒,才能開始編寫服務。
圖2,展示課粗粒度的服務划分通常的結構。這時,一個微服務接口是一個粗粒度的接口,微服務與微服務之間不允許相互調用,而允許跨庫訪問,降低了服務之間的依賴,這樣的的微服務是一個高內聚、低耦合的模塊。還是以下單服務來舉例,此時負責訂單服務的團隊,編寫服務的時候,不必等待商品團隊、庫存團隊、用戶團隊(收貨地址)等其他團隊,他們自己可以快速着手開發下單服務。負責庫存服務的團隊,只寫他們關心的服務,比如商品的采購、入庫。而訂單團隊需要的庫存扣減操作接口,訂單團隊自給自足。(這里引申一個問題,從DDD的思想來看,一個Domain的邊界,到底在哪里?拿庫存來說,一切有關庫存的操作都寫到這個Domain叫做DDD?如果只有訂單模塊會扣減庫存,退單后會加回庫存,這樣的接口還寫在庫存服務模塊?這類接口屬於訂單領域還是庫存領域?)
微服務,不僅能夠降低業務復雜度、開發團隊管理難度,而且由於微服務的特性,使得部署軟件的資源能更合理高效的應用,降低資源成本。
高並發
(圖3)
軟件並發量逐漸提高,不管是三層架構、還是微服務,優化的途徑都差不多,讀寫分離-》加緩存-》分庫分表。上方所示圖2到圖3,展示了利用一些數據訪問中間件(Sharding-JDBC、Macat、Atlas&&)實現分庫分表的架構。
重構
對於一個潛在的可能存在高並發場景的項目,如何能在遇到高並發的時候,從容地重構一個項目?我有一些淺見,供大家討論:
(臨時搭建的項目,湊合看吧)
如上圖所示,項目划分了兩個Package,一個Product,一個Membership,請注意,ProductController里面引用了Membership這個Package里的UserService,換句話說,就是ProductController依賴了Membership這個Package里的UserService。假如我們約定,不允許跨Package依賴,那么Product這個Package里有需要用到User相關的服務的時候,自己寫到Product這個Package里。那么當我要進行服務拆分的時候,只需要把Product這個Package單獨打包成一個jar,即可拆分完成。這樣約定,方便從三層架構重構到上圖2的架構,對將來可能的分庫分表沒有任何影響。
如果覺得不允許跨Package調用代碼不能復用,造成了代碼冗余,也可以采用下面的項目結構:
Service以下層(包含Service)仍然安裝單體應用的開發模式開發,拆分時,只需要將Controller層拆開打包成jar即可,后續的維護要求拆分后的團隊各自維護各自的代碼,而不是繼續一個團隊維護底層。
如果業務進一步復雜,采用圖2所示架構也會遇到問題。比如庫存數據,如果除了訂單服務之外還有其他的服務都去修改庫存數據的話,在沒有統一日志或者這樣的服務數量比較多的情況下,想要知道這個庫存數據是怎么來的,會非常困難。這個時候只能重構成圖1的架構。采用圖1的架構,需要業務專家來定義接口,使其盡可能地適應多的使用場景。