微服務如今辣麼火,業界流行的對比的卻都是所謂的Monolithic單體應用,而大量的系統在十幾年前都是已是分佈式系統了,那麼微服務做爲新的理念和原來的分佈式系統,或者說SOA(面向服務架構)是什麼區別呢?前端
咱們先看相同點:程序員
須要Registry,實現動態的服務註冊發現機制;數據庫
須要考慮分佈式下面的事務一致性,CAP原則下,兩段式提交不能保證性能,事務補償機制須要考慮;編程
同步調用仍是異步消息傳遞,如何保證消息可靠性?SOA由ESB來集成全部的消息;後端
都須要統一的Gateway來匯聚、編排接口,實現統一認證機制,對外提供APP使用的RESTful接口;設計模式
一樣的要關注如何再分佈式下定位系統問題,如何作日誌跟蹤,就像咱們電信領域作了十幾年的信令跟蹤的功能;架構
那麼差異在哪?併發
是持續集成、持續部署?對於CI、CD(持續集成、持續部署),這自己和敏捷、DevOps是交織在一塊兒的,我認爲這更傾向於軟件工程的領域而不是微服務技術自己;框架
使用不一樣的通信協議是否是區別?微服務的標杆通信協議是RESTful,而傳統的SOA通常是SOAP,不過目前來講採用輕量級的RPC框架Dubbo、Thrift、gRPC很是多,在Spring Cloud中也有Feign框架將標準RESTful轉爲代碼的API這種仿RPC的行爲,這些通信協議不該該是區分微服務架構和SOA的核心差異;異步
是流行的基於容器框架仍是虛擬機爲主?Docker和虛擬機仍是物理機都是架構實現的一種方式,不是核心區別;
微服務架構的精髓在切分
服務的切分上有比較大的區別,SOA本來是以一種「集成」技術出現的,不少技術方案是將原有企業內部服務封裝爲一個獨立進程,這樣新的業務開發就可重用這些服務,這些服務極可能是相似供應鏈、CRM這樣的很是大的顆粒;而微服務這個「微」,就說明了他在切分上有講究,不妥協。無數的案例證實,若是你的切分是錯誤的,那麼你得不到微服務承諾的「低耦合、升級不影響、可靠性高」之類的優點,而會比使用Monolithic有更多的麻煩。
不拆分存儲的微服務是僞服務:在實踐中,咱們經常見到一種架構,後端存儲是所有和在一個數據庫中,僅僅把前端的業務邏輯拆分到不一樣的服務進程中,本質上和一個Monolithic同樣,只是把模塊之間的進程內調用改成進程間調用,這種切分不可取,違反了分佈式第一原則,模塊耦合沒有解決,性能卻受到了影響。
分佈式設計第一原則 — 「不要分佈你的對象」
微服務的「Micro」這個詞並非越小越好,而是相對SOA那種粗粒度的服務,咱們須要更小更合適的粒度,這種Micro不是無限制的小。
若是咱們將兩路(同步)通訊與小/微服務結合使用,並根據好比「1個類=1個服務」的原則,那麼咱們實際上回到了使用Corba、J2EE和分佈式對象的20世紀90年代。遺憾的是,新生代的開發人員沒有使用分佈式對象的經驗,所以也就沒有認識到這個主意多麼糟糕,他們正試圖重複歷史,只是此次使用了新技術,好比用HTTP取代了RMI或IIOP。
一個簡單的圖書管理系統確定無需微服務架構。既然採用了微服務架構,那麼面對的問題空間必然是比較宏大,好比整個電商、CRM。
如何拆解服務呢?
使用什麼樣的方法拆解服務?業界流行1個類=1個服務、1個方法=1個服務、2 Pizza團隊、2周能重寫完成等方法,可是這些都缺少實施基礎。咱們必須從一些軟件設計方法中尋找,面向對象和設計模式適用的問題空間是一個模塊,而函數式編程的理念更多的是在代碼層面的微觀上起做用。
Eric Evans 的《領域驅動設計》這本書對微服務架構有很大借鑑意義,這本書提出了一個能將一個大問題空間拆解分爲領域和實體之間的關係和行爲的技術。目前來講,這是一個最合理的解決拆分問題的方案,透過限界上下文(Bounded Context,下文簡稱爲BC)這個概念,咱們能將實現細節封裝起來,讓BC都可以實現SRP(單一職責)原則。而每一個微服務正是BC在實際世界的物理映射,符合BC思路的微服務互相獨立鬆耦合。
微服務架構是一件好事,逼着你們關注設計軟件的合理性,若是原來在Monolithic中領域分析、面向對象設計作很差,換微服務會把這個問題成倍的放大
以電商中的訂單和商品兩個領域舉例,按照DDD拆解,他們應該是兩個獨立的限界上下文,可是訂單中確定是包含商品的,若是貿然拆爲兩個BC,查詢、調用關係就耦合在一塊兒了,甚至有了麻煩的分佈式事務的問題,這個關聯如何拆解?BC理論認爲在不一樣的BC中,即便是一個術語,他的關注點也不同,在商品BC中,關注的是屬性、規格、詳情等等(實際上商品BC這個領域有價格、庫存、促銷等等,把他做爲單獨一個BC也是不合理的,這裏爲了簡化例子,你們先認爲商品BC就是商品基礎信息), 而在訂單BC中更關注商品的庫存、價格。因此在實際編碼設計中,訂單服務每每將關注的商品名稱、價格等等屬性冗餘在訂單中,這個設計解脫了和商品BC的強關聯,兩個BC能夠獨立提供服務,獨立數據存儲
微服務架構首先要關注的不是RPC/ServiceDiscovery/Circuit Breaker這些概念,也不是Eureka/Docker/SpringCloud/Zipkin這些技術框架,而是服務的邊界、職責劃分,劃分錯誤就會陷入大量的服務間的相互調用和分佈式事務中,這種狀況微服務帶來的不是便利而是麻煩。
DDD給咱們帶來了合理的劃分手段,可是DDD的概念衆多,晦澀難以理解,如何抓住重點,合理的運用到微服務架構中呢?
我認爲以下的幾個架構思想是重中之重
充血模型
事件驅動
實際上DDD和麪向對象設計、設計模式等等理論有千絲萬縷的聯繫,若是不熟悉OOA、OOD,DDD也是使用很差的。不過學習這些OO理論的時候,你們每每感受到無用武之地,由於大部分的Java程序員開發生涯是從學習J2EE經典的分層理論開始的(Action、Service、Dao),在這種分層理論中,咱們基本沒有啥機會使用那些所謂的「行爲型」的設計模式,這裏的核心緣由,就是J2EE經典分層的開發方式是「貧血模型」。
Martin Fowler在他的《企業應用架構模式》這本書中提出了兩種開發方式「事務腳本」和「領域模型」,這兩種開發分別對應了「貧血模型」和「充血模型」。
針對上面的技術我特地整理了一下,有不少技術不是靠幾句話能講清楚,因此乾脆找朋友錄製了一些視頻,不少問題其實答案很簡單,可是背後的思考和邏輯不簡單,要作到知其然還要知其因此然。若是想學習Java工程化、高性能及分佈式、深刻淺出。微服務、Spring,MyBatis,Netty源碼分析的朋友能夠加個人Java進階羣:582505643,羣裏有阿里大牛直播講解技術,以及Java大型互聯網技術的視頻免費分享給你們。
事務腳本開發模式
事務腳本的核心是過程,能夠認爲大部分的業務處理都是一條條的SQL,事務腳本把單個SQL組織成爲一段業務邏輯,在邏輯執行的時候,使用事務來保證邏輯的ACID。最典型的就是存儲過程。固然咱們在平時J2EE經典分層架構中,常常在Service層使用事務腳本。
使用這種開發方式,對象只用於在各層之間傳輸數據用,這裏的對象就是「貧血模型」,只有數據字段和Get/Set方法,沒有邏輯在對象中。
咱們以一個庫存扣減的場景來舉例:
業務場景
首先談一下業務場景,一個下訂單扣減庫存(鎖庫存),這個很簡單
先判斷庫存是否足夠,而後扣減可銷售庫存,增長訂單佔用庫存,而後再記錄一個庫存變更記錄日誌(做爲憑證)
貧血模型的設計
首先設計一個庫存表 Stock,有以下字段
設計一個Stock對象(Getter和Setter省略)
1
2
3
4
5
6
|
public
class
Stock {
private
String spuId;
private
String skuId;
private
int
stockNum;
private
int
orderStockNum;
}
|
Service入口
設計一個StockService,在其中的lock方法中寫邏輯
入參爲(spuId, skuId, num)
實現僞代碼
1
2
3
4
5
6
7
|
count = select stocknum from stock where spuId=xx and skuid=xx
if
count>num {
update stock set stocknum=stocknum-num, orderstocknum=orderstocknum+num where skuId=xx and spuId=xx
}
else
{
//庫存不足,扣減失敗
}
insert stock_log set xx=xx, date=
new
Date()
|
ok,打完收工,若是作的好一些,能夠把update和select count合一,這樣能夠利用一條語句完成自旋,解決併發問題(高手)。
小結一下:
有沒有發現,在這個業務領域很是重要的核心邏輯 — 下訂單扣減庫存中操做過程當中,Stock對象根本不用出現,所有是數據庫操做SQL,所謂的業務邏輯就是由多條SQL構成。Stock只是CRUD的數據對象而已,沒邏輯可言。
馬丁福勒定義的「貧血模型」是反模式,面對簡單的小系統用事務腳本方式開發沒問題,業務邏輯複雜了,業務邏輯、各類狀態散佈在大量的函數中,維護擴展的成本一會兒就上來,貧血模型沒有實施微服務的基礎。
針對上面的技術我特地整理了一下,有不少技術不是靠幾句話能講清楚,因此乾脆找朋友錄製了一些視頻,不少問題其實答案很簡單,可是背後的思考和邏輯不簡單,要作到知其然還要知其因此然。若是想學習Java工程化、高性能及分佈式、深刻淺出。微服務、Spring,MyBatis,Netty源碼分析的朋友能夠加個人Java進階羣:582505643,羣裏有阿里大牛直播講解技術,以及Java大型互聯網技術的視頻免費分享給你們。
雖然咱們用Java這樣的面嚮對象語言來開發,可是其實和過程型語言是同樣的,因此不少狀況下你們用數據庫的存儲過程來替代Java寫邏輯反而效果會更好,(ps:用了Spring boot也不是微服務),
領域模型的開發模式
領域模型是將數據和行爲封裝在一塊兒,並與現實世界的業務對象相映射。各種具有明確的職責劃分,使得邏輯分散到合適對象中。這樣的對象就是「充血模型」 。
在具體實踐中,咱們須要明確一個概念,就是領域模型是有狀態的,他表明一個實際存在的事物。仍是接着上面的例子,咱們設計Stock對象須要表明一種商品的實際庫存,並在這個對象上面加上業務邏輯的方法
這樣作下單鎖庫存業務邏輯的時候,每次必須先從Rep
ository根據主鍵load還原Inventory這個對象,而後執行對應的lock(num)方法改變這個Inventory對象的狀態(屬性也是狀態的一種),而後再經過Repository的save方法把這個對象持久化到存儲去。
完成上述一系列操做的是Application,Application對外提供了這種集成操做的接口
領域模型開發方法最重要的是把扣減形成的狀態變化的細節放到了Inventory對象執行,這就是對業務邏輯的封裝。
Application對象的lock方法能夠和事務腳本方法的StockService的lock來作個對比,StockService是徹底掌握全部細節,一旦有了變化(好比庫存爲0也能夠扣減),Service方法要跟着變;而Application這種方式不須要變化,只要在Inventory對象內部計算就能夠了。代碼放到了合適的地方,計算在合適層次,一切都很合理。這種設計能夠充分利用各類OOD、OOP的理論把業務邏輯實現的很漂亮。
充血模型的缺點
從上面的例子,在Repository的load 到執行業務方法,再到save回去,這是須要耗費必定時間的,可是這個過程當中若是多個線程同時請求對Inventory庫存的鎖定,那就會致使狀態的不一致,麻煩的是針對庫存的併發不只難處理並且很常見。
貧血模型徹底依靠數據庫對併發的支撐,實現能夠簡化不少,但充血模型就得本身實現了,無論是在內存中經過鎖對象,仍是使用Redis的遠程鎖機制,都比貧血模型複雜並且可靠性降低,這是充血模型帶來的挑戰。更好的辦法是能夠經過事件驅動的架構來取消併發。
領域模型和微服務的關係
上面講了領域模型的實現,可是他和微服務是什麼關係呢?在實踐中,這個Inventory是一個限界上下文的聚合根,咱們能夠認爲一個聚合根就是一個微服務進程。
不過問題又來了,一個庫存的Inventory必定和商品信息是有關聯的,僅僅靠Inventory中的冗餘那點商品ID是不夠的,商品的上下架狀態等等都是業務邏輯須要的,那不是又把商品Sku這樣的重型對象引入了這個微服務?兩個重型的對象在一個服務中?這樣的微服務拆不開啊,仍是必須依靠商品庫?!