微服務(wù)架構(gòu)變得越來越流行了。它是模塊化的一種方法。它把一整塊應(yīng)用拆分成一個個服務(wù)。它讓團隊在開發(fā)大型復(fù)雜的應(yīng)用時更快地交付出高質(zhì)量的軟件。團隊成員們可以輕松地接受到新技術(shù),因為他們可以使用最新且推薦的技術(shù)棧來實現(xiàn)各自的服務(wù)。微服務(wù)架構(gòu)也通過讓每個服務(wù)都被部署在最佳狀態(tài)的硬件上而改善了應(yīng)用的擴展性。
創(chuàng)新互聯(lián)是一家專業(yè)提供利辛企業(yè)網(wǎng)站建設(shè),專注與成都做網(wǎng)站、成都網(wǎng)站設(shè)計、H5響應(yīng)式網(wǎng)站、小程序制作等業(yè)務(wù)。10年已為利辛眾多企業(yè)、政府機構(gòu)等服務(wù)。創(chuàng)新互聯(lián)專業(yè)網(wǎng)站設(shè)計公司優(yōu)惠進行中。但微服務(wù)不是萬能的。特別是在 領(lǐng)域模型、事務(wù)以及查詢這幾個地方,似乎總是不能適應(yīng)拆分?;蛘哒f這幾塊也是微服務(wù)需要專門處理的地方,相對于過去的單體架構(gòu)。
在這篇文章中,我會描述一種開發(fā)微服務(wù)的方法,這個方法可以解決這些問題。主要是通過領(lǐng)域模型設(shè)計,也就是DDD以及事件源(Event Sourcing)以及CQRS。讓我們首先來看看開發(fā)人員在開發(fā)微服務(wù)的時候會遇到哪些問題吧。
微服務(wù)開發(fā)過程中的挑戰(zhàn)
模塊化在開發(fā)大型復(fù)雜的應(yīng)用的時候是非常有必要的。
現(xiàn)在許多應(yīng)用大到一個人根本無法完成。而且復(fù)雜到光靠一個人去理解是不可能的。
這種情況下,應(yīng)用就必須被拆分成一個個模塊。在單體應(yīng)用中,模塊被定義為比方一個java package。然而,這種做法在實踐中并不是很理想,時間長了,單體應(yīng)用就變得越來越龐大。微服務(wù)架構(gòu)把服務(wù)作為一個模塊單元。
每個服務(wù)對應(yīng)一個業(yè)務(wù)能力,這個業(yè)務(wù)能力是組織為了創(chuàng)造價值而需要的。例如,基于微服務(wù)的在線商店包括各種服務(wù),包括訂購服務(wù)(Order Service),客戶服務(wù)(Customer Service),目錄服務(wù)(Catalog Service)。
每個服務(wù)都有一個不可***且很難違反的邊界。也就是每個微服務(wù)要提供一種單獨而獨立的能力。這樣的話,應(yīng)用程序的模塊化就更容易隨時間保存。
微服務(wù)架構(gòu)還有其他優(yōu)點。包括獨立地部署服務(wù),獨立地擴展服務(wù)等等這些能力。相比單體來說。
不幸的是,拆分并沒有聽起來那么容易。相當(dāng)難。
應(yīng)用的領(lǐng)域模型,事務(wù),查詢這三個東西就是拆分過程中和拆分后你所面臨的拆分難題。讓我們來看看具體原因吧。
問題1 – 拆分領(lǐng)域模型
領(lǐng)域模型模式是實現(xiàn)復(fù)雜業(yè)務(wù)邏輯的一種非常好的方式。比如針對一個在線商店,領(lǐng)域模型將會包含這么幾個類: Order, OrderLineItem, Customer 和 Product。在微服務(wù)架構(gòu)中,Order和OrderLineItem類是Order Service的一部分;Customer是Customer Service的一部分;Product屬于Catalog Service的一部分。
拆分領(lǐng)域模型的挑戰(zhàn)之一就是class們通常會引用一個或多個其他類。
比如,Order類引用了該訂單的客戶Customer;OrderLineItem引用了該訂單所訂產(chǎn)品Product。
對于這些想要橫跨服務(wù)邊界的引用,我們該怎么辦呢?
稍后你將會看到一個來自領(lǐng)域模型設(shè)計的概念:聚合(Aggregate)。我們通過聚合來解決這個問題。
微服務(wù)和數(shù)據(jù)庫
微服務(wù)架構(gòu)的一個非常明顯的功能就是一個服務(wù)所擁有的數(shù)據(jù)只能通過這個服務(wù)的API來訪問。
在一個電商網(wǎng)站中,比如,OrderService占有一個數(shù)據(jù)庫,里邊有一張表ORDERS;CustomerService也有自己的數(shù)據(jù)庫包含表CUSTOMERS。
通過這樣的封裝,微服務(wù)之間就解耦了。
在開發(fā)期間,開發(fā)人員可以獨立修改自己服務(wù)的數(shù)據(jù)庫shema而不需要與其他服務(wù)的開發(fā)協(xié)調(diào)勾兌。
在生產(chǎn)上,服務(wù)之間都是隔離的。比如,一個服務(wù)從來不會因為另外一個服務(wù)占有了數(shù)據(jù)庫的鎖而導(dǎo)致阻塞等待。
不幸的是,這種數(shù)據(jù)庫的拆分讓管理數(shù)據(jù)的一致性以及不同服務(wù)間跨表查詢變得困難。
問題2 – 跨服務(wù)分布式事務(wù)實現(xiàn)
一個傳統(tǒng)的單體應(yīng)用可以通過ACID事務(wù)來強制業(yè)務(wù)規(guī)則從而實現(xiàn)一致性。
想象一下,比如,電商里的用戶都有信用額度,就是在創(chuàng)建訂單之前必須先看信用如何。
應(yīng)用程序必須確保潛在的多個并發(fā)嘗試去創(chuàng)建訂單不超過客戶的信用限額。
如果Orders和Customers都在同一個庫中,那么就可以使用ACID事務(wù)來搞定:
BEGIN TRANSACTION … SELECT ORDER_TOTAL FROM ORDERS WHERE CUSTOMER_ID = ? … SELECT CREDIT_LIMIT FROM CUSTOMERS WHERE CUSTOMER_ID = ? … INSERT INTO ORDERS … … COMMIT TRANSACTION
不幸的是,在微服務(wù)架構(gòu)中我們無法通過這種方式管理數(shù)據(jù)的一致性。
ORDERS和CUSTOMERS表被不同的服務(wù)所擁有,只能通過各自的服務(wù)API訪問。他們甚至可能在不同的數(shù)據(jù)庫。
一種比較常見的做法就是使用分布式事務(wù)來搞定,比如2PC等。但是這種做法對于現(xiàn)代應(yīng)用來說也許不是一種可行的方案。CAP定理要求你必須在可用性和一致性之間選擇,可用性通常是較好的選擇。
而且,許多現(xiàn)代技術(shù),例如大多數(shù)NoSQL數(shù)據(jù)庫,甚至不支持ACID事務(wù),更不用說2PC。
所以管理數(shù)據(jù)的一致性需要使用其他的方式。
稍后你將會看到我們使用事件驅(qū)動架構(gòu)中的一種技術(shù)叫事件源(event sourcing)來解決分布式事務(wù)。
問題3 -查詢
管理數(shù)據(jù)一致性不是唯一的挑戰(zhàn)。還有一個問題就是查詢問題。
在傳統(tǒng)的單體應(yīng)用中,我們通常使用join來實現(xiàn)跨表查詢。
比如,我們可以通過下面的sql輕松的查詢出最近客戶所訂的大額訂單:
SELECT * FROM CUSTOMER c, ORDER o WHERE c.id = o.ID AND o.ORDER_TOTAL > 100000 AND o.STATE = 'SHIPPED' AND c.CREATION_DATE > ?
但我們無法在微服務(wù)架構(gòu)中實現(xiàn)這樣的查詢。
就像前面提到的那樣,ORDERS與CUSTOMERS表分屬不同的服務(wù),只能通過服務(wù)API來訪問。
而且他們可能使用了不同的數(shù)據(jù)庫。
而且,即使你使用事件源(Event Sourcing )處理查詢問題可能更麻煩。
稍后,你將會學(xué)習(xí)到一種解決方案就是通過一種叫CQRS(Command Query Responsibility Segregation)做法來解決分布式查詢問題。
但首先,讓我們看看領(lǐng)域驅(qū)動設(shè)計(DDD)這個工具,在我們的微服務(wù)架構(gòu)下基于領(lǐng)域模型開發(fā)業(yè)務(wù)邏輯是必要的。
DDD聚合是微服務(wù)的構(gòu)建塊
像你看到的那樣,為了使用微服務(wù)架構(gòu)成功的開發(fā)業(yè)務(wù)應(yīng)用,我們必須去解決上面所說的那些問題。
這幾個問題的解決辦法你可以去Eric Evans的書Domain-Driven Design中找得到。
這本書,是2003年出版的,主要介紹了設(shè)計復(fù)雜軟件的一些方法。這些方法對開發(fā)微服務(wù)也同樣有用。
尤其是領(lǐng)域驅(qū)動設(shè)計可以讓你創(chuàng)建一個模塊化的領(lǐng)域模型,這個領(lǐng)域模型可以被多個微服務(wù)所使用。
什么是聚合?
在領(lǐng)域驅(qū)動設(shè)計中,Evans為領(lǐng)域模型定義了幾個構(gòu)建塊。
許多已經(jīng)成為日常開發(fā)人員語言的一部分,包括entity,就是指一個具有唯一標識的持久化對象。value object,也就是VO,你經(jīng)常聽說的,是用來存放數(shù)據(jù)的,可以與數(shù)據(jù)庫表對應(yīng),也可以不對應(yīng),有點類似用來傳輸數(shù)據(jù)的DTO。service,就是指包含業(yè)務(wù)邏輯的服務(wù)。但不應(yīng)歸類到entity或者value object。
repository,表示一堆entity 的集合就是一個repository。
構(gòu)建塊(building block),聚合(aggregate)常常被開發(fā)人員忽略,除了那些DDD愛好者,或者叫“狂熱分子”。
然而,聚合(aggregate)被證明是開發(fā)微服務(wù)的關(guān)鍵,非常重要。
一個聚合(aggregate)就是一組domain的集合,可以被當(dāng)作一個單元來處理。這里說的一個單元就是可以當(dāng)做原子來處理。
它包含了一個root entity以及可能還有一到多個關(guān)聯(lián)的entity以及value object。
比如,針對一個在線商店的domain model就會有幾個聚合,比如Order和Customer。
Order聚合又由一個root entity Order和一個以上的OrderLineItem value object組成,而且OrderLineItem還有可能關(guān)聯(lián)有其他vo,比如快遞地址(Address)以及支付賬戶信息PaymentInformation。
Customer聚合又由一個root entity Customer和其他的vo比如DeliveryInfo 和PaymentInformation組成。
使用聚合將領(lǐng)域模型(domain model)分散和參與到每個聚合中,這也使得領(lǐng)域模型更容易理解了。這也同時厘清了操作的scope,比如查詢操作和刪除操作等。
一個聚合通常作為一個整體被從數(shù)據(jù)庫中l(wèi)oad出來。刪除一個聚合,也就是刪除了里邊所有的object。
然而,聚合的好處遠遠超出了模塊化一個領(lǐng)域模型。 這是因為聚合必須遵守一定的規(guī)則。
聚合之間的引用必須使用主鍵
第一個規(guī)則就是聚合通過id(例如主鍵)來引用而不是通過對象引用 。
比如,Order通過customerId來引用Customer,而不是引用Customer的對象。
類似的,OrderLineItem通過productId來引用Product。
這種做法與傳統(tǒng)的object modeling非常的不同。雖然后者認為通過外鍵引用在領(lǐng)域模型中這樣做看起來怪怪的。
通過使用ID而不是object引用,意味著聚合是松耦合。你可以輕松地把不同的聚合放在不同的service。
事實上,一個微服務(wù)的業(yè)務(wù)邏輯是由一個領(lǐng)域模型組成。這個領(lǐng)域模型是幾個聚合的一個組合。比如,OrderService包含了Customer聚合。
一個事務(wù)只創(chuàng)建或更新一個聚合
第二個規(guī)則就是聚合必須遵循一個事務(wù)只能對一個聚合進行創(chuàng)建或更新。
當(dāng)我第一次看這些規(guī)則的時候,當(dāng)時并沒有什么感覺。因為那時候,我還在開發(fā)傳統(tǒng)的單體應(yīng)用,那種基于RDBMS的應(yīng)用。所以事務(wù)可以更新任何的數(shù)據(jù)。今天,這些約束依然適用于微服務(wù)架構(gòu)。它確保一個事務(wù)只被包含在一個微服務(wù)中。此約束還符合大多數(shù)NoSQL數(shù)據(jù)庫的有限事務(wù)模型。
當(dāng)開發(fā)一個領(lǐng)域模型,一個很重要的事情就是你必須確定每個聚合得搞多大。
一方面,聚合理想情況下應(yīng)該是小的。它通過分離關(guān)注點來改善模塊化。
這是更有效的,因為聚合通常被全部加載。
此外,由于對每個聚合的更新是順序發(fā)生的,因此使用細粒度聚合將增加應(yīng)用程序可以處理的并發(fā)請求數(shù),從而提高可擴展性。
它還將改善用戶體驗,因為它降低了兩個用戶嘗試更新同一聚合的可能性。
另一方面,因為聚合是事務(wù)的范圍,您可能需要定義一個較大的聚合,以使特定的更新原子化。
例如,之前我描述了在在線商店領(lǐng)域模型中,Order和Customer是獨立的聚合。
另一種設(shè)計可以是把Orders作為Customer聚合的一部分。
一個較大的Customer聚合的好處就是應(yīng)用可以強制對于信用額度進行原子驗證。這種方法的缺點是它將訂單和客戶管理功能組合到同一服務(wù)中。這也降低了可擴展性,因為更新同一客戶的不同訂單的事務(wù)將被順序化。
類似的,兩個用戶去嘗試編輯同一個客戶下的不同訂單有可能會沖突。而且,隨著訂單數(shù)量的增加,加載一個Customer聚合的成本也會變得更昂貴。
由于這些問題,盡可能的把聚合細粒度是最好的。
即使一個事務(wù)只能創(chuàng)建和更新一個單獨的聚合,微服務(wù)應(yīng)用中也依然必須去管理聚合之間的一致性。
在Order服務(wù)中必須驗證一個新建的Order聚合將不超過Customer聚合的信用額度。
這里有兩種不同的解決一致性的方法。
一個做法就是在單個事務(wù)中欺騙的創(chuàng)建和/或更新多個聚合。這種做法的前提是,所有的聚合都被一個服務(wù)所擁有并且這些聚合都被持久保存在同一個RDBMS中才有可能。
另一個做法就是使用最終一致的事件驅(qū)動(event-driven)方法來維護聚合之間的一致性。
使用事件驅(qū)動來維護數(shù)據(jù)一致性
在現(xiàn)代應(yīng)用中,對事務(wù)有各種約束,這使得難以在服務(wù)之間維持數(shù)據(jù)一致性。
每個服務(wù)都有自己的私有的數(shù)據(jù),這時候2PC的方案就變得不可行了。
更重要的是,很多的應(yīng)用使用的是NoSQL數(shù)據(jù)庫,這些數(shù)據(jù)庫根本就不支持本地ACID事務(wù),更不用說分布式事務(wù)了。
因此,現(xiàn)代應(yīng)用程序必須使用事件驅(qū)動的,最終一致的事務(wù)模型。
什么是事件(Event)?
根據(jù)Merriam-Webster(一個單詞網(wǎng)站),事件的意思就是:something that happens:
在本文中,我們將領(lǐng)域事件定義為聚合發(fā)生的事件。一個事件(event)通常表示一個狀態(tài)的改變?,F(xiàn)在還是拿電商系統(tǒng)舉例,一個Order聚合。其狀態(tài)更改事件包括訂單已創(chuàng)建(Order Created),訂單已取消(Order Cancelled),訂單已下達(Order Shipped)。事件可以表示違反業(yè)務(wù)規(guī)則的動作,如客戶(Customer)的信用額度。
使用Event-Driven架構(gòu)
服務(wù)們使用事件來管理聚合之間的一致性,像下面這樣的一個場景:一個聚合發(fā)布事件,比如,這個聚合的狀態(tài)改變或者一次違反業(yè)務(wù)規(guī)則的嘗試等等。
其它聚合訂閱這個事件,然后負責(zé)更新他們自己的狀態(tài)。
在線商店制創(chuàng)建一個訂單(order)的時候驗證客戶(customer)信用額度使用下面一系列步驟:
1.一個訂單(Order)聚合創(chuàng)建,并且狀態(tài)為NEW,發(fā)布一個OrderCreated 事件。
2.客戶(Customer)消費這個OrderCreated事件,然后保存為這個訂單保存信用值然后發(fā)布一個CreditReserved事件。
3.訂單(Order)聚合消費CreditReserved事件,然后修改自己的狀態(tài)為APPROVED。
如果信用檢查由于資金不足而失敗,則客戶(Customer)聚合發(fā)布CreditLimitExceeded事件。
這個事件不對應(yīng)于一個狀態(tài)的改變,而是表示一次違反業(yè)務(wù)規(guī)則的失敗嘗試。 訂單(Order)聚合消費這個事件后,并將自己的狀態(tài)更改為CANCELLED。
微服務(wù)架構(gòu)可以比作事件驅(qū)動聚合的Web
在這個架構(gòu)下,每個服務(wù)的業(yè)務(wù)邏輯都是由一個或多個聚合組成。
一個事務(wù)只能包含一個服務(wù),并且是更新或創(chuàng)建一個單獨的聚合。也就是聚合內(nèi)事務(wù)。
服務(wù)們通過使用事件管理聚合之間的一致性。
這種做法一個非常明顯的好處就是一個個聚合變成了松散而解耦的構(gòu)建塊。
他們可以被作為單體應(yīng)用來部署或者作為一組服務(wù)來部署。
這種情況下,在一個project開始的時候,你可以使用單體架構(gòu)。
之后,隨著應(yīng)用的體積和開發(fā)團隊的規(guī)模的擴大,你就可以很容易的切換到微服務(wù)架構(gòu)上來。
總結(jié)
微服務(wù)架構(gòu)從功能上把一整個應(yīng)用拆分成了一個個服務(wù),每個服務(wù)又都對應(yīng)一個業(yè)務(wù)能力。當(dāng)我們開發(fā)基于微服務(wù)架構(gòu)的業(yè)務(wù)應(yīng)用的時候,一個關(guān)鍵的挑戰(zhàn)就是事務(wù)、領(lǐng)域模型以及查詢,這三個主要的麻煩都是拆分之后所帶來的問題。你可以通過使用DDD聚合的概念來拆分領(lǐng)域模型。每個服務(wù)的業(yè)務(wù)邏輯是一個領(lǐng)域模型,然后這個領(lǐng)域模型是由一個或多個DDD聚合組成。
在每個服務(wù)中,一個事務(wù)只能創(chuàng)建或更新一個單獨的聚合。由于2PC對于現(xiàn)代應(yīng)用來說并不是一個可行的解決方案,所以我們需要使用事件機制來去實現(xiàn)聚合之間的一致性(以及服務(wù)之間)。在下一集,我們會描述使用event sourcing來實現(xiàn)一個事件驅(qū)動的架構(gòu)。我們也會向你展示在微服務(wù)架構(gòu)下通過使用CQRS來實現(xiàn)查詢。
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)cdcxhl.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機、免備案服務(wù)器”等云主機租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價比高”等特點與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。