分布式計(jì)算是如何控制事務(wù)的?
事務(wù)的管理不應(yīng)該屬于Dubbo框架, Dubbo只需實(shí)現(xiàn)可被事務(wù)管理即可, 像JDBC和JMS都是可被事務(wù)管理的分布式資源, Dubbo只要實(shí)現(xiàn)相同的可被事務(wù)管理的行為,比如可以回滾, 其它事務(wù)的調(diào)度,都應(yīng)該由專門的事務(wù)管理器實(shí)現(xiàn)。 在Java中,分布式事務(wù)主要的規(guī)范是JTA/XA, 其中:JTA是Java的事務(wù)管理器規(guī)范, XA是工業(yè)標(biāo)準(zhǔn)的X/Open CAE規(guī)范,可被兩階段提交及回滾的事務(wù)資源定義, 比如某數(shù)據(jù)庫(kù)實(shí)現(xiàn)了XA規(guī)范,則不管是JTA,還是MSDTC,都可以基于同樣的行為對(duì)該數(shù)據(jù)庫(kù)進(jìn)行事務(wù)處理。
首先是不建議采用XA兩階段提交方式去處理分布式事務(wù),要知道要能夠支持XA分布式事務(wù),必須是要實(shí)現(xiàn)XA規(guī)范才可以,而Service本身是無(wú)狀態(tài)的,如果這樣去做了等于是把Service內(nèi)部的東西暴露了出去。對(duì)于分布式事務(wù)最好的方式還是事務(wù)補(bǔ)償或者BASE基于消息的最終一致性。
可以設(shè)想一個(gè)最簡(jiǎn)單的分布式事務(wù)場(chǎng)景,對(duì)于跨銀行的轉(zhuǎn)賬操作,該操作涉及到調(diào)用兩個(gè)異地的Service服務(wù),一個(gè)是本地提供的取款服務(wù),一個(gè)是目標(biāo)銀行提供的存款服務(wù),該兩個(gè)服務(wù)本身無(wú)狀態(tài)且獨(dú)立,構(gòu)成一個(gè)完整的事務(wù)。對(duì)于事務(wù)的處理初步分析: 事務(wù)補(bǔ)償機(jī)制 事務(wù)補(bǔ)償即在事務(wù)鏈中的任何一個(gè)正向事務(wù)操作,都必須存在一個(gè)完全符合回滾規(guī)則的可逆事務(wù)。如果是一個(gè)完整的事務(wù)鏈,則必須事務(wù)鏈中的每一個(gè)業(yè)務(wù)服務(wù)或操作都有對(duì)應(yīng)的可逆服務(wù)。對(duì)于Service服務(wù)本身無(wú)狀態(tài),也不容易實(shí)現(xiàn)前面討論過的通過DTC或XA機(jī)制實(shí)現(xiàn)的跨應(yīng)用和資源的事務(wù)管理,建立跨資源的事務(wù)上下文。因此也較難以實(shí)現(xiàn)真正的預(yù)提交和正式提交的分離。
在這種情況下以上面例子來說,首先調(diào)用取款服務(wù),完全調(diào)用成功并返回,數(shù)據(jù)已經(jīng)持久化。然后調(diào)用異地的存款服務(wù),如果也調(diào)用成功,則本身無(wú)任何問題。如果調(diào)用失敗,則需要調(diào)用本地注冊(cè)的逆向服務(wù)(本地存款服務(wù)),如果本地存款服務(wù)調(diào)用失敗,則必須考慮重試,如果約定重試次數(shù)仍然不成功,則必須log到完整的不一致信息。也可以是將本地存款服務(wù)作為消息發(fā)送到消息中間件,由消息中間件接管后續(xù)操作。 在上面方式中可以看到需要手工編寫大量的代碼來處理以保證事務(wù)的完整性,我們可以考慮實(shí)現(xiàn)一個(gè)通用的事務(wù)管理器,實(shí)現(xiàn)事務(wù)鏈和事務(wù)上下文的管理。對(duì)于事務(wù)鏈上的任何一個(gè)服務(wù)正向和逆向操作均在事務(wù)管理和協(xié)同器上注冊(cè),由事務(wù)管理器接管所有的事務(wù)補(bǔ)償和回滾操作。
基于消息的最終一致性 在這里首先要回答的是我們需要時(shí)實(shí)時(shí)一致性還是最終一致性的問題,如果需要的是最終一致性,那么BASE策略中的基于消息的最終一致性是比較好的解決方案。這種方案真正實(shí)現(xiàn)了兩個(gè)服務(wù)的真正解耦,解耦的關(guān)鍵就是異步消息和消息持久化機(jī)制。 還是以上面的例子來看。對(duì)于轉(zhuǎn)賬操作,原有的兩個(gè)服務(wù)調(diào)用變化為第一步調(diào)用本地的取款服務(wù),第二步發(fā)送異地取款的異步消息到消息中間件。如果第二步在本地,則保證事務(wù)的完整性基本無(wú)任何問題,即本身就是本地事務(wù)的管理機(jī)制。只要兩個(gè)操作都成功即可以返回客戶成功。
由于解耦,我們看到客戶得到成功返回的時(shí)候,如果是上面一種情況則異地卡馬上就能查詢賬戶存款增加。而第二種情況則不一定,因?yàn)楸旧硎且环N異步處理機(jī)制。消息中間件得到消息后會(huì)去對(duì)消息解析,然后調(diào)用異地銀行提供的存款服務(wù)進(jìn)行存款,如果服務(wù)調(diào)用失敗則進(jìn)行重試。
異地銀行存款操作不應(yīng)該長(zhǎng)久地出現(xiàn)異常而無(wú)法使用,因此一旦發(fā)現(xiàn)異常我們可以迅速的解決,消息中間件中異常服務(wù)自然會(huì)進(jìn)行重試以保證事務(wù)的最終一致性。這種方式假設(shè)問題一定可以解決,在不到萬(wàn)不得已的情況下本地的取款服務(wù)一般不進(jìn)行可逆操作。 在本地取款到異地存款兩個(gè)服務(wù)調(diào)用之間,會(huì)存在一個(gè)真空期,這段時(shí)間相關(guān)現(xiàn)金不在任何一個(gè)賬戶,而只是在一個(gè)事務(wù)的中間狀態(tài),但是客戶并不關(guān)心這個(gè),只要在約定的時(shí)間保證事務(wù)最終的一致性即可。
關(guān)于冪等操作的問題 重復(fù)調(diào)用多次產(chǎn)生的業(yè)務(wù)結(jié)果與調(diào)用一次產(chǎn)生的業(yè)務(wù)結(jié)果相同,簡(jiǎn)單點(diǎn)講所有提供的業(yè)務(wù)服務(wù),不管是正向還是逆向的業(yè)務(wù)服務(wù),都必須要支持重試。因?yàn)榉?wù)調(diào)用失敗這種異常必須考慮到,不能因?yàn)榉?wù)的多次調(diào)用而導(dǎo)致業(yè)務(wù)數(shù)據(jù)的累計(jì)增加或減少。 關(guān)于是否可以補(bǔ)償?shù)膯栴} 在這里我們談的是多個(gè)跨系統(tǒng)的業(yè)務(wù)服務(wù)組合成一個(gè)分布式事務(wù),因此在對(duì)事務(wù)進(jìn)行補(bǔ)償?shù)臅r(shí)候必須要考慮客戶需要的是否一定是最終一致性。客戶對(duì)中間階段出現(xiàn)的不一致的承受度是如何的。 3
在上面的例子來看,如果采用事務(wù)補(bǔ)償機(jī)制,基本可以是做到準(zhǔn)實(shí)時(shí)的補(bǔ)償,不會(huì)有太大的影響。而如果采用基于消息的最終一致性方式,則可能整個(gè)周期比較長(zhǎng),需要較長(zhǎng)的時(shí)間才能給得到最終的一致性。比如周六轉(zhuǎn)款,客戶可能下周一才得到通知轉(zhuǎn)賬不成功而進(jìn)行了回退,那么就必須要考慮客戶是否能給忍受。
其次對(duì)于前面討論,如果真正需要的是實(shí)時(shí)的一致性,那么即使采用事務(wù)補(bǔ)償機(jī)制,也無(wú)法達(dá)到實(shí)時(shí)的一致性。即很可能在兩個(gè)業(yè)務(wù)服務(wù)調(diào)用中間,客戶前臺(tái)業(yè)務(wù)操作對(duì)持久化的數(shù)據(jù)進(jìn)行了其它額外的操作。在這種模式下,我們不得不考慮需要在數(shù)據(jù)庫(kù)表增加業(yè)務(wù)狀態(tài)鎖的問題,即整個(gè)事務(wù)沒有完整提交并成功前,第一個(gè)業(yè)務(wù)服務(wù)調(diào)用雖然持久化在數(shù)據(jù)庫(kù),但是仍然是一個(gè)中間狀態(tài),需要通過業(yè)務(wù)鎖來標(biāo)記,控制相關(guān)的業(yè)務(wù)操作和行為。但是在這種模式下無(wú)疑增加了整個(gè)分布式業(yè)務(wù)系統(tǒng)的復(fù)雜度。