分布式事務(wù),缺陷方案咋那么多?
說(shuō)到分布式事務(wù),可能很多時(shí)候想到的是支付轉(zhuǎn)賬、跨DB數(shù)據(jù)更新等典型的場(chǎng)景,但實(shí)際上,分布式事務(wù)是一個(gè)比高并發(fā)、高可用都普遍的多、幾乎無(wú)處不在的一個(gè)問(wèn)題。但因?yàn)橹匾暢潭炔粔?,?shí)際方案往往存在各種缺陷。
無(wú)論大公司,還是小公司,都不太可能把所有數(shù)據(jù)都存在一個(gè)DB里面,用DB的單機(jī)事務(wù)完成。實(shí)際上,都不止一個(gè)系統(tǒng),系統(tǒng)之間需要接口互相調(diào)用。
今天就舉個(gè)最簡(jiǎn)單的分布式事務(wù)例子:
你的系統(tǒng),接收到用戶請(qǐng)求,需要更新自己的DB + 調(diào)用隔壁團(tuán)隊(duì)的一個(gè)http接口(做數(shù)據(jù)更新操作),并且2個(gè)操作必須保證數(shù)據(jù)一致性。
(1) 是先調(diào)用別人接口,后更新DB,還是反過(guò)來(lái)?
(2) 2個(gè)操作中,1個(gè)成功,1個(gè)失敗,給用戶是返回成功,還是失敗?
先說(shuō)一下常用的缺陷方案:
缺陷方案1:把接口調(diào)用包在DB事務(wù)里面
調(diào)用接口成功,DB事務(wù)提交;調(diào)用接口失敗,DB事務(wù)回滾。看起來(lái)很正確,存在問(wèn)題:
(1)接口超時(shí),那DB事務(wù)是應(yīng)該提交,還是回滾?答案:2個(gè)都不對(duì),不確定。
(2)調(diào)用接口block,導(dǎo)致DB事務(wù)一直卡在那,鎖沒(méi)有辦法及時(shí)釋放。在并發(fā)量大的情況下,DB會(huì)被拖死。
缺陷方案2:接口調(diào)用失敗,DB數(shù)據(jù)做逆向操作。
先提交DB事務(wù),然后調(diào)用外部接口。如果接口調(diào)用失敗,DB數(shù)據(jù)再做邏輯上回滾。存在問(wèn)題:
(1)同樣,接口如果不是失敗,是“超時(shí)”,也不確定DB是應(yīng)該回滾,還是提交。
(2)DB邏輯回滾本身,是個(gè)網(wǎng)絡(luò)操作。也可能失敗/超時(shí),此時(shí)如何處理?
缺陷方案3:接口調(diào)用失敗,插入異常表,再事后回補(bǔ)。
先提交DB事務(wù),然后調(diào)用外部接口。如果接口調(diào)用失敗,往異常表插入1條記錄,然后事后根據(jù)異常表里面的記錄,去重試外部接口。存在問(wèn)題:
同上面一樣,插入異常表,是個(gè)網(wǎng)絡(luò)操作,也沒(méi)辦法保證這個(gè)一定成功。
缺陷方案4:接口調(diào)用失敗,往Kafka里面插入一條異常消息,消費(fèi)消息做事后回補(bǔ)。
同方案3一樣,插入異常消息也是個(gè)網(wǎng)絡(luò)操作,有可能超時(shí)/失敗。
正確的策略1:部分成功時(shí),給調(diào)用方返回失敗。讓調(diào)用方重試,被調(diào)方冪等
無(wú)論是先調(diào)用外部接口,后更新DB,還是反過(guò)來(lái),只要任何一個(gè)超時(shí),給用戶返回的不是“成功”,也不是“失敗”,而是“不確定”。
所謂“不確定”,就是類(lèi)似“目前網(wǎng)絡(luò)繁忙,請(qǐng)稍候再次確認(rèn)。有可能之前的更新成功了,也有可能更新失敗了“。
所以,無(wú)論是DB更新,還是接口調(diào)用,都要保證冪等。
正確的策略2:部分成功時(shí),給調(diào)用方返回成功。然后做最終一致性保證。
先更新DB,后調(diào)用外部接口。不管接口結(jié)果如何,只要DB成功了,就給用戶返回成功。
如果接口調(diào)用失敗/超時(shí),重試3次之后,仍然失敗/超時(shí)。那往異常表插入1條記錄,事后再去根據(jù)異常表做補(bǔ)償。
但異常表本身有可能插入失敗,所以需要一個(gè)”對(duì)賬任務(wù)“去做兜底,不斷掃描DB,對(duì)于DB中的每條記錄,查詢(xún)對(duì)應(yīng)的外部接口是否更新成功,如果沒(méi)有更新成功,不斷重試,直到成功。重試N次之后,還不行,告警人工處理。
”對(duì)賬任務(wù)“可以是增量的,根據(jù)DB中數(shù)據(jù)的update_time,定期對(duì)賬。
正確的策略3:DB成功了,就返回成功。然后異步去調(diào)外部接口,最終一致性。
DB成功了,就對(duì)用戶返回成功。然后一個(gè)后臺(tái)任務(wù),不斷去掃描DB,調(diào)對(duì)應(yīng)的外部接口。接口失敗/超時(shí),后臺(tái)任務(wù)可以無(wú)限次重試,直到最終2者一致。同樣,超過(guò)N次之后,還不一致,告警人工處理。
最后總結(jié):因?yàn)榫W(wǎng)絡(luò)調(diào)用”超時(shí)“是不可避免的,超時(shí)后結(jié)果是”不確定“的。不管怎么做,都沒(méi)有辦法在1次用戶請(qǐng)求中,保證2次網(wǎng)絡(luò)調(diào)用同時(shí)成功。所以,要么”調(diào)用方有無(wú)限次重試 + 告警人工修補(bǔ)機(jī)制”,要么“被調(diào)方有無(wú)限次重試 + 告警人工修補(bǔ)機(jī)制”,也就是“對(duì)賬兜底”。
