溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

微服務(wù)業(yè)務(wù)開發(fā)三個(gè)難題-拆分、事務(wù)、查詢(上)

發(fā)布時(shí)間:2020-08-10 13:53:02 來(lái)源:網(wǎng)絡(luò) 閱讀:15515 作者:yushiwh 欄目:數(shù)據(jù)庫(kù)

微服務(wù)架構(gòu)變得越來(lái)越流行了。它是模塊化的一種方法。它把一整塊應(yīng)用拆分成一個(gè)個(gè)服務(wù)。它讓團(tuán)隊(duì)在開發(fā)大型復(fù)雜的應(yīng)用時(shí)更快地交付出高質(zhì)量的軟件。團(tuán)隊(duì)成員們可以輕松地接受到新技術(shù),因?yàn)樗麄兛梢允褂米钚虑彝扑]的技術(shù)棧來(lái)實(shí)現(xiàn)各自的服務(wù)。微服務(wù)架構(gòu)也通過(guò)讓每個(gè)服務(wù)都被部署在最佳狀態(tài)的硬件上而改善了應(yīng)用的擴(kuò)展性。


但微服務(wù)不是萬(wàn)能的。特別是在 領(lǐng)域模型、事務(wù)以及查詢這幾個(gè)地方,似乎總是不能適應(yīng)拆分。或者說(shuō)這幾塊也是微服務(wù)需要專門處理的地方,相對(duì)于過(guò)去的單體架構(gòu)。


在這篇文章中,我會(huì)描述一種開發(fā)微服務(wù)的方法,這個(gè)方法可以解決這些問(wèn)題。主要是通過(guò)領(lǐng)域模型設(shè)計(jì),也就是DDD以及事件源(Event Sourcing)以及CQRS。讓我們首先來(lái)看看開發(fā)人員在開發(fā)微服務(wù)的時(shí)候會(huì)遇到哪些問(wèn)題吧。



微服務(wù)開發(fā)過(guò)程中的挑戰(zhàn)


模塊化在開發(fā)大型復(fù)雜的應(yīng)用的時(shí)候是非常有必要的。


現(xiàn)在許多應(yīng)用大到一個(gè)人根本無(wú)法完成。而且復(fù)雜到光靠一個(gè)人去理解是不可能的。


這種情況下,應(yīng)用就必須被拆分成一個(gè)個(gè)模塊。在單體應(yīng)用中,模塊被定義為比方一個(gè)java package。然而,這種做法在實(shí)踐中并不是很理想,時(shí)間長(zhǎng)了,單體應(yīng)用就變得越來(lái)越龐大。微服務(wù)架構(gòu)把服務(wù)作為一個(gè)模塊單元。


每個(gè)服務(wù)對(duì)應(yīng)一個(gè)業(yè)務(wù)能力,這個(gè)業(yè)務(wù)能力是組織為了創(chuàng)造價(jià)值而需要的。例如,基于微服務(wù)的在線商店包括各種服務(wù),包括訂購(gòu)服務(wù)(Order Service),客戶服務(wù)(Customer Service),目錄服務(wù)(Catalog Service)。


微服務(wù)業(yè)務(wù)開發(fā)三個(gè)難題-拆分、事務(wù)、查詢(上)

每個(gè)服務(wù)都有一個(gè)不可***且很難違反的邊界。也就是每個(gè)微服務(wù)要提供一種單獨(dú)而獨(dú)立的能力。這樣的話,應(yīng)用程序的模塊化就更容易隨時(shí)間保存。


微服務(wù)架構(gòu)還有其他優(yōu)點(diǎn)。包括獨(dú)立地部署服務(wù),獨(dú)立地?cái)U(kuò)展服務(wù)等等這些能力。相比單體來(lái)說(shuō)。


不幸的是,拆分并沒有聽起來(lái)那么容易。相當(dāng)難。


應(yīng)用的領(lǐng)域模型,事務(wù),查詢這三個(gè)東西就是拆分過(guò)程中和拆分后你所面臨的拆分難題。讓我們來(lái)看看具體原因吧。


問(wèn)題1 – 拆分領(lǐng)域模型


領(lǐng)域模型模式是實(shí)現(xiàn)復(fù)雜業(yè)務(wù)邏輯的一種非常好的方式。比如針對(duì)一個(gè)在線商店,領(lǐng)域模型將會(huì)包含這么幾個(gè)類: Order, OrderLineItem, Customer 和 Product。在微服務(wù)架構(gòu)中,Order和OrderLineItem類是Order Service的一部分;Customer是Customer Service的一部分;Product屬于Catalog Service的一部分。


微服務(wù)業(yè)務(wù)開發(fā)三個(gè)難題-拆分、事務(wù)、查詢(上)


拆分領(lǐng)域模型的挑戰(zhàn)之一就是class們通常會(huì)引用一個(gè)或多個(gè)其他類。


比如,Order類引用了該訂單的客戶Customer;OrderLineItem引用了該訂單所訂產(chǎn)品Product。


對(duì)于這些想要橫跨服務(wù)邊界的引用,我們?cè)撛趺崔k呢?


稍后你將會(huì)看到一個(gè)來(lái)自領(lǐng)域模型設(shè)計(jì)的概念:聚合(Aggregate)。我們通過(guò)聚合來(lái)解決這個(gè)問(wèn)題。


微服務(wù)和數(shù)據(jù)庫(kù)


微服務(wù)架構(gòu)的一個(gè)非常明顯的功能就是一個(gè)服務(wù)所擁有的數(shù)據(jù)只能通過(guò)這個(gè)服務(wù)的API來(lái)訪問(wèn)。


在一個(gè)電商網(wǎng)站中,比如,OrderService占有一個(gè)數(shù)據(jù)庫(kù),里邊有一張表ORDERS;CustomerService也有自己的數(shù)據(jù)庫(kù)包含表CUSTOMERS。


通過(guò)這樣的封裝,微服務(wù)之間就解耦了。


在開發(fā)期間,開發(fā)人員可以獨(dú)立修改自己服務(wù)的數(shù)據(jù)庫(kù)shema而不需要與其他服務(wù)的開發(fā)協(xié)調(diào)勾兌。


在生產(chǎn)上,服務(wù)之間都是隔離的。比如,一個(gè)服務(wù)從來(lái)不會(huì)因?yàn)榱硗庖粋€(gè)服務(wù)占有了數(shù)據(jù)庫(kù)的鎖而導(dǎo)致阻塞等待。


不幸的是,這種數(shù)據(jù)庫(kù)的拆分讓管理數(shù)據(jù)的一致性以及不同服務(wù)間跨表查詢變得困難。


問(wèn)題2 – 跨服務(wù)分布式事務(wù)實(shí)現(xiàn)


一個(gè)傳統(tǒng)的單體應(yīng)用可以通過(guò)ACID事務(wù)來(lái)強(qiáng)制業(yè)務(wù)規(guī)則從而實(shí)現(xiàn)一致性。


想象一下,比如,電商里的用戶都有信用額度,就是在創(chuàng)建訂單之前必須先看信用如何。


應(yīng)用程序必須確保潛在的多個(gè)并發(fā)嘗試去創(chuàng)建訂單不超過(guò)客戶的信用限額。


如果Orders和Customers都在同一個(gè)庫(kù)中,那么就可以使用ACID事務(wù)來(lái)搞定:


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)中我們無(wú)法通過(guò)這種方式管理數(shù)據(jù)的一致性。



ORDERS和CUSTOMERS表被不同的服務(wù)所擁有,只能通過(guò)各自的服務(wù)API訪問(wèn)。他們甚至可能在不同的數(shù)據(jù)庫(kù)。


一種比較常見的做法就是使用分布式事務(wù)來(lái)搞定,比如2PC等。但是這種做法對(duì)于現(xiàn)代應(yīng)用來(lái)說(shuō)也許不是一種可行的方案。CAP定理要求你必須在可用性和一致性之間選擇,可用性通常是較好的選擇。


而且,許多現(xiàn)代技術(shù),例如大多數(shù)NoSQL數(shù)據(jù)庫(kù),甚至不支持ACID事務(wù),更不用說(shuō)2PC。


所以管理數(shù)據(jù)的一致性需要使用其他的方式。


稍后你將會(huì)看到我們使用事件驅(qū)動(dòng)架構(gòu)中的一種技術(shù)叫事件源(event sourcing)來(lái)解決分布式事務(wù)。



問(wèn)題3 -查詢


管理數(shù)據(jù)一致性不是唯一的挑戰(zhàn)。還有一個(gè)問(wèn)題就是查詢問(wèn)題。


在傳統(tǒng)的單體應(yīng)用中,我們通常使用join來(lái)實(shí)現(xiàn)跨表查詢。

比如,我們可以通過(guò)下面的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ú)法在微服務(wù)架構(gòu)中實(shí)現(xiàn)這樣的查詢。


就像前面提到的那樣,ORDERS與CUSTOMERS表分屬不同的服務(wù),只能通過(guò)服務(wù)API來(lái)訪問(wèn)。


而且他們可能使用了不同的數(shù)據(jù)庫(kù)。

而且,即使你使用事件源(Event Sourcing )處理查詢問(wèn)題可能更麻煩。


稍后,你將會(huì)學(xué)習(xí)到一種解決方案就是通過(guò)一種叫CQRS(Command Query Responsibility Segregation)做法來(lái)解決分布式查詢問(wèn)題。


但首先,讓我們看看領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(DDD)這個(gè)工具,在我們的微服務(wù)架構(gòu)下基于領(lǐng)域模型開發(fā)業(yè)務(wù)邏輯是必要的。


DDD聚合是微服務(wù)的構(gòu)建塊


像你看到的那樣,為了使用微服務(wù)架構(gòu)成功的開發(fā)業(yè)務(wù)應(yīng)用,我們必須去解決上面所說(shuō)的那些問(wèn)題。


這幾個(gè)問(wèn)題的解決辦法你可以去Eric Evans的書Domain-Driven Design中找得到。


這本書,是2003年出版的,主要介紹了設(shè)計(jì)復(fù)雜軟件的一些方法。這些方法對(duì)開發(fā)微服務(wù)也同樣有用。


尤其是領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)可以讓你創(chuàng)建一個(gè)模塊化的領(lǐng)域模型,這個(gè)領(lǐng)域模型可以被多個(gè)微服務(wù)所使用。 



什么是聚合?


在領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)中,Evans為領(lǐng)域模型定義了幾個(gè)構(gòu)建塊。


許多已經(jīng)成為日常開發(fā)人員語(yǔ)言的一部分,包括entity,就是指一個(gè)具有唯一標(biāo)識(shí)的持久化對(duì)象。value object,也就是VO,你經(jīng)常聽說(shuō)的,是用來(lái)存放數(shù)據(jù)的,可以與數(shù)據(jù)庫(kù)表對(duì)應(yīng),也可以不對(duì)應(yīng),有點(diǎn)類似用來(lái)傳輸數(shù)據(jù)的DTO。service,就是指包含業(yè)務(wù)邏輯的服務(wù)。但不應(yīng)歸類到entity或者value object。

repository,表示一堆entity 的集合就是一個(gè)repository。


構(gòu)建塊(building block),聚合(aggregate)常常被開發(fā)人員忽略,除了那些DDD愛好者,或者叫狂熱分子。


然而,聚合(aggregate)被證明是開發(fā)微服務(wù)的關(guān)鍵,非常重要。


一個(gè)聚合(aggregate)就是一組domain的集合,可以被當(dāng)作一個(gè)單元來(lái)處理。這里說(shuō)的一個(gè)單元就是可以當(dāng)做原子來(lái)處理。


它包含了一個(gè)root entity以及可能還有一到多個(gè)關(guān)聯(lián)的entity以及value object。


比如,針對(duì)一個(gè)在線商店的domain model就會(huì)有幾個(gè)聚合,比如OrderCustomer。


Order聚合又由一個(gè)root entity Order和一個(gè)以上的OrderLineItem value object組成,而且OrderLineItem還有可能關(guān)聯(lián)有其他vo,比如快遞地址(Address)以及支付賬戶信息PaymentInformation。


Customer聚合又由一個(gè)root entity Customer和其他的vo比如DeliveryInfo PaymentInformation組成。




使用聚合將領(lǐng)域模型(domain model)分散和參與到每個(gè)聚合中,這也使得領(lǐng)域模型更容易理解了。這也同時(shí)厘清了操作的scope,比如查詢操作和刪除操作等。


一個(gè)聚合通常作為一個(gè)整體被從數(shù)據(jù)庫(kù)中load出來(lái)。刪除一個(gè)聚合,也就是刪除了里邊所有的object。


然而,聚合的好處遠(yuǎn)遠(yuǎn)超出了模塊化一個(gè)領(lǐng)域模型。 這是因?yàn)榫酆媳仨氉袷匾欢ǖ囊?guī)則。


聚合之間的引用必須使用主鍵


第一個(gè)規(guī)則就是聚合通過(guò)id(例如主鍵)來(lái)引用而不是通過(guò)對(duì)象引用 


比如,Order通過(guò)customerId來(lái)引用Customer,而不是引用Customer的對(duì)象。

類似的,OrderLineItem通過(guò)productId來(lái)引用Product。



這種做法與傳統(tǒng)的object modeling非常的不同。雖然后者認(rèn)為通過(guò)外鍵引用在領(lǐng)域模型中這樣做看起來(lái)怪怪的。


通過(guò)使用ID而不是object引用,意味著聚合是松耦合。你可以輕松地把不同的聚合放在不同的service。


事實(shí)上,一個(gè)微服務(wù)的業(yè)務(wù)邏輯是由一個(gè)領(lǐng)域模型組成。這個(gè)領(lǐng)域模型是幾個(gè)聚合的一個(gè)組合。比如,OrderService包含了Customer聚合。


一個(gè)事務(wù)只創(chuàng)建或更新一個(gè)聚合


第二個(gè)規(guī)則就是聚合必須遵循一個(gè)事務(wù)只能對(duì)一個(gè)聚合進(jìn)行創(chuàng)建或更新。


當(dāng)我第一次看這些規(guī)則的時(shí)候,當(dāng)時(shí)并沒有什么感覺。因?yàn)槟菚r(shí)候,我還在開發(fā)傳統(tǒng)的單體應(yīng)用,那種基于RDBMS的應(yīng)用。所以事務(wù)可以更新任何的數(shù)據(jù)。今天,這些約束依然適用于微服務(wù)架構(gòu)。它確保一個(gè)事務(wù)只被包含在一個(gè)微服務(wù)中。此約束還符合大多數(shù)NoSQL數(shù)據(jù)庫(kù)的有限事務(wù)模型。


當(dāng)開發(fā)一個(gè)領(lǐng)域模型,一個(gè)很重要的事情就是你必須確定每個(gè)聚合得搞多大。


一方面,聚合理想情況下應(yīng)該是小的。它通過(guò)分離關(guān)注點(diǎn)來(lái)改善模塊化。

這是更有效的,因?yàn)榫酆贤ǔ1蝗考虞d。

此外,由于對(duì)每個(gè)聚合的更新是順序發(fā)生的,因此使用細(xì)粒度聚合將增加應(yīng)用程序可以處理的并發(fā)請(qǐng)求數(shù),從而提高可擴(kuò)展性。

它還將改善用戶體驗(yàn),因?yàn)樗档土藘蓚€(gè)用戶嘗試更新同一聚合的可能性。


另一方面,因?yàn)榫酆鲜鞘聞?wù)的范圍,您可能需要定義一個(gè)較大的聚合,以使特定的更新原子化。

例如,之前我描述了在在線商店領(lǐng)域模型中,OrderCustomer是獨(dú)立的聚合。


另一種設(shè)計(jì)可以是把Orders作為Customer聚合的一部分。

一個(gè)較大的Customer聚合的好處就是應(yīng)用可以強(qiáng)制對(duì)于信用額度進(jìn)行原子驗(yàn)證。這種方法的缺點(diǎn)是它將訂單和客戶管理功能組合到同一服務(wù)中。這也降低了可擴(kuò)展性,因?yàn)楦峦豢蛻舻牟煌唵蔚氖聞?wù)將被順序化。


類似的,兩個(gè)用戶去嘗試編輯同一個(gè)客戶下的不同訂單有可能會(huì)沖突。而且,隨著訂單數(shù)量的增加,加載一個(gè)Customer聚合的成本也會(huì)變得更昂貴。


由于這些問(wèn)題,盡可能的把聚合細(xì)粒度是最好的。

即使一個(gè)事務(wù)只能創(chuàng)建和更新一個(gè)單獨(dú)的聚合,微服務(wù)應(yīng)用中也依然必須去管理聚合之間的一致性。

Order服務(wù)中必須驗(yàn)證一個(gè)新建的Order聚合將不超過(guò)Customer聚合的信用額度。


這里有兩種不同的解決一致性的方法。


一個(gè)做法就是在單個(gè)事務(wù)中欺騙的創(chuàng)建和/或更新多個(gè)聚合。這種做法的前提是,所有的聚合都被一個(gè)服務(wù)所擁有并且這些聚合都被持久保存在同一個(gè)RDBMS中才有可能。


另一個(gè)做法就是使用最終一致的事件驅(qū)動(dòng)(event-driven)方法來(lái)維護(hù)聚合之間的一致性。


使用事件驅(qū)動(dòng)來(lái)維護(hù)數(shù)據(jù)一致性


在現(xiàn)代應(yīng)用中,對(duì)事務(wù)有各種約束,這使得難以在服務(wù)之間維持?jǐn)?shù)據(jù)一致性。

每個(gè)服務(wù)都有自己的私有的數(shù)據(jù),這時(shí)候2PC的方案就變得不可行了。

更重要的是,很多的應(yīng)用使用的是NoSQL數(shù)據(jù)庫(kù),這些數(shù)據(jù)庫(kù)根本就不支持本地ACID事務(wù),更不用說(shuō)分布式事務(wù)了。


因此,現(xiàn)代應(yīng)用程序必須使用事件驅(qū)動(dòng)的,最終一致的事務(wù)模型。


什么是事件(Event)?


根據(jù)Merriam-Webster(一個(gè)單詞網(wǎng)站),事件的意思就是:something that happens:




在本文中,我們將領(lǐng)域事件定義為聚合發(fā)生的事件。一個(gè)事件(event)通常表示一個(gè)狀態(tài)的改變?,F(xiàn)在還是拿電商系統(tǒng)舉例,一個(gè)Order聚合。其狀態(tài)更改事件包括訂單已創(chuàng)建(Order Created),訂單已取消(Order Cancelled),訂單已下達(dá)(Order Shipped)。事件可以表示違反業(yè)務(wù)規(guī)則的動(dòng)作,如客戶(Customer)的信用額度。


使用Event-Driven架構(gòu)


服務(wù)們使用事件來(lái)管理聚合之間的一致性,像下面這樣的一個(gè)場(chǎng)景:一個(gè)聚合發(fā)布事件,比如,這個(gè)聚合的狀態(tài)改變或者一次違反業(yè)務(wù)規(guī)則的嘗試等等。

其它聚合訂閱這個(gè)事件,然后負(fù)責(zé)更新他們自己的狀態(tài)。


在線商店制創(chuàng)建一個(gè)訂單(order)的時(shí)候驗(yàn)證客戶(customer)信用額度使用下面一系列步驟:

1.一個(gè)訂單(Order)聚合創(chuàng)建,并且狀態(tài)為NEW,發(fā)布一個(gè)OrderCreated 事件。

2.客戶(Customer)消費(fèi)這個(gè)OrderCreated事件,然后保存為這個(gè)訂單保存信用值然后發(fā)布一個(gè)CreditReserved事件。

3.訂單(Order)聚合消費(fèi)CreditReserved事件,然后修改自己的狀態(tài)為APPROVED。

如果信用檢查由于資金不足而失敗,則客戶(Customer)聚合發(fā)布CreditLimitExceeded事件。


這個(gè)事件不對(duì)應(yīng)于一個(gè)狀態(tài)的改變,而是表示一次違反業(yè)務(wù)規(guī)則的失敗嘗試。 訂單(Order)聚合消費(fèi)這個(gè)事件后,并將自己的狀態(tài)更改為CANCELLED。


微服務(wù)架構(gòu)可以比作事件驅(qū)動(dòng)聚合的Web


在這個(gè)架構(gòu)下,每個(gè)服務(wù)的業(yè)務(wù)邏輯都是由一個(gè)或多個(gè)聚合組成。

一個(gè)事務(wù)只能包含一個(gè)服務(wù),并且是更新或創(chuàng)建一個(gè)單獨(dú)的聚合。也就是聚合內(nèi)事務(wù)。


服務(wù)們通過(guò)使用事件管理聚合之間的一致性。


微服務(wù)業(yè)務(wù)開發(fā)三個(gè)難題-拆分、事務(wù)、查詢(上)


這種做法一個(gè)非常明顯的好處就是一個(gè)個(gè)聚合變成了松散而解耦的構(gòu)建塊。

他們可以被作為單體應(yīng)用來(lái)部署或者作為一組服務(wù)來(lái)部署。

這種情況下,在一個(gè)project開始的時(shí)候,你可以使用單體架構(gòu)。

之后,隨著應(yīng)用的體積和開發(fā)團(tuán)隊(duì)的規(guī)模的擴(kuò)大,你就可以很容易的切換到微服務(wù)架構(gòu)上來(lái)。



總結(jié)


微服務(wù)架構(gòu)從功能上把一整個(gè)應(yīng)用拆分成了一個(gè)個(gè)服務(wù),每個(gè)服務(wù)又都對(duì)應(yīng)一個(gè)業(yè)務(wù)能力。當(dāng)我們開發(fā)基于微服務(wù)架構(gòu)的業(yè)務(wù)應(yīng)用的時(shí)候,一個(gè)關(guān)鍵的挑戰(zhàn)就是事務(wù)、領(lǐng)域模型以及查詢,這三個(gè)主要的麻煩都是拆分之后所帶來(lái)的問(wèn)題。你可以通過(guò)使用DDD聚合的概念來(lái)拆分領(lǐng)域模型。每個(gè)服務(wù)的業(yè)務(wù)邏輯是一個(gè)領(lǐng)域模型,然后這個(gè)領(lǐng)域模型是由一個(gè)或多個(gè)DDD聚合組成。


在每個(gè)服務(wù)中,一個(gè)事務(wù)只能創(chuàng)建或更新一個(gè)單獨(dú)的聚合。由于2PC對(duì)于現(xiàn)代應(yīng)用來(lái)說(shuō)并不是一個(gè)可行的解決方案,所以我們需要使用事件機(jī)制來(lái)去實(shí)現(xiàn)聚合之間的一致性(以及服務(wù)之間)。在下一集,我們會(huì)描述使用event sourcing來(lái)實(shí)現(xiàn)一個(gè)事件驅(qū)動(dòng)的架構(gòu)。我們也會(huì)向你展示在微服務(wù)架構(gòu)下通過(guò)使用CQRS來(lái)實(shí)現(xiàn)查詢。


向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI