在nginx官網(wǎng)的blog中浮定,作者Chris Richardson關(guān)于微服務(wù)的文章有七篇:
1. Introduction to Microservices(微服務(wù)介紹)
2. Building Microservices: Using an API Gateway(構(gòu)建微服務(wù):API網(wǎng)關(guān))
3. Building Microservices: Inter-Process Communication in a Microservices Architecture(構(gòu)建微服務(wù):微服務(wù)架構(gòu)中的進(jìn)程間通信)
4. Service Discovery in a Microservices Architecture(微服務(wù)架構(gòu)中的服務(wù)發(fā)現(xiàn))
5. Event-Driven Data Management for Microservices(微服務(wù)中基于事件驅(qū)動(dòng)的數(shù)據(jù)管理)
6. Choosing a Microservices Deployment Strategy(微服務(wù)部署策略)
7. Refactoring a Monolith into Microservices(重構(gòu)單體應(yīng)用為微服務(wù))
第5篇文章是我最關(guān)注的問(wèn)題挟憔,這里先翻譯它阴幌。
01 微服務(wù)和分布式數(shù)據(jù)管理的問(wèn)題
單體應(yīng)用一般只會(huì)使用一個(gè)關(guān)系型數(shù)據(jù)庫(kù),使用一個(gè)關(guān)系型數(shù)據(jù)庫(kù)的重要優(yōu)點(diǎn)是蒋困,在你的應(yīng)用中可以使用事務(wù)的ACID特性幢痘,它能提供一些重要的保證:
- 原子性 – 改變都是原子級(jí)的
- 一致性 – 數(shù)據(jù)庫(kù)中的狀態(tài)總是一致的
- 隔離性 – 即使事務(wù)是并行執(zhí)行的,也會(huì)看起來(lái)和串行的一樣
- 持久性 – 一旦事務(wù)被提交家破,數(shù)據(jù)的變化是永久性的
這樣的結(jié)果就是,在應(yīng)用中你可以很輕松地開啟一個(gè)事務(wù)购岗,然后改變(插入汰聋、更新和刪除)多行數(shù)據(jù),最后一起提交這個(gè)事務(wù)喊积。
使用一個(gè)關(guān)系型數(shù)據(jù)庫(kù)的另一個(gè)很大的好處是烹困,它提供了豐富的、聲明式的乾吻、并且是標(biāo)準(zhǔn)的查詢語(yǔ)句(sql)髓梅。你可以很輕松地寫一個(gè)查詢語(yǔ)句,從多張表中查詢整合數(shù)據(jù)绎签。
不幸的是枯饿,當(dāng)我們轉(zhuǎn)向微服務(wù)架構(gòu)的時(shí)候,數(shù)據(jù)訪問(wèn)變得復(fù)雜了很多诡必。那是因?yàn)閿?shù)據(jù)都被每個(gè)微服務(wù)所私有奢方,其他的微服務(wù)只能通過(guò)它的API訪問(wèn)。封裝數(shù)據(jù)是為了確保微服務(wù)之間是松耦合的,能彼此獨(dú)立地發(fā)展蟋字。如果多個(gè)服務(wù)訪問(wèn)同一個(gè)數(shù)據(jù)稿蹲,當(dāng)數(shù)據(jù)結(jié)構(gòu)發(fā)生變化的時(shí)候,需要花費(fèi)很多時(shí)間去協(xié)調(diào)所有相關(guān)的服務(wù)更新鹊奖。
更糟糕的是苛聘,不同的微服務(wù)經(jīng)常使用不同的數(shù)據(jù)庫(kù)。現(xiàn)在的應(yīng)用經(jīng)常存儲(chǔ)和處理多種多樣的數(shù)據(jù)忠聚,關(guān)系型數(shù)據(jù)庫(kù)經(jīng)常不是一種最好的選擇设哗。在某些情況下,一個(gè)特殊的NOSql數(shù)據(jù)庫(kù)會(huì)有更多方便使用的數(shù)據(jù)模型咒林,而且經(jīng)常會(huì)有更高的性能和擴(kuò)展性熬拒。舉個(gè)例子,如果是一個(gè)存儲(chǔ)和檢索文本的服務(wù)垫竞,將會(huì)使用全文搜索引擎澎粟,如Elasticsearch。同樣地欢瞪,如果是一個(gè)社交服務(wù)存儲(chǔ)一些圖數(shù)據(jù)活烙,可能會(huì)使用圖數(shù)據(jù)庫(kù),如Neo4j遣鼓。因此啸盏,基于微服務(wù)的應(yīng)用中經(jīng)常講SQL和NoSql數(shù)據(jù)庫(kù)混合著使用,這就是所謂的混合持久化(Polyglot Persistence)骑祟。
另外回懦,雖然數(shù)據(jù)存儲(chǔ)的混合持久化架構(gòu)帶了很多好處,包括:松耦合服務(wù)次企、高性能和易擴(kuò)展怯晕,然而,這也帶來(lái)了一些分布式數(shù)據(jù)管理的挑戰(zhàn)缸棵。
第一個(gè)挑戰(zhàn)就是舟茶,如何實(shí)現(xiàn)業(yè)務(wù)數(shù)據(jù)的事務(wù),保持跨多個(gè)服務(wù)的數(shù)據(jù)一致性堵第。為了證明為什么這是一個(gè)問(wèn)題吧凉,讓我們看一個(gè)在線B2B商城的例子√ぶ荆客戶服務(wù)提供關(guān)于客戶的信息阀捅,包括他們的信用額度。訂單服務(wù)管理訂單针余,并且驗(yàn)證新訂單金額不能超過(guò)客戶的信用限額也搓。在這個(gè)應(yīng)用的單體架構(gòu)版本中赏廓,訂單服務(wù)可以簡(jiǎn)單地使用一個(gè)ACID的事務(wù)來(lái)實(shí)現(xiàn)校驗(yàn)信用余額和創(chuàng)建訂單。
相比之下傍妒,在微服務(wù)架構(gòu)下訂單表和客戶表是分別屬于各自的服務(wù)的幔摸。如下圖:
訂單服務(wù)是不能直接訪問(wèn)客戶表的,它僅能使用客戶服務(wù)提供的API接口颤练。訂單服務(wù)可以使用分布式事務(wù)既忆,例如眾所周知的兩階段提交(2PC)。然而嗦玖,2PC在現(xiàn)在的應(yīng)用中通常不是一個(gè)可用的選擇患雇。根據(jù)CAP原理,需要你在可用性和ACID式的一致性之間做出選擇宇挫,通晨林ǎ可用性是較好的選擇。此外器瘪,很多現(xiàn)在的技術(shù)不支持2PC翠储,例如大部分的NoSql數(shù)據(jù)庫(kù)。維持跨服務(wù)的數(shù)據(jù)一致性橡疼,數(shù)據(jù)庫(kù)是必不可少的援所,因此我們需要另外的解決方案。
第二個(gè)挑戰(zhàn)是如何實(shí)現(xiàn)從多服務(wù)中檢索數(shù)據(jù)欣除。例如住拭,讓我們想象一下,應(yīng)用需要展示客戶和他最近的訂單历帚。如果訂單服務(wù)提供一個(gè)檢索客戶訂單數(shù)據(jù)的API滔岳,你可以檢索到這些數(shù)據(jù)直接加入到應(yīng)用中。應(yīng)用從客戶服務(wù)中檢索客戶信息挽牢,從訂單服務(wù)中檢索客戶訂單信息澈蟆。然而,假設(shè)訂單服務(wù)僅支持根據(jù)客戶主鍵查詢訂單(也許它使用的是NoSql數(shù)據(jù)庫(kù)卓研,僅支持基于主鍵的檢索)。在這種情況下睹簇,顯然是沒(méi)有簡(jiǎn)單的方法檢索到所需要的數(shù)據(jù)奏赘。
02 基于事件驅(qū)動(dòng)的架構(gòu)
對(duì)于很多應(yīng)用來(lái)說(shuō),解決方案是采用基于事件驅(qū)動(dòng)的架構(gòu)太惠。在這樣的架構(gòu)中磨淌,當(dāng)有重要的事情發(fā)生時(shí)微服務(wù)發(fā)布一個(gè)事件,例如當(dāng)更新一個(gè)交易實(shí)體時(shí)凿渊,其它微服務(wù)需要訂閱這些事件梁只。當(dāng)一個(gè)微服務(wù)接收到一個(gè)事件時(shí)缚柳,它會(huì)更新它自己的交易實(shí)體,同時(shí)這也可能導(dǎo)致更多的事件被發(fā)布搪锣。
你可以使用事件來(lái)實(shí)現(xiàn)跨越多個(gè)服務(wù)的交易的事務(wù)性秋忙。一個(gè)事務(wù)包括一系列的步驟,每一步都是由微服務(wù)更新一個(gè)交易實(shí)體和發(fā)布一個(gè)觸發(fā)下一個(gè)步驟的事件構(gòu)成构舟。下面的序列圖顯示了灰追,你如何使用事件驅(qū)動(dòng)的方法在創(chuàng)建訂單的時(shí)候檢查可用信用額度。微服務(wù)通過(guò)Message Broker交換事件狗超。
- 訂單創(chuàng)建一個(gè)狀態(tài)為NEW的訂單弹澎,然后發(fā)布一個(gè)訂單創(chuàng)建的事件。
- 客戶服務(wù)訂閱訂單創(chuàng)建事件努咐,為這個(gè)訂單預(yù)留信用苦蒿,然后發(fā)布一個(gè)信用預(yù)留的事件。
- 訂單服務(wù)訂閱信用預(yù)留事件渗稍,然后改變訂單狀態(tài)為OPEN佩迟。
如果是更復(fù)雜的場(chǎng)景可以添加額外的步驟,例如在校驗(yàn)客戶信用的同事預(yù)留庫(kù)存免胃。
(1)每個(gè)服務(wù)要保證更新數(shù)據(jù)庫(kù)和發(fā)布事件的原子性音五,更重要的是后者;(2)Message Broker要確保事件被至少傳遞一次羔沙;這樣你就可以實(shí)現(xiàn)跨多個(gè)服務(wù)的交易的事務(wù)躺涝。需要注意的是,這些不是ACID的事務(wù)扼雏。它們只是提供了一種相對(duì)較弱的擔(dān)保坚嗜,例如最終一致性。這種事務(wù)模型已經(jīng)被稱為基礎(chǔ)模型诗充。
你也可以使用事件來(lái)維護(hù)一個(gè)具體的視圖苍蔬,將來(lái)自多個(gè)微服務(wù)的數(shù)據(jù)預(yù)加載進(jìn)來(lái)。維護(hù)這個(gè)視圖的服務(wù)需要訂閱相關(guān)服務(wù)蝴蜓,然后更新這個(gè)視圖碟绑。例如客戶訂單事務(wù)更新服務(wù)維護(hù)了一個(gè)客戶訂單的視圖,它需要訂閱客戶服務(wù)和訂單服務(wù)的事件茎匠。
當(dāng)客戶訂單更新服務(wù)接收到客戶或者是訂單事件時(shí)格仲,它會(huì)更新客戶訂單視圖的數(shù)據(jù)庫(kù)∷忻埃可以使用文檔數(shù)據(jù)庫(kù)(MongoDB)實(shí)現(xiàn)客戶訂單視圖凯肋,為每個(gè)客戶存儲(chǔ)一個(gè)文檔∑觯客戶訂單視圖查詢服務(wù)通過(guò)查詢客戶訂單視圖數(shù)據(jù)庫(kù)處理查詢一個(gè)客戶最近的訂單的請(qǐng)求侮东。
基于事件驅(qū)動(dòng)的架構(gòu)的優(yōu)點(diǎn)和缺點(diǎn):實(shí)現(xiàn)了跨多個(gè)服務(wù)的事務(wù)圈盔,并提供了最終一致性;另一個(gè)優(yōu)點(diǎn)是可以使應(yīng)用維護(hù)一個(gè)實(shí)體化的視圖悄雅。一個(gè)缺點(diǎn)是驱敲,編程模型比使用ACID事務(wù)時(shí)更加復(fù)雜了,通常為了從應(yīng)用級(jí)的故障恢復(fù)煤伟,你必須實(shí)現(xiàn)補(bǔ)償事務(wù)癌佩,例如如果校驗(yàn)信用余額失敗時(shí)你必須取消訂單。還有應(yīng)用必須能夠處理不一致的數(shù)據(jù)便锨,這是因?yàn)榕R時(shí)(in-flight)事務(wù)的影響是可見围辙。此外,如果實(shí)體化的視圖中數(shù)據(jù)還未更新就被服務(wù)讀取到了放案,這樣你也會(huì)看到不一致的數(shù)據(jù)姚建。另一個(gè)缺點(diǎn)是事件的訂閱者必須檢測(cè)和忽略重復(fù)的事件。
03 實(shí)現(xiàn)原子性
在基于事件驅(qū)動(dòng)的架構(gòu)中還有另外一個(gè)問(wèn)題吱殉,就是更新數(shù)據(jù)庫(kù)和發(fā)布事件的原子性掸冤。例如,訂單服務(wù)必須在訂單表中插入一條數(shù)據(jù)同時(shí)發(fā)布一個(gè)訂單創(chuàng)建的事件友雳,這兩個(gè)操作必須原子性地完成稿湿,這是基本要求。如果在更新數(shù)據(jù)庫(kù)之后發(fā)布事件之前服務(wù)崩潰了押赊,系統(tǒng)就變得不一致了饺藤。確保原子性的標(biāo)準(zhǔn)方式是,在涉及到的數(shù)據(jù)庫(kù)和Message Broker中使用分布式事務(wù)流礁,然而由于上述原因涕俗,如CAP原理,這不是我們想要的神帅。
03.1 使用本地事務(wù)發(fā)布事件
實(shí)現(xiàn)原子性的一個(gè)方法是再姑,應(yīng)用使用“僅在本地事務(wù)中做多步驟處理”的方法發(fā)布事件。關(guān)鍵是需要有一個(gè)Event表找御,它的功能是作為一個(gè)消息隊(duì)列元镀,數(shù)據(jù)庫(kù)中存儲(chǔ)交易實(shí)體的狀態(tài)。應(yīng)用可以開啟一個(gè)本地?cái)?shù)據(jù)庫(kù)事務(wù)霎桅,更新交易實(shí)體的狀態(tài)和插入一條數(shù)據(jù)到EVENT表栖疑,然后提交事務(wù)。EVENT表還需要一個(gè)獨(dú)立的應(yīng)用線程或者進(jìn)程來(lái)發(fā)布EVENT到Message Broker哆档,然后使用本地事務(wù)標(biāo)記EVENT已經(jīng)被發(fā)布了。下圖描述了該設(shè)計(jì):
訂單服務(wù)插入一條數(shù)據(jù)到訂單表住闯,同時(shí)插入一個(gè)訂單創(chuàng)建事件到EVENT表瓜浸,EVENT表的事件發(fā)布線程或進(jìn)程會(huì)將這些未發(fā)布的事件發(fā)布到Message Broker中澳淑,然后更新EVENT表中的事件狀態(tài)為已發(fā)布。
這個(gè)方法的優(yōu)點(diǎn)和缺點(diǎn):一個(gè)優(yōu)點(diǎn)是插佛,它能保證每次更新都能將事件發(fā)布出去杠巡,而又不依賴2PC。應(yīng)用發(fā)布了交易級(jí)別的事件雇寇,我們不需要推斷具體發(fā)生了什么氢拥。這個(gè)方法的缺點(diǎn)是,易于犯錯(cuò)锨侯,因?yàn)槌绦騿T必須記得發(fā)布事件嫩海。這種方法還有一個(gè)限制,當(dāng)我們使用NoSql數(shù)據(jù)庫(kù)時(shí)這是一個(gè)挑戰(zhàn)囚痴,因?yàn)镹oSql數(shù)據(jù)庫(kù)的事務(wù)和查詢的能力有限叁怪。
這種方法消除了我們對(duì)2PC的需求,應(yīng)用直接使用本地事務(wù)更新數(shù)據(jù)庫(kù)狀態(tài)和發(fā)布事件∩罟觯現(xiàn)在讓我們看另外一種實(shí)現(xiàn)原子性的方法奕谭,應(yīng)用僅僅需要更新狀態(tài)就能實(shí)現(xiàn)。
03.2 利用數(shù)據(jù)庫(kù)事務(wù)日志
在不使用2PC情況下痴荐,保證發(fā)布事件的原子性的另一個(gè)方法是血柳,創(chuàng)建一個(gè)線程或者進(jìn)程來(lái)采集數(shù)據(jù)庫(kù)的事務(wù)或者提交日志。應(yīng)用更新數(shù)據(jù)庫(kù)的時(shí)候生兆,數(shù)據(jù)被改變的結(jié)果都被記錄在數(shù)據(jù)庫(kù)的事務(wù)日志中难捌。數(shù)據(jù)庫(kù)事務(wù)日志的采集者線程或者進(jìn)程讀取這個(gè)事務(wù)日志,然后發(fā)布事件到Message Broker中皂贩。詳細(xì)設(shè)計(jì)如下圖:
開源的項(xiàng)目 LinkedIn Databus 是一個(gè)這種方法的例子栖榨。Databus采集Oracle數(shù)據(jù)庫(kù)的事務(wù)日志,然后根據(jù)改變內(nèi)容發(fā)布事件明刷。LinkedIn使用Databus保持各種數(shù)據(jù)存儲(chǔ)中的系統(tǒng)數(shù)據(jù)的一致性婴栽。
另一個(gè)例子是,streams mechanism in AWS DynamoDB辈末,這個(gè)是管理NoSql數(shù)據(jù)庫(kù)的工具愚争。DynamoDB流包含過(guò)去24小時(shí)的實(shí)時(shí)訂單的改變(創(chuàng)建、更新和刪除操作)數(shù)據(jù)序列挤聘,這些數(shù)據(jù)項(xiàng)會(huì)被存儲(chǔ)在DynamoDB表中轰枝。有一個(gè)應(yīng)用可以從這個(gè)流中讀取到這些變化,然后將它們發(fā)布成事件组去。
采集事務(wù)日志有優(yōu)點(diǎn)也有缺點(diǎn)鞍陨。一個(gè)優(yōu)點(diǎn)是,在不使用2PC的情況下,能確保每個(gè)更新都被發(fā)布成事件诚撵;采集事務(wù)日志的方法也能簡(jiǎn)化應(yīng)用缭裆,它將發(fā)布事件從應(yīng)用的業(yè)務(wù)邏輯中分離出來(lái)。一個(gè)主要的缺點(diǎn)是寿烟,每個(gè)數(shù)據(jù)庫(kù)的事務(wù)日志格式都是特有的澈驼,甚至是不同的數(shù)據(jù)庫(kù)版本格式也是不同的。同時(shí)筛武,在事務(wù)日志中從低級(jí)別的更新記錄轉(zhuǎn)變?yōu)楦呒?jí)別的業(yè)務(wù)事件的逆向工程是很困難的缝其。
采集事務(wù)日志解除對(duì)2PC的需要,應(yīng)用只需要做一件事:更新數(shù)據(jù)庫(kù)∨橇現(xiàn)在讓我們看另外一種不同的方法内边,不用更新,只需要事件硕噩。
03.3 使用事件源
事件源(Event sourcing)實(shí)現(xiàn)原子性假残,而不使用2PC,這是一個(gè)完全不同的方法炉擅,就是以事件為中心的方法去持久化業(yè)務(wù)實(shí)體辉懒。應(yīng)用存儲(chǔ)一系列的狀態(tài)改變事件,而不是存儲(chǔ)一個(gè)實(shí)體的當(dāng)前狀態(tài)谍失。應(yīng)用依賴重放事件來(lái)重建實(shí)體的當(dāng)前狀態(tài)眶俩,無(wú)論業(yè)務(wù)實(shí)體的狀態(tài)什么時(shí)候變化,一個(gè)新的事件都會(huì)被追加到事件列表中快鱼。因?yàn)榇鎯?chǔ)事件是一個(gè)單獨(dú)的操作颠印,所以它天生具有原子性。
為了展現(xiàn)事件源是如何工作的抹竹,舉一個(gè)訂單實(shí)體的例子线罕,在一個(gè)交易方法中,例如每個(gè)訂單都被映射成ORDER表中的一行數(shù)據(jù)和ORDER_LINE_ITEM表中的一行數(shù)據(jù)窃判。但是當(dāng)我們使用事件源方法時(shí)钞楼,訂單服務(wù)存儲(chǔ)訂單方式是,存儲(chǔ)訂單的狀態(tài)改變事件:創(chuàng)建袄琳、通過(guò)询件、發(fā)貨、取消唆樊。每個(gè)事件包含充足的數(shù)據(jù)去重建訂單狀態(tài)宛琅。
事件持久化在一個(gè)事件數(shù)據(jù)庫(kù)中,該數(shù)據(jù)庫(kù)有添加和查詢實(shí)體事件的API逗旁,這個(gè)事件數(shù)據(jù)庫(kù)在架構(gòu)上類似于一個(gè)Message Broker嘿辟,我們可以實(shí)現(xiàn)訂閱事件。它提供有訂閱事件的API。該事件數(shù)據(jù)庫(kù)會(huì)將所有的事件傳遞到所有感興趣的訂閱者中红伦,事件數(shù)據(jù)庫(kù)是事件驅(qū)動(dòng)的微服務(wù)架構(gòu)的重要保障介陶。
事件源機(jī)制有幾個(gè)優(yōu)點(diǎn),它解決了事件驅(qū)動(dòng)架構(gòu)中的關(guān)鍵問(wèn)題色建,無(wú)論狀態(tài)什么時(shí)候變化都能可靠地發(fā)布事件侥蒙。因此寻拂,它解決了微服務(wù)架構(gòu)中的數(shù)據(jù)一致性問(wèn)題俊庇。而且因?yàn)樗志没氖鞘录皇穷I(lǐng)域?qū)ο笸晃艽蟪潭壬线€避免了對(duì)象和關(guān)系型數(shù)據(jù)庫(kù)字段不匹配的問(wèn)題给涕。事件源提供了100%可靠的業(yè)務(wù)實(shí)體改變的審計(jì)日志怀伦,而且任何時(shí)間點(diǎn)都能查詢到實(shí)體的狀態(tài)忿晕。事件源方法的另外一個(gè)好處是呐芥,由兩個(gè)松耦合的業(yè)務(wù)實(shí)體組成的業(yè)務(wù)邏輯可以交換事件介牙。這樣就方便了單體應(yīng)用轉(zhuǎn)向微服務(wù)架構(gòu)壮虫。
事件源機(jī)制也有幾個(gè)缺點(diǎn),這是一種不同且陌生的編程模型环础,因此有一定的學(xué)習(xí)曲線囚似。事件數(shù)據(jù)庫(kù)僅支持使用主鍵查詢業(yè)務(wù)實(shí)體,你必須使用 Command Query Responsibility Segregation來(lái)實(shí)現(xiàn)查詢线得。因此應(yīng)用程序操作處理最終一致的數(shù)據(jù)饶唤。
04 總結(jié)
在微服務(wù)架構(gòu)中,每個(gè)微服務(wù)都有它自己的數(shù)據(jù)存儲(chǔ)贯钩。不同的微服務(wù)可能使用不同的SQL和NoSQL數(shù)據(jù)庫(kù)募狂,雖然這個(gè)數(shù)據(jù)庫(kù)架構(gòu)有很大的好處,但是它創(chuàng)造了分布式數(shù)據(jù)管理的挑戰(zhàn)角雷。第一個(gè)挑戰(zhàn)是祸穷,為了維持不同服務(wù)之間的數(shù)據(jù)一致性,如何實(shí)現(xiàn)業(yè)務(wù)邏輯中的事務(wù)勺三;第二個(gè)挑戰(zhàn)是雷滚,如何實(shí)現(xiàn)從多個(gè)服務(wù)中檢索數(shù)據(jù)。
對(duì)于大多數(shù)的應(yīng)用來(lái)書檩咱,解決方案都是采用事件驅(qū)動(dòng)的架構(gòu)揭措。實(shí)現(xiàn)事件驅(qū)動(dòng)的架構(gòu)一個(gè)挑戰(zhàn)是,實(shí)現(xiàn)更新數(shù)據(jù)狀態(tài)和發(fā)布事件的原子性刻蚯。實(shí)現(xiàn)這個(gè)目的的方法有幾個(gè)绊含,包括 使用數(shù)據(jù)庫(kù)作為消息隊(duì)列、采集數(shù)據(jù)庫(kù)的事務(wù)日志 和 事件源機(jī)制炊汹。
在未來(lái)的博客中躬充,我們會(huì)繼續(xù)探討微服務(wù)其他方面的話題。