編者的話|本文來自 Nginx 官方博客,是「Chris Richardson 微服務(wù)」系列的第五篇文章甘萧。第一篇文章介紹了微服務(wù)架構(gòu)模式彩匕,并且討論了使用微服務(wù)的優(yōu)缺點(diǎn);第二和第三篇描述了微服務(wù)架構(gòu)模塊間通訊的不同方面堪伍;第四篇研究了服務(wù)發(fā)現(xiàn)中的問題锚烦。本篇研究微服務(wù)架構(gòu)帶來的分布式數(shù)據(jù)管理問題。
作者介紹:Chris Richardson帝雇,是世界著名的軟件大師涮俄,經(jīng)典技術(shù)著作《POJOS IN ACTION》一書的作者,也是 cloudfoundry.com 最初的創(chuàng)始人尸闸,Chris Richardson 與 Martin Fowler彻亲、Sam Newman、Adrian Cockcroft 等并稱為世界十大軟件架構(gòu)師吮廉。
Chris Richardson 所著所有文章已獨(dú)家授權(quán) DaoCloud 翻譯并刊載苞尝。
本系列包含 7 篇文章,介紹了微服務(wù)的設(shè)計(jì)宦芦、構(gòu)建和部署宙址,并與傳統(tǒng)的單體架構(gòu)進(jìn)行了比較。本系列將分析微服務(wù)架構(gòu)的各種因素调卑,你也將了解微服務(wù)架構(gòu)模型的優(yōu)劣曼氛、是否適合你的項(xiàng)目,以及如何應(yīng)用令野。
Chris Richardson 微服務(wù)系列全 7 篇:
本期內(nèi)容:
一舀患、微服務(wù)以及分布式數(shù)據(jù)管理中存在的問題
單體應(yīng)用通常使用單個(gè)關(guān)系型數(shù)據(jù)庫,由此帶來的好處在于應(yīng)用能夠使用 ACID 事務(wù)气破,后者提供了重要的操作特性:
- 原子化:原子粒度的更改
- 一致性:數(shù)據(jù)庫的狀態(tài)始終保持一致
- 隔離:并發(fā)執(zhí)行的事務(wù)顯示為串行執(zhí)行
- 持久:事務(wù)一旦提交就不會(huì)被撤銷
如此聊浅,應(yīng)用能夠簡單地開始事務(wù)、更改(插入现使、更新和刪除)多行低匙、以及提交事務(wù)。
使用關(guān)系型數(shù)據(jù)庫的另一大好處是它支持 SQL碳锈。SQL 是一門豐富顽冶、可聲明的和標(biāo)準(zhǔn)化的查詢預(yù)約。用戶能夠輕松通過查詢將多個(gè)表中的數(shù)據(jù)組合起來售碳,然后 RDBMS 查詢調(diào)度器決定執(zhí)行查詢的最優(yōu)方法强重。用戶不必關(guān)心底層細(xì)節(jié),比如如何訪問數(shù)據(jù)庫贸人。此外间景,由于所有的應(yīng)用數(shù)據(jù)在一個(gè)數(shù)據(jù)庫中,很容易查詢艺智。
然而倘要,微服務(wù)架構(gòu)中的數(shù)據(jù)訪問變得復(fù)雜許多。每個(gè)微服務(wù)擁有的數(shù)據(jù)專門用于該微服務(wù)十拣,僅通過其 API 訪問封拧。這種數(shù)據(jù)封裝保證了微服務(wù)松散耦合志鹃,并且可以獨(dú)立更新。但如果多個(gè)服務(wù)訪問相同數(shù)據(jù)泽西,架構(gòu)更新會(huì)耗費(fèi)時(shí)間曹铃、也需要所有服務(wù)的協(xié)調(diào)更新。
更糟糕的是尝苇,不同的微服務(wù)通常使用不同類型的數(shù)據(jù)庫。現(xiàn)代應(yīng)用存儲(chǔ)和處理各種類型的數(shù)據(jù)埠胖,而關(guān)系型數(shù)據(jù)庫并非總是好選擇糠溜。對(duì)于一些使用場景,特定的 NoSQL 數(shù)據(jù)庫能提供更方便的數(shù)據(jù)模型直撤、更好的性能和可擴(kuò)展性非竿。譬如,服務(wù)使用 Elasticsearch 這樣的文本搜索引擎來存儲(chǔ)和查詢文本谋竖;同樣地红柱,存儲(chǔ)社交圖譜數(shù)據(jù)的服務(wù)可能需要使用 Neo4j 這樣的圖譜數(shù)據(jù)庫。因此蓖乘,基于微服務(wù)的應(yīng)用通常會(huì)混合使用 SQL 和 NoSQL 數(shù)據(jù)庫锤悄,即多語言留存(polyglot persistence approach)。
分區(qū)的嘉抒、多語言留存的架構(gòu)對(duì)于數(shù)據(jù)存儲(chǔ)有很多好處零聚,包括服務(wù)的松耦合、更好的性能和可擴(kuò)展性些侍。然而隶症,它也確實(shí)給分布式數(shù)據(jù)管理帶來了挑戰(zhàn)。
第一個(gè)挑戰(zhàn)就是如何實(shí)現(xiàn)業(yè)務(wù)邏輯岗宣,保持多種服務(wù)的一致性蚂会。為了說明為何這是一個(gè)問題,我們以在線 B2B 商店為例耗式。Customer Service(下文使用客戶服務(wù))維護(hù)與用戶有關(guān)的信息胁住,包括信用信息。Order Service(下文使用訂單服務(wù))管理訂單刊咳,驗(yàn)證新訂單沒有超出用戶的信用額度措嵌。在單體應(yīng)用里,訂單服務(wù)可以簡單地使用 ACID 事務(wù)來核對(duì)提供的信用信息和創(chuàng)建訂單芦缰。
相反企巢,在微服務(wù)架構(gòu)中,如下圖所示让蕾,訂單表和客戶表為各自對(duì)應(yīng)的服務(wù)私有浪规。
訂單服務(wù)無法直接訪問客戶表或听,只能通過客戶服務(wù)提供的 API。訂購服務(wù)可能使用分布式事務(wù)笋婿,也被稱為兩步提交(2PC)誉裆。然而,2PC 通常不是現(xiàn)代應(yīng)用的可行選項(xiàng)缸濒。CAP 定理需要用戶在可用性和 ACID 風(fēng)格的一致性中二選一足丢,通常可用性是更好的選擇庇配。此外斩跌,許多現(xiàn)代技術(shù),譬如大多數(shù) NoSQL 數(shù)據(jù)庫并不支持 2PC捞慌。維護(hù)整個(gè)服務(wù)和數(shù)據(jù)庫中的數(shù)據(jù)一致性是至關(guān)重要的耀鸦,因此我們需要另一種解決方案。
第二個(gè)挑戰(zhàn)就是如何實(shí)現(xiàn)檢索多個(gè)服務(wù)數(shù)據(jù)的查詢啸澡。假設(shè)應(yīng)用需要顯示一位客戶和他的最近的訂單袖订。如果訂單服務(wù)為檢索客戶訂單提供了 API,那么可以使用應(yīng)用端獲取該數(shù)據(jù)嗅虏。應(yīng)用通過客戶服務(wù)檢索該客戶洛姑,通過訂單服務(wù)檢索該顧客的訂單。但是假如訂單服務(wù)只支持通過訂單主鍵查詢訂單(可能使用僅支持鍵值檢索的 NoSQL 數(shù)據(jù)庫)皮服,這種情況下吏口,就沒有合適的方法來檢索所需數(shù)據(jù)。
二冰更、事件驅(qū)動(dòng)的架構(gòu)
對(duì)于許多應(yīng)用产徊,解決方案就是事件驅(qū)動(dòng)的架構(gòu)。在這一架構(gòu)里蜀细,當(dāng)有顯著事件發(fā)生時(shí)舟铜,譬如更新業(yè)務(wù)實(shí)體,某個(gè)微服務(wù)會(huì)發(fā)布事件奠衔,其它微服務(wù)則訂閱這些事件谆刨。當(dāng)某一微服務(wù)接收到事件就可以更新自己的業(yè)務(wù)實(shí)體,實(shí)現(xiàn)更多事件被發(fā)布归斤。
用戶能夠使用事件來實(shí)現(xiàn)跨多個(gè)服務(wù)的業(yè)務(wù)邏輯痊夭。事務(wù)由一系列步驟組成,每一步都有一個(gè)微服務(wù)更新業(yè)務(wù)實(shí)體脏里,然后發(fā)布觸發(fā)下一步的事件她我。下面的系列圖展示了如何使用事件驅(qū)動(dòng)的方法在創(chuàng)建訂單時(shí)檢查可用信用。微服務(wù)通過消息代理來交換事件。
1. 訂單服務(wù)創(chuàng)建狀態(tài)為 NEW 的訂單番舆,并發(fā)布“訂單已創(chuàng)建”事件酝碳。
2. 客戶服務(wù)獲取“訂單已創(chuàng)建”事件,為此訂單保留信用恨狈,發(fā)布“信用保留”事件疏哗。
3. 訂單服務(wù)獲取“信用保留”事件,把訂單狀態(tài)修改為 OPEN禾怠。
更為復(fù)雜的場景可能涉及更多的步驟返奉,比如在核對(duì)客戶信用的同時(shí)預(yù)留庫存。
基于(a)每個(gè)服務(wù)自動(dòng)更新數(shù)據(jù)庫和發(fā)布事件吗氏,以及(b)消息代理確保事件傳遞至少一次芽偏,用戶能夠跨多個(gè)服務(wù)完成業(yè)務(wù)邏輯。注意它們并非 ACID 業(yè)務(wù)牲证。這種模式提供弱確定性哮针,比如最終一致性关面。這種事務(wù)模型也被稱作 BASE 模型坦袍。
用戶也可以使用事件來維護(hù)不同微服務(wù)擁有的預(yù)連接數(shù)據(jù)的物化視圖。維護(hù)此視圖的服務(wù)訂閱相關(guān)事件等太,并更新視圖捂齐。例如,維護(hù)客戶訂單視圖的客戶訂單視圖更新服務(wù)會(huì)訂閱由客戶服務(wù)和訂單服務(wù)發(fā)布的事件缩抡。
當(dāng)客戶訂單查看更新服務(wù)收到客戶或者訂單事件奠宜,就會(huì)更新客戶訂單查看的數(shù)據(jù)存儲(chǔ)。用戶能夠使用類似 MongoDB 的文檔數(shù)據(jù)庫查看用戶訂單瞻想,并為每位客戶存儲(chǔ)一個(gè)文檔压真。用戶訂單預(yù)覽查詢服務(wù)通過客戶訂單預(yù)覽數(shù)據(jù)存儲(chǔ),處理來自客戶和最近訂單的請(qǐng)求蘑险。
事件驅(qū)動(dòng)的架構(gòu)有優(yōu)點(diǎn)也有缺點(diǎn)滴肿。它使得事務(wù)跨多個(gè)服務(wù)并提供最終一致性,也可以讓應(yīng)用維護(hù)物化視圖佃迄。缺點(diǎn)之一在于泼差,它的編程模型要比使用 ACID 事務(wù)的更加復(fù)雜。為了從應(yīng)用級(jí)別的失效中恢復(fù)呵俏,還需要完成補(bǔ)償性事務(wù)堆缘,例如,如果信用檢查不成功則必須取消訂單普碎。此外吼肥,由于臨時(shí)事務(wù)造成的改變顯而易見,因而應(yīng)用必須處理不一致的數(shù)據(jù)。此外潜沦,如果應(yīng)用從物化視圖中讀取的數(shù)據(jù)沒有更新時(shí)萄涯,也會(huì)遇到不一致的問題。此架構(gòu)的另一缺點(diǎn)就是用戶必須檢測并忽略重復(fù)事件唆鸡。
三涝影、實(shí)現(xiàn)原子化
事件驅(qū)動(dòng)的架構(gòu)還存在以原子粒度更新數(shù)據(jù)庫并發(fā)布事件的問題。例如争占,訂單服務(wù)必須在訂單表中插入一行燃逻,然后發(fā)布“訂單已創(chuàng)建”事件。這兩個(gè)操作需要原子化實(shí)現(xiàn)臂痕。如果服務(wù)在更新數(shù)據(jù)庫之后伯襟、發(fā)布事件之前崩潰,系統(tǒng)變得不一致握童。確保原子化的標(biāo)準(zhǔn)做法是使用包含數(shù)據(jù)庫和消息代理的分布式事務(wù)姆怪。然而,基于以上描述的 CAP 理論澡绩,這并非我們所想稽揭。
使用本地事務(wù)發(fā)布事件
實(shí)現(xiàn)原子化的方法是使用多步驟進(jìn)程來發(fā)布事件,該進(jìn)程只包含本地事務(wù)肥卡。訣竅就是在存儲(chǔ)業(yè)務(wù)實(shí)體狀態(tài)的數(shù)據(jù)庫中溪掀,有一個(gè)事件表來充當(dāng)消息隊(duì)列。應(yīng)用啟動(dòng)一個(gè)(本地)數(shù)據(jù)庫事務(wù)步鉴,更新業(yè)務(wù)實(shí)體的狀態(tài)揪胃,在事件表中插入一個(gè)事件,并提交該事務(wù)氛琢。獨(dú)立的應(yīng)用線程或進(jìn)程查詢事件表喊递,將事件發(fā)不到消息代理,然后使用本地事務(wù)標(biāo)注事件并發(fā)布阳似。下圖展示了這一設(shè)計(jì)骚勘。
訂單服務(wù)在訂單表中插入一行,然后在事件表中插入“訂單已創(chuàng)建”的事件障般。時(shí)間發(fā)布線程或進(jìn)程在事件表中查詢未發(fā)布的事件并發(fā)布调鲸,然后更新事件表,將該事件標(biāo)記為已發(fā)布挽荡。
這種方法優(yōu)缺點(diǎn)兼具藐石。優(yōu)點(diǎn)之一是保證每個(gè)更新都有對(duì)應(yīng)的事件發(fā)布,并且無需依賴 2PC定拟。此外于微,應(yīng)用發(fā)布業(yè)務(wù)級(jí)別的事件逗嫡,消除了推斷事件的需要。這種方法也有缺點(diǎn)株依。由于開發(fā)者必須牢記發(fā)布事件驱证,因此有很大可能出錯(cuò)。此外這一方法對(duì)于某些使用 NoSQL 數(shù)據(jù)庫的應(yīng)用是個(gè)挑戰(zhàn)恋腕,因?yàn)?NoSQL 本身交易和查詢能力有限抹锄。
通過此方法,應(yīng)用使用本地事務(wù)來更新狀態(tài)和發(fā)布事件荠藤,排除了對(duì) 2PC 的需要伙单。接下來,我們了解使用應(yīng)用更新狀態(tài)實(shí)現(xiàn)原子化的方法哈肖。
挖掘數(shù)據(jù)庫事務(wù)日志
無需 2PC 實(shí)現(xiàn)原子化的另一種方式是由線程或者進(jìn)程通過挖掘數(shù)據(jù)庫事務(wù)或提交日志來發(fā)布事件吻育。應(yīng)用更新數(shù)據(jù)庫,數(shù)據(jù)庫的事務(wù)日志記錄這些變更淤井。事務(wù)日志挖掘線程或進(jìn)程讀取這些日志布疼,并把事件發(fā)布到消息代理。如下圖所示:
這一方法的范例是開源的 LinkedIn Databus 項(xiàng)目币狠。Databus 挖掘 Oracle 事務(wù)日志并發(fā)布與之對(duì)應(yīng)的事件游两。LinkedIn 使用 Databus 維持各種來源的數(shù)據(jù)存儲(chǔ)與記錄系統(tǒng)一致。
另一個(gè)范例則是 AWS DynamoDB 采用的流機(jī)制总寻,AWS DynamoDB 是一個(gè)可管理的 NoSQL 數(shù)據(jù)庫器罐。每個(gè) DynamoDB 流包括 DynamoDB 表在過去 24 小時(shí)之內(nèi)的時(shí)序變化梢为,包括創(chuàng)建渐行、更新和刪除操作。應(yīng)用能夠讀取這些變更铸董,將其作為事件發(fā)布祟印。
事務(wù)日志挖掘具有多個(gè)優(yōu)點(diǎn)。首先粟害,它能保證無需使用 2PC 就能針對(duì)每個(gè)更新發(fā)布事件蕴忆。其次,通過將日志發(fā)布于應(yīng)用的業(yè)務(wù)邏輯分離悲幅,事務(wù)日志挖掘能夠簡化應(yīng)用套鹅。事務(wù)日志挖掘也有缺點(diǎn),主要缺點(diǎn)就是事務(wù)日志的格式與每個(gè)數(shù)據(jù)庫對(duì)應(yīng)汰具,甚至隨著數(shù)據(jù)庫版本而變化卓鹿。此外,很難從底層事務(wù)日志更新記錄中逆向工程這些業(yè)務(wù)事件留荔。
通過讓應(yīng)用更新數(shù)據(jù)庫吟孙,事務(wù)日志挖掘消除了對(duì) 2PC 的需要。接下來我們會(huì)討論另一種方法——消除更新,只依賴事件杰妓。
使用事件源
通過采用一種截然不同的藻治、以事件為中心的方法來留存業(yè)務(wù)實(shí)體,事件源無需 2PC 實(shí)現(xiàn)了原子化巷挥。不同于存儲(chǔ)實(shí)體的當(dāng)前狀態(tài)桩卵,應(yīng)用存儲(chǔ)狀態(tài)改變的事件序列。應(yīng)用通過重播事件來重構(gòu)實(shí)體的當(dāng)前狀態(tài)倍宾。每當(dāng)業(yè)務(wù)實(shí)體的狀態(tài)改變吸占,新事件就被附加到事件列表。鑒于保存事件是一個(gè)單一的操作凿宾,本質(zhì)上也是原子化的矾屯。
要了解事件源如何運(yùn)行,可以以訂單實(shí)體為例初厚。在傳統(tǒng)的方法中件蚕,每個(gè)訂單映射為訂單表的一行,例如一個(gè) ORDERLINEITEM 表产禾。使用事件源的時(shí)候排作,訂單服務(wù)以狀態(tài)更改事件的方式存儲(chǔ)訂單,包括已創(chuàng)建亚情、已批準(zhǔn)妄痪、已發(fā)貨、已取消等楞件。每個(gè)事件都包含足夠的數(shù)據(jù)去重建訂單狀態(tài)衫生。
事件長期保存在事件數(shù)據(jù)庫,使用 API 添加和檢索實(shí)體的事件土浸。事件存儲(chǔ)類似上文提及的消息代理罪针,通過 API 讓服務(wù)訂閱事件,將所有事件傳達(dá)到所有感興趣的訂閱者黄伊。事件存儲(chǔ)是事件驅(qū)動(dòng)的微服務(wù)架構(gòu)的支柱泪酱。
事件源有不少優(yōu)點(diǎn)。它解決了實(shí)施事件驅(qū)動(dòng)的微服務(wù)架構(gòu)時(shí)的一個(gè)關(guān)鍵問題还最,能夠只要狀態(tài)改變就可靠地發(fā)布事件墓阀。另外,它也解決了微服務(wù)架構(gòu)中的數(shù)據(jù)一致性問題拓轻。由于儲(chǔ)存事件而不是域?qū)ο笏勾椋脖苊饬藢?duì)象關(guān)系抗阻不匹配的問題(object?relational impedance mismatch problem)。事件源提供了 100% 可靠的業(yè)務(wù)實(shí)體變化的審計(jì)日志悦即,使得獲取任何時(shí)間點(diǎn)的實(shí)體狀態(tài)成為可能吮成。事件源的另一大優(yōu)勢在于業(yè)務(wù)邏輯由松耦合的橱乱、事件交換的業(yè)務(wù)實(shí)體構(gòu)成,便于從單體應(yīng)用向微服務(wù)架構(gòu)遷移粱甫。
事件源也有缺點(diǎn)泳叠。由于采用了不同或不熟悉的編程風(fēng)格,會(huì)有學(xué)習(xí)曲線茶宵。事件存儲(chǔ)只直接支持通過主鍵查詢業(yè)務(wù)實(shí)體危纫,用戶還需要使用 Command Query Responsibility Segregation (CQRS) 來完成查詢。因此乌庶,應(yīng)用必須處理最終一致的數(shù)據(jù)种蝶。
四、總結(jié)
在微服務(wù)架構(gòu)中瞒大,每個(gè)微服務(wù)都有其私有數(shù)據(jù)存儲(chǔ)螃征,不同的微服務(wù)可能使用不同的 SQL 和 NoSQL 數(shù)據(jù)庫。這些數(shù)據(jù)庫架構(gòu)帶來便利的同時(shí)透敌,也給分布式數(shù)據(jù)管理帶來挑戰(zhàn)盯滚。第一個(gè)挑戰(zhàn)就是如何實(shí)現(xiàn)業(yè)務(wù)事務(wù),保持多個(gè)服務(wù)的一致性酗电。第二個(gè)挑戰(zhàn)就是如何從多個(gè)服務(wù)中檢索數(shù)據(jù)魄藕,實(shí)現(xiàn)查詢。
對(duì)于許多應(yīng)用撵术,解決方案就是使用事件驅(qū)動(dòng)的架構(gòu)背率。事件驅(qū)動(dòng)的架構(gòu)帶來的挑戰(zhàn)是如何原子化地更新狀態(tài)和發(fā)布事件。有幾個(gè)方法可以做到這一點(diǎn)嫩与,包括把數(shù)據(jù)庫用作消息隊(duì)列寝姿、事務(wù)日志挖掘和事件源。