眾所周知拼缝,微服務(wù)架構(gòu)解決了很多問題,通過分解復(fù)雜的單體式應(yīng)用瓷炮,在功能不變的情況下,使應(yīng)用被分解為多個(gè)可管理的服務(wù)递宅,為采用單體式編碼方式很難實(shí)現(xiàn)的功能提供了模塊化的解決方案娘香。同時(shí),每個(gè)微服務(wù)獨(dú)立部署办龄、獨(dú)立擴(kuò)展烘绽,使得持續(xù)化集成成為可能。由此俐填,單個(gè)服務(wù)很容易開發(fā)安接、理解和維護(hù)。
微服務(wù)架構(gòu)為開發(fā)帶來了諸多好處的同時(shí)英融,也引發(fā)了很多問題赫段。比如服務(wù)運(yùn)維變得更復(fù)雜呀打,服務(wù)之間的依賴關(guān)系更復(fù)雜,數(shù)據(jù)一致性難以保證糯笙。
本篇文章將討論和介紹Choerodon豬齒魚是如何保障微服務(wù)架構(gòu)的數(shù)據(jù)一致性的。
主要內(nèi)容包括 :
- 傳統(tǒng)應(yīng)用使用本地事務(wù)保持一致性
- 多數(shù)據(jù)源下的分布式事務(wù)
- 微服務(wù)架構(gòu)中應(yīng)滿足數(shù)據(jù)最終一致性原則
- 使用Event Sourcing保證微服務(wù)的最終一致性
- 使用可靠事件模式保證微服務(wù)的最終一致性
- 使用Saga保證微服務(wù)的最終一致性
下面將通過一個(gè)實(shí)例來分別介紹這幾種模式撩银。
在Choerodon 豬齒魚的 DevOps流程中给涕,有這樣一個(gè)步驟。
- 用戶在Choerodon 平臺上創(chuàng)建一個(gè)項(xiàng)目额获;
- DevOps 服務(wù)對應(yīng)創(chuàng)建一個(gè)項(xiàng)目够庙;
- DevOps 為該項(xiàng)目 在 Gitlab 上創(chuàng)建對應(yīng)的group。
傳統(tǒng)應(yīng)用使用本地事務(wù)保持一致性
在講微服務(wù)架構(gòu)的數(shù)據(jù)一致性之前抄邀,先介紹一下傳統(tǒng)關(guān)系型數(shù)據(jù)庫是如何保證一致性的耘眨,從關(guān)系型數(shù)據(jù)庫中的ACID理論講起。
ACID 即數(shù)據(jù)庫事務(wù)正確執(zhí)行的四個(gè)基本要素境肾。分別是:
- 原子性(Atomicity):要么全部完成剔难,要么全部不完成,不存在中間狀態(tài)
- 一致性(Consistency):事務(wù)必須始終保持系統(tǒng)處于一致的狀態(tài)
- 隔離性(Isolation):事務(wù)之間相互隔離奥喻,同一時(shí)間僅有一個(gè)請求用于同一數(shù)據(jù)
- 持久性(Durability):事務(wù)一旦提交偶宫,該事務(wù)對數(shù)據(jù)庫所作的更改便持久的保存在數(shù)據(jù)庫之中,并不會被回滾
可以通過使用數(shù)據(jù)庫自身的ACID Transactions环鲤,將上述步驟簡化為如下偽代碼:
transaction.strat();
createProject();
devopsCreateProject();
gitlabCreateGroup();
transaction.commit();
這個(gè)過程可以說是十分簡單纯趋,如果在這一過程中發(fā)生失敗,例如DevOps創(chuàng)建項(xiàng)目失敗冷离,那么該事務(wù)做回滾操作吵冒,使得最終平臺創(chuàng)建項(xiàng)目失敗。由于傳統(tǒng)應(yīng)用一般都會使用一個(gè)關(guān)系型數(shù)據(jù)庫西剥,所以可以直接使用 ACID transactions痹栖。 保證了數(shù)據(jù)本身不會出現(xiàn)不一致。為保證一致性只需要:開始一個(gè)事務(wù)蔫耽,改變(插入结耀,刪除,更新)很多行匙铡,然后提交事務(wù)(如果有異常時(shí)回滾事務(wù))图甜。
隨著業(yè)務(wù)量的不斷增長,單數(shù)據(jù)庫已經(jīng)不足以支撐龐大的業(yè)務(wù)數(shù)據(jù)鳖眼,此時(shí)就需要對應(yīng)用和數(shù)據(jù)庫進(jìn)行拆分黑毅,于此同時(shí),也就出現(xiàn)了一個(gè)應(yīng)用需要同時(shí)訪問兩個(gè)或者兩個(gè)以上的數(shù)據(jù)庫或多個(gè)應(yīng)用分別訪問不同的數(shù)據(jù)庫的情況钦讳,數(shù)據(jù)庫的本地事務(wù)則不再適用矿瘦。
為了解決這一問題枕面,分布式事務(wù)應(yīng)運(yùn)而生。
多數(shù)據(jù)源下的分布式事務(wù)
想象一下缚去,如果很多用戶同時(shí)對Choerodon 平臺進(jìn)行創(chuàng)建項(xiàng)目的操作潮秘,應(yīng)用接收的流量和業(yè)務(wù)數(shù)據(jù)劇增。一個(gè)數(shù)據(jù)庫并不足以存儲所有的業(yè)務(wù)數(shù)據(jù)易结,那么我們可以將應(yīng)用拆分成IAM服務(wù)和DevOps服務(wù)枕荞。其中兩個(gè)服務(wù)分別使用各自的數(shù)據(jù)庫,這樣的情況下搞动,我們就減輕了請求的壓力和數(shù)據(jù)庫訪問的壓力躏精,兩個(gè)分別可以很明確的知道自己執(zhí)行的事務(wù)是成功還是失敗。但是同時(shí)在這種情況下鹦肿,每個(gè)服務(wù)都不知道另一個(gè)服務(wù)的狀態(tài)矗烛。因此,在上面的例子中箩溃,如果當(dāng)DevOps創(chuàng)建項(xiàng)目失敗時(shí)瞭吃,就無法直接使用數(shù)據(jù)庫的事務(wù)。
那么如果當(dāng)一個(gè)事務(wù)要跨越多個(gè)分布式服務(wù)的時(shí)候碾篡,我們應(yīng)該如何保證事務(wù)呢?
為了保證該事務(wù)可以滿足ACID虱而,一般采用2PC或者3PC。 2PC(Two Phase Commitment Protocol)开泽,實(shí)現(xiàn)分布式事務(wù)的經(jīng)典代表就是兩階段提交協(xié)議牡拇。2PC包括準(zhǔn)備階段和提交階段。在此協(xié)議中穆律,一個(gè)或多個(gè)資源管理器的活動均由一個(gè)稱為事務(wù)協(xié)調(diào)器的單獨(dú)軟件組件來控制惠呼。
我們?yōu)镈evOps服務(wù)分配一個(gè)事務(wù)管理器。那么上面的過程可以整理為如下兩個(gè)階段:
準(zhǔn)備階段:
提交/回滾階段:
2PC 提供了一套完整的分布式事務(wù)的解決方案峦耘,遵循事務(wù)嚴(yán)格的 ACID 特性剔蹋。
但是,當(dāng)在準(zhǔn)備階段的時(shí)候辅髓,對應(yīng)的業(yè)務(wù)數(shù)據(jù)會被鎖定泣崩,直到整個(gè)過程結(jié)束才會釋放鎖。如果在高并發(fā)和涉及業(yè)務(wù)模塊較多的情況下洛口,會對數(shù)據(jù)庫的性能影響較大矫付。而且隨著規(guī)模的增大,系統(tǒng)的可伸縮性越差第焰。同時(shí)由于 2PC引入了事務(wù)管理器买优,如果事務(wù)管理器和執(zhí)行的服務(wù)同時(shí)宕機(jī),則會導(dǎo)致數(shù)據(jù)產(chǎn)生不一致。雖然又提出了3PC 將2PC中的準(zhǔn)備階段再次一分為二的來解決這一問題杀赢,但是同樣可能會產(chǎn)生數(shù)據(jù)不一致的結(jié)果烘跺。
微服務(wù)架構(gòu)中應(yīng)滿足數(shù)據(jù)最終一致性原則
不可否認(rèn),2PC 和3PC 提供了解決分布式系統(tǒng)下事務(wù)一致性問題的思路脂崔,但是2PC同時(shí)又是一個(gè)非常耗時(shí)的復(fù)雜過程滤淳,會嚴(yán)重影響系統(tǒng)效率,在實(shí)踐中我們盡量避免使用它砌左。所以在分布式系統(tǒng)下無法直接使用此方案來保證事務(wù)娇钱。
對于分布式的微服務(wù)架構(gòu)而言,傳統(tǒng)數(shù)據(jù)庫的ACID原則可能并不適用绊困。首先微服務(wù)架構(gòu)自身的所有數(shù)據(jù)都是通 過API 進(jìn)行訪問。這種數(shù)據(jù)訪問方式使得微服務(wù)之間松耦合适刀,并且彼此之間獨(dú)立非常容易進(jìn)行性能擴(kuò)展秤朗。其次 不同服務(wù)通常使用不同的數(shù)據(jù)庫,甚至并不一定會使用同一類數(shù)據(jù)庫笔喉,反而使用非關(guān)系型數(shù)據(jù)庫取视,而大部分的 非關(guān)系型數(shù)據(jù)庫都不支持2PC。
在這種情況下常挚,又如何解決事務(wù)一致性問題呢作谭?
一個(gè)最直接的辦法就是考慮數(shù)據(jù)的強(qiáng)一致性。根據(jù)Eric Brewer提出的CAP理論奄毡,只能在數(shù)據(jù)強(qiáng)一致性(C)和可用性(A)之間做平衡折欠。
CAP 是指在一個(gè)分布式系統(tǒng)下,包含三個(gè)要素:Consistency(一致性)吼过、Availability(可用性)锐秦、Partition tolerance(分區(qū)容錯(cuò)性),并且三者不可得兼盗忱。
- 一致性(Consistency)酱床,是指對于每一次讀操作,要么都能夠讀到最新寫入的數(shù)據(jù)趟佃,要么錯(cuò)誤扇谣,所有數(shù)據(jù)變動都是同步的。
- 可用性(Availability)闲昭,是指對于每一次請求罐寨,都能夠得到一個(gè)及時(shí)的、非錯(cuò)的響應(yīng)汤纸,但是不保證請求的結(jié)果是基于最新寫入的數(shù)據(jù)衩茸。即在可以接受的時(shí)間范圍內(nèi)正確地響應(yīng)用戶請求。
- 分區(qū)容錯(cuò)性(Partition tolerance),是指由于節(jié)點(diǎn)之間的網(wǎng)絡(luò)問題楞慈,即使一些消息丟包或者延遲幔烛,整個(gè)系統(tǒng)仍能夠提供滿足一致性和可用性的服務(wù)。
關(guān)系型數(shù)據(jù)庫單節(jié)點(diǎn)保證了數(shù)據(jù)強(qiáng)一致性(C)和可用性(A)囊蓝,但是卻無法保證分區(qū)容錯(cuò)性(P)饿悬。
然而在分布式系統(tǒng)下,為了保證模塊的分區(qū)容錯(cuò)性(P)聚霜,只能在數(shù)據(jù)強(qiáng)一致性(C)和可用性(A)之間做平衡狡恬。具體表現(xiàn)為在一定時(shí)間內(nèi),可能模塊之間數(shù)據(jù)是不一致的蝎宇,但是通過自動或手動補(bǔ)償后能夠達(dá)到最終的一致弟劲。
可用性一般是更好的選擇,但是在服務(wù)和數(shù)據(jù)庫之間維護(hù)事務(wù)一致性是非常根本的需求姥芥,微服務(wù)架構(gòu)中應(yīng)該選擇滿足最終一致性兔乞。
那么我們應(yīng)該如何實(shí)現(xiàn)數(shù)據(jù)的最終一致性呢?
使用Event Sourcing保證微服務(wù)的最終一致性
什么是Event Sourcing(事件溯源)凉唐?
一個(gè)對象從創(chuàng)建開始到消亡會經(jīng)歷很多事件庸追,傳統(tǒng)的方式是保存這個(gè)業(yè)務(wù)對象當(dāng)前的狀態(tài)。但更多的時(shí)候台囱,我們也許更關(guān)心這個(gè)業(yè)務(wù)對象是怎樣達(dá)到這一狀態(tài)的淡溯。Event Sourcing從根本上和傳統(tǒng)的數(shù)據(jù)存儲不同,它存儲的不是業(yè)務(wù)對象的狀態(tài)簿训,而是有關(guān)該業(yè)務(wù)對象一系列的狀態(tài)變化的事件咱娶。只要一個(gè)對象的狀態(tài)發(fā)生變化,服務(wù)就需要自動發(fā)布事件來附加到事件的序列中煎楣。這個(gè)操作本質(zhì)上是原子的豺总。
現(xiàn)在將上面的訂單過程用Event Sourcing 進(jìn)行改造,將訂單變動的一個(gè)個(gè)事件存儲起來择懂,服務(wù)監(jiān)聽事件喻喳,對訂單的狀態(tài)進(jìn)行修改。