業(yè)務(wù)層面冪等
冗余部署多個(gè)進(jìn)程
存在并發(fā)消費(fèi)的可能性
并發(fā)轉(zhuǎn)變成串行消費(fèi)
發(fā)送端會(huì)發(fā)送很多次,消費(fèi)端可能會(huì)消費(fèi)很多次,需要做去重,對(duì)共享資源做去重,其實(shí)也是一個(gè)分布式鎖的問(wèn)題
本質(zhì)
分布式鎖問(wèn)題
分布式鎖設(shè)計(jì)與實(shí)踐
分布式鎖的定義
分布式環(huán)境下,鎖定全局唯一資源
請(qǐng)求處理串行化
實(shí)際表現(xiàn)互斥鎖
分布式鎖的目的
交易訂單鎖定
防止重復(fù)下單
解決業(yè)務(wù)層冪等問(wèn)題
MQ消息消費(fèi)冪等性
發(fā)送消息重復(fù)
消息消費(fèi)端去重
比如手機(jī)提現(xiàn)
在用戶對(duì)商品下單后,訂單狀態(tài)為待支付,在某一時(shí)刻用戶正在對(duì)該訂單做支付操作,商家對(duì)該訂單進(jìn)行改價(jià)操作
狀態(tài)的修改行為需要做串行處理,避免出現(xiàn)數(shù)據(jù)錯(cuò)亂
基于Redis 分布式鎖
基于Redis 分布式鎖方案
1、唯一線程串行處理
2、實(shí)現(xiàn)方式
2.1 Redis Setnx(SET if Not eXists) 命令在指定的key不存在時(shí),為key 設(shè)置指定的值
SETNX KEY_NAME VALUE Expire_Time
設(shè)置成功,返回1 設(shè)置失敗 返回0
如果Redis 是單機(jī)的話很危險(xiǎn),比如T1 在redis上加了一把鎖,這時(shí)候Redis掛了,然后重啟,這時(shí)候T2也對(duì)相同資源加了一把鎖,此時(shí)就有兩把鎖, 當(dāng)然你對(duì)鎖做持久化,但是從架構(gòu)層面上來(lái)講是沒(méi)有必要的,因?yàn)槭聞?wù)的生命周期比較短。
2.2 存在問(wèn)題
鎖時(shí)間不可控
無(wú)法續(xù)租期
redis鎖依賴于失效時(shí)間 expire,比如失效時(shí)間為5秒鐘,但是5秒鐘我的事務(wù)還沒(méi)執(zhí)行完,鎖失效了,另外一個(gè)線程又拿到了這把鎖,又得重復(fù)去做這個(gè)事情。
單點(diǎn)問(wèn)題
單實(shí)例存在進(jìn)程一旦死掉,會(huì)徹底阻塞業(yè)務(wù)流程
主從方式,主從數(shù)據(jù)異步,會(huì)存在鎖失效問(wèn)題
2.2.1、第一種情況單機(jī)如果掛掉的話,如果你的服務(wù)強(qiáng)依賴redis,redis 還沒(méi)起來(lái)的話,會(huì)阻塞業(yè)務(wù)流程。
2.2.2、第二情況 T1寫(xiě)到主redis,如果redis 主掛了,還沒(méi)有同步到從redis ,根據(jù)哨兵原理,從redis會(huì)切換為主,T2會(huì)從新的主去拿,此時(shí)T2也會(huì)重新生成一把鎖,這種情況下,T1、T2都會(huì)拿到這把鎖。這樣就會(huì)出現(xiàn)重復(fù)鎖的問(wèn)題
那么基于Redis分布式鎖到底能不能使用?取決于你的業(yè)務(wù)場(chǎng)景決定的。
如果你的業(yè)務(wù)是交易場(chǎng)景,嚴(yán)格要求他的一致性,極端情況下,鎖重復(fù)問(wèn)題一定是不能忍的。
如果是社交發(fā)消息場(chǎng)景,極端情況下,如果鎖重復(fù)的話,發(fā)消息重新消費(fèi)一次,也是能接受的。
redis 作為鎖
高可用無(wú)法保證
問(wèn)題本質(zhì),分布式鎖是CP模型,Redis集群是AP模型,Redis是半同步的。 所以要通過(guò)CP模型解決 Redis 自己實(shí)現(xiàn)了一套R(shí)AFT協(xié)議:redlock
3、官方建議
Redis 本身建議使用Redlock 算法來(lái)保證,但是問(wèn)題是需要至少三個(gè)Redis主從實(shí)例來(lái)完成,維護(hù)成本相對(duì)比較高,Redlock等同于自己實(shí)現(xiàn)簡(jiǎn)單的一致性協(xié)議,細(xì)節(jié)繁瑣,且容易出錯(cuò)。
高可用分布式鎖設(shè)計(jì)目標(biāo)
設(shè)計(jì)目標(biāo)
1、強(qiáng)一致性
其實(shí)是任何鎖都要做的事情
2、服務(wù)高可用、系統(tǒng)穩(wěn)健
要保證服務(wù)的高可用,不能因?yàn)橐慌_(tái)機(jī)器宕機(jī)了就不可用了
3、鎖自動(dòng)續(xù)約及其自動(dòng)釋放
要有能力,在業(yè)務(wù)無(wú)感知的情況下,做自動(dòng)的續(xù)約,比如Redis 鎖過(guò)期時(shí)間是5秒鐘,當(dāng)我的事務(wù)5秒鐘還沒(méi)有結(jié)束的時(shí)候,自動(dòng)再續(xù)約5秒鐘。
4、代碼高度抽象業(yè)務(wù)接入極簡(jiǎn)
當(dāng)然也希望我的代碼比較抽象,接入也比較簡(jiǎn)單,
5、可視化管理后臺(tái),監(jiān)控及管理
高可用分布式鎖設(shè)計(jì)方案對(duì)比
存儲(chǔ)層產(chǎn)品對(duì)比
| redis | zookeeper | etcd | |
|---|---|---|---|
| 一致性算法 | 無(wú) | paxos(zab) | raft |
| CAP | AP | CP | CP/AP |
| 高可用 | 主從 | N+1可用 | N+1可用 |
| 接口類(lèi)型 | 客戶端 | 客戶端 | http/grpc |
| 實(shí)現(xiàn) | setNX | createEphemeral | restful API |
zookeeper 為什么是n+1 是因?yàn)槭瞧鏀?shù)個(gè),etcd 客戶端比較糟糕
1、由于Redis 無(wú)法保證數(shù)據(jù)一致性
2、Zookeeper對(duì)鎖實(shí)現(xiàn)使用創(chuàng)建臨時(shí)節(jié)點(diǎn)和watch機(jī)制,執(zhí)行效率、擴(kuò)展能力、社區(qū)活躍度等方面低于etcd
3、選擇基于etcd 實(shí)現(xiàn) tidb也是基于 etcd來(lái)實(shí)現(xiàn)的
分布式鎖存儲(chǔ)選型
etcd
簡(jiǎn)單KV
強(qiáng)一致
高可用--無(wú)單點(diǎn)
數(shù)據(jù)高可靠--持久化
分布式鎖整體方案
分布式Client+etcd
clientTTL模式
Client TTL模式
1、ClientA->etcd->("key","ttl","value","uuid")
2、ClientB->etcd->("key","ttl","value","uuid");
etcd 只需要填 key ttl ,value隨便填,對(duì)于鎖來(lái)說(shuō)value無(wú)所謂,uuid不需要填,當(dāng)你成功拿到鎖以后,etcd集群會(huì)給你生成一個(gè)uuid,這個(gè)uuid其實(shí)就是你的鎖的唯一的憑證,接下來(lái)所以對(duì)鎖的操作,都是基于這個(gè)uuid來(lái)實(shí)現(xiàn)的。
3、ClientA 拿鎖成功,ClientB 拿鎖失敗
4、A服務(wù)需要對(duì)etcd 保持后臺(tái)心跳線程,比如key的租期是10ms,后臺(tái)心跳線程為3ms,心跳線程負(fù)責(zé)在拿到key之后,每3ms cas 唯一憑證uuid;
比如key的租期是10ms,那么我們的心跳往往會(huì)設(shè)置租期的1/3的時(shí)間,也就是3ms,每次心跳的就是就會(huì)續(xù)約租期,當(dāng)心跳到的時(shí)候,租期還有10-3=7ms,這時(shí)候我會(huì)刷新ttl改成10ms,相當(dāng)于又增加了3ms。
什么時(shí)候釋放呢,這個(gè)時(shí)候需要業(yè)務(wù)方主動(dòng)去釋放這個(gè)鎖
使用場(chǎng)景一:申請(qǐng)鎖
1、業(yè)務(wù)放申請(qǐng)資源鎖,調(diào)用時(shí)提供key,ttl
2、etcd生成uuid,作為當(dāng)前鎖的唯一憑證,將(key,uuid,ttl)寫(xiě)etcd
3、檢查etcd中此key 是否存在,如沒(méi)有,嘗試寫(xiě)入key,寫(xiě)入失敗,拿鎖失敗,寫(xiě)入成功拿到鎖。
4、拿到鎖后,客戶端異步心跳線程啟動(dòng),心跳線程維持時(shí)間為ttl/3,compare and swap uuid ,從而將key 值續(xù)租
5、相關(guān)etcd API
a、申請(qǐng)鎖
curl http://127.0.0.12379/v2/keys/foo -XPUT -d value=bar -d ttl=5 prevExist=false;
b、CAS更新鎖租約
curl http://127.0.0.12379/v2/keys/foo?prevValue=prev_uuid -XPUT -d ttl=5 refresh=true prevExist=true;
c、CAS刪除鎖
curl http://127.0.0.12379/v2/keys/foo?prevValue=prev_uuid -XDELETE;
使用場(chǎng)景二:申請(qǐng)鎖,但鎖已被持有
1、業(yè)務(wù)方申請(qǐng)資源鎖,調(diào)用時(shí)提供key.ttl
2、檢查etcd中key的存在,若已存在,拿鎖失敗
使用場(chǎng)景三:鎖的清理
1、如果調(diào)用方正常結(jié)束,通過(guò)cas 接口調(diào)用delete方法自動(dòng)清理etcd中的key值
2、如果調(diào)用方異常終止,等待原有鎖ttl 過(guò)期后,鎖資源釋放。
如果沒(méi)有調(diào)用 delete方法,心跳超時(shí)以后,過(guò)時(shí)間過(guò)期后,鎖會(huì)自動(dòng)釋放。
業(yè)務(wù)接入
JDK 7 及以上
獲取鎖示例
try(zzlock=zzlock.getlock("resource_id",ttl)){
//jobs
zzlock.isTrue();
}
//釋放鎖示例
Optional<LockItem> lockItem=getlock("key");
if(lockItem.isPresent()){
System.out.println("獲得鎖,如果進(jìn)程被終止,鎖會(huì)在10s后失效!");
System.out.println("如果進(jìn)程繼續(xù),后臺(tái)心跳線程更新3s鎖時(shí)間");
releaseLock(lockItem.get());
}else{
System.out.println("獲取鎖的失?。。?!");
}
獲取鎖平均耗時(shí)監(jiān)控
1、下面是獲取鎖的平均耗時(shí)情況
a、獲取鎖的平均耗時(shí)大概是在2.1ms左右
b、由于etcd的強(qiáng)一致性,根據(jù)raft算法,消耗時(shí)間稍微長(zhǎng)一點(diǎn)
etcd兼容性測(cè)試
etcd提供獨(dú)有的集群管理模式,方便進(jìn)行極端case下的測(cè)試,以三個(gè)節(jié)點(diǎn)的etcd集群為例
a、單節(jié)點(diǎn)停機(jī),不影響持續(xù)寫(xiě)入,不影響讀,結(jié)果有一致性
b、當(dāng)只有一個(gè)節(jié)點(diǎn)時(shí),讀會(huì)停機(jī),寫(xiě)入正常
c、理論上只要不是多節(jié)點(diǎn)同時(shí)停機(jī),線上服務(wù)不會(huì)受影響
etcd 恢復(fù)/版本
1、etcd 有自有的數(shù)據(jù)恢復(fù)方式,如果服務(wù)停機(jī)后,可以將所有數(shù)據(jù)轉(zhuǎn)移重啟
2、etcd的增刪節(jié)點(diǎn),節(jié)點(diǎn)遷移等部署相關(guān),均有相關(guān)操作方式
3、etcd 版本選擇,選擇使用etcd 3.2.9 ,但是因?yàn)閂3 API 暫時(shí)還不夠完備,建議用V2 方式實(shí)現(xiàn)
a、V3提供gRPC接口
b、天然提供分布式鎖功能,只需申請(qǐng)鎖,釋放鎖,不用關(guān)注鎖的租期問(wèn)題。
分布式鎖特殊場(chǎng)景
1、特殊場(chǎng)景一:分布式鎖只是在同一自然時(shí)間的互斥鎖,本身不解決冪等性問(wèn)題;接入業(yè)務(wù)需要完善從獲得鎖到釋放鎖中間的數(shù)據(jù)冪等邏輯。
例如:T1 拿到lock 很快就處理完成了,T2 也拿到lock做同樣的操作,也是可以的。所以它不能解決冪等問(wèn)題,只是同一自然時(shí)間的互斥鎖。
T1 拿到訂單了進(jìn)行業(yè)務(wù)處理,T2也拿到了訂單也進(jìn)行業(yè)務(wù)處理,要做冪等只需要判斷訂單的狀態(tài),比如這個(gè)訂單已經(jīng)支付了,很顯然就不能重復(fù)支付。這個(gè)冪等只能業(yè)務(wù)方去處理,分布式鎖不會(huì)幫你去解決這個(gè)問(wèn)題。
2、特殊場(chǎng)景二:鎖沒(méi)有按照預(yù)期續(xù)租
a、心跳續(xù)租沒(méi)成功
b、馬上啟動(dòng)GC,GC時(shí)間夠長(zhǎng)
缺點(diǎn)1:etcd的租期為10秒鐘,這時(shí)候每次心跳3秒去續(xù)租,這時(shí)候網(wǎng)絡(luò)異常,有可能續(xù)租沒(méi)有成功。
缺點(diǎn)2:etcd 租期為10秒,這時(shí)候服務(wù)器執(zhí)行FullGC,這個(gè)FullGC 是11秒鐘,當(dāng)然GC 要11秒鐘,你的服務(wù)器可能要優(yōu)化,每3秒的時(shí)候就在執(zhí)行續(xù)租的時(shí)候,GC導(dǎo)致業(yè)務(wù)方的請(qǐng)求暫停了,當(dāng)你停止以后已經(jīng)11秒過(guò)后了,這時(shí)候鎖已經(jīng)被釋放掉了。
3、特殊場(chǎng)景三:etcd內(nèi)部協(xié)調(diào)發(fā)生問(wèn)題
a、Leader節(jié)點(diǎn)掛了,選主中
選主這個(gè)過(guò)程中,是停止響應(yīng)的,是沒(méi)辦法拿到鎖的
b、Raft日志數(shù)據(jù)同步發(fā)生錯(cuò)誤或者不一致問(wèn)題
當(dāng)Rfat 發(fā)生同步錯(cuò)誤,因?yàn)樗荂P模型,這時(shí)候不會(huì)讓你寫(xiě)或者讀。
consul 也是cp模型 consul 生態(tài)沒(méi)有etcd 龐大
為什么k8s 選中etcd 作為它的整個(gè)的注冊(cè)中心?
k8s 沒(méi)有好的選擇,k8s 當(dāng)你的集群特別大的時(shí)候,當(dāng)你的日志就是你的事件需要寫(xiě)的時(shí)候,etcd 就會(huì)成為你的瓶頸,這時(shí)候要怎么辦,要定期清理你的etcd事件的同步。
冪等需要兩個(gè)層次,一是不并發(fā);二是重復(fù)消費(fèi)結(jié)果一樣;分布式鎖的作用就是“串行”;
Redission里實(shí)現(xiàn)的分布式鎖初始過(guò)期時(shí)間也是30秒
一臺(tái)機(jī)器 耗時(shí) 2.1ms qps=500
當(dāng)業(yè)務(wù)線程假死,ttl線程還在跑,這個(gè)只能ttl超時(shí)了自動(dòng)釋放
線上鎖的最長(zhǎng)時(shí)間是10秒鐘
zk做分布式鎖,因?yàn)槭荂P模型,當(dāng)寫(xiě)入量很大的話會(huì)有問(wèn)題?其實(shí)對(duì)鎖還好,無(wú)非就是生成一把鎖會(huì)寫(xiě)一次,心跳更新租約再寫(xiě)一次,delete鎖再寫(xiě)一次;
如果業(yè)務(wù)阻塞了,假死了無(wú)法釋放鎖,此時(shí)心跳還在,心跳還在就會(huì)自動(dòng)續(xù)租。
這時(shí)候網(wǎng)關(guān)發(fā)現(xiàn)服務(wù)器假死,觸發(fā)熔斷,會(huì)發(fā)送通知給控制中心,控制中心重啟服務(wù),這時(shí)候心跳檢測(cè)程序就沒(méi)有了;
或者提供一個(gè)鎖的最長(zhǎng)超時(shí)時(shí)間,定義最大續(xù)約次數(shù)。
舉個(gè)例子:有兩個(gè)進(jìn)程拿到相同的orderId,進(jìn)程1拿到了鎖,先處理了訂單,完事之后更新?tīng)顟B(tài),進(jìn)程2拿不到鎖,或通過(guò)檢測(cè)訂單的狀態(tài),來(lái)避免重復(fù)執(zhí)行相同的操作。
數(shù)據(jù)一致性定義
任何人
任何時(shí)間
任何地點(diǎn)
任何接入方式
任何服務(wù)
數(shù)據(jù)都是一致
數(shù)據(jù)不一致性產(chǎn)生原因
1、數(shù)據(jù)分散在多處
a、多個(gè)DB;b、DB和緩存
2、電商交易平臺(tái)案例
用戶、商品、交易等功能
分布式事務(wù)場(chǎng)景
1、電商下單場(chǎng)景
a、下單
b、發(fā)送消息到MQ
2、一致性保證
a、本地事務(wù)
下單操作
發(fā)送MQ消息操作
放進(jìn)一個(gè)本地事務(wù)
上述做法有什么問(wèn)題?
java 實(shí)例代碼
try{
stmt=DriverManager.getConnection("jdbc:mysqlxxxx");
stmt=conn.createStatement();
//數(shù)據(jù)庫(kù)生成訂單操作
stmt.executeUpdate("insert order values(orderId,timestamp,price,state)");
//生成發(fā)送的消息內(nèi)容
MsgObject MsgContent(orderID);
//發(fā)送消息操作
MQClient.sendMsg(MsgContent);
//事務(wù)提交
conn.commit();
}catch(Exception e){
e.printStackTrace();
try{
//操作不成功則返回退
conn.rollback();
}catch(Exception ex){
ex.printStackTrace();
}
}
分布式事務(wù)分類(lèi)
1、剛性分布式事務(wù)
a、強(qiáng)一致性
b、XA 模型
XA模型是完全遵循ACID的高可用事務(wù)
c、CAP:CP
2、柔性分布式事務(wù)
a、最終一致性
b、CAP、BASE理論 AP
剛性分布式事務(wù)
1、滿足傳統(tǒng)事務(wù)特性
ACID(Atomicity-原子性、Consistency-一致性、Isolation-隔離性、Durability-持久性)
2、XA模型
a、XA是X/Open CAE Specification(Distributed Transaction Processing )模型定義,XA規(guī)范由AP、RM 、TM組成。
b、其中應(yīng)用程序(Application Program,簡(jiǎn)稱(chēng)AP):AP定義事務(wù)邊界(定義事務(wù)開(kāi)始和結(jié)束)并訪問(wèn)事務(wù)邊界內(nèi)的資源。
c、資源管理器(Resource Manager,簡(jiǎn)稱(chēng)RM):RM 管理計(jì)算機(jī)共享的資源,資源即數(shù)據(jù)庫(kù)等。
d、事務(wù)管理器(Transaction Manager ,簡(jiǎn)稱(chēng)TM):負(fù)責(zé)管理全局事務(wù),分配事務(wù)唯一標(biāo)識(shí),監(jiān)控事務(wù)的執(zhí)行進(jìn)度,并負(fù)責(zé)事務(wù)的提交、會(huì)滾、失敗恢復(fù)等。

剛性分布式事務(wù)
1、案例:組織爬山
2、過(guò)程:
a、二階段提交,是XA規(guī)范標(biāo)準(zhǔn)實(shí)現(xiàn)
b、TM 發(fā)起prepare投票
c、RM 都同意后,TM再發(fā)起commit
d、Commit過(guò)程出現(xiàn)宕機(jī)等異常,節(jié)點(diǎn)服務(wù)重啟后,根據(jù)XA recover再次進(jìn)行commit補(bǔ)償
3、缺點(diǎn)
a、同步阻塞模型
b、數(shù)據(jù)庫(kù)資源鎖定時(shí)間過(guò)長(zhǎng)
它的鎖庫(kù)時(shí)間很長(zhǎng)
現(xiàn)在是兩個(gè)庫(kù),所以同時(shí)要鎖定兩個(gè)庫(kù);
如果是100個(gè)庫(kù),那么就要同時(shí)鎖定100個(gè)庫(kù);
c、全局鎖(隔離級(jí)別串行化),并發(fā)低。
基于XA的分布式事務(wù)如果要嚴(yán)格保證ACID,實(shí)際需要事務(wù)隔離級(jí)別為SERLALIZABLE。這時(shí)候會(huì)鎖表,這個(gè)隔離級(jí)別是非常粗的,所以并發(fā)也低。
d、不適合長(zhǎng)事務(wù)場(chǎng)景
長(zhǎng)事務(wù)就是一個(gè)事務(wù)里面操作很多步驟,比如大于3;所以2PC 比較適合短事務(wù)。

柔性分布式事務(wù)
1、CAP
分布式環(huán)境下P一定需要,CA權(quán)衡折中
2、BASE理論
a、Basically Available 基本可用
b、Soft state-柔性狀態(tài)
c、Eventual consistency 最終一致性
3、架構(gòu)思考
柔性事務(wù)是對(duì)XA協(xié)議的妥協(xié),它通過(guò)降低強(qiáng)一致性要求,從而降低數(shù)據(jù)庫(kù)資源鎖定時(shí)間,提升可用性
4、典型架構(gòu)實(shí)現(xiàn)
a、TCC模型
b、Saga模型
TCC模型
1、Try-Confirm-Cancel
2、TCC模型完全交由業(yè)務(wù)實(shí)現(xiàn),每個(gè)子業(yè)務(wù)都需要實(shí)現(xiàn)Try-Confirm-Cancel接口,對(duì)業(yè)務(wù)侵入大
假如:用戶購(gòu)買(mǎi)流程:下單->減庫(kù)存->支付,那么你每一個(gè)步驟都要實(shí)現(xiàn)一遍T(mén)ry-Confirm-Cancel
對(duì)業(yè)務(wù)侵入很大,在業(yè)務(wù)層實(shí)現(xiàn)
a、資源鎖定交由業(yè)務(wù)方
3、Try
嘗試執(zhí)行業(yè)務(wù),完成所有業(yè)務(wù)檢查,預(yù)留必要的業(yè)務(wù)資源
每次提交之前,都要去判斷是否具備提交的必要條件
4、Confirm
真正執(zhí)行業(yè)務(wù),不再做業(yè)務(wù)檢查
5、Cancel
釋放Try階段預(yù)留的業(yè)務(wù)資源
6、匯款服務(wù)、收款服務(wù)案例
A用戶向B用戶匯款500元
匯款服務(wù):
a.Try
1、檢查A賬戶有效性,即查看A賬戶的狀態(tài)是否為“轉(zhuǎn)賬中”或者“凍結(jié)”;
2、檢查A賬戶余額是否充足;
3、從A賬戶中扣減500元,并將狀態(tài)置為“轉(zhuǎn)賬中”;
4、預(yù)留扣減資源,將從A往B賬戶轉(zhuǎn)賬500元這個(gè)事件存入消息或者日志中
b.Confirm:
1、不做任何操作
c.Cancel:
1、A賬戶增加500元;
2、從日志或者消息中,釋放扣減資源;
收款服務(wù)
a.Try:
1.檢查B賬戶賬戶是否有效
b.Confirm:
1.讀取日志或者消息,B賬戶增加500元;
2.從日志或者消息中,釋放扣減資源;
c.Cancel:
1.不做任何操作;
柔性分布式事務(wù)--Saga模型
1、起源于1987年Hector & Kenneth 發(fā)表的論文Sagas
2、Saga 模型把一個(gè)分布式事務(wù)拆分為多個(gè)本地事務(wù),每個(gè)本地事務(wù)都有相應(yīng)的執(zhí)行模塊和補(bǔ)償模塊(對(duì)應(yīng)TCC的Confirm和Cancel)
2、當(dāng)Saga 事務(wù)中任意一個(gè)本地事務(wù)出錯(cuò)時(shí),可以通過(guò)調(diào)用相關(guān)的補(bǔ)償方法恢復(fù)之前的事務(wù),達(dá)到事務(wù)最終一致性。
3、當(dāng)每個(gè)Saga 子事務(wù)T1、T2、...Tn 都有對(duì)應(yīng)的補(bǔ)償定義C1、C2,....Cn-1,那么Saga系統(tǒng)可以保證
a.子事務(wù)序列T1,T2,......,Tn得完成(最佳情況)
b.或者序列T1,T2,....Tj,Cj-1,.....C2,C1, 0<j<n得以完成
逆序串行執(zhí)行,保證有序
Saga隔離性
1、業(yè)務(wù)層控制并發(fā)
a.在應(yīng)用層加鎖
b.應(yīng)用層預(yù)先凍結(jié)資源等
Saga恢復(fù)方式
1、向后恢復(fù):補(bǔ)償所有已經(jīng)完成的事務(wù),如果任一子事務(wù)失?。?br>
2、向前恢復(fù):重試失敗的事務(wù),假設(shè)每個(gè)子事務(wù)最終都會(huì)成功;
剛性分布式事務(wù)VS柔性分布式事務(wù)
| 剛性事務(wù)(XA) | 柔性事務(wù) | |
|---|---|---|
| 業(yè)務(wù)改造 | 無(wú) | 有(需要改造) |
| 回滾 | 支持 | 實(shí)現(xiàn)補(bǔ)償接口 |
| 一致性 | 強(qiáng)一致 | 最終一致 |
| 隔離性 | 原生支持 | 實(shí)現(xiàn)資源鎖定接口 |
| 并發(fā)性能 | 嚴(yán)重衰退 | 略微衰退 |
| 適合場(chǎng)景 | 短事務(wù),并發(fā)較低 | 長(zhǎng)事務(wù),高并發(fā) |
解決思路
問(wèn)題通用解決思路
1、解決這個(gè)問(wèn)題本身
2、讓問(wèn)題本身消失,圓珠筆筆芯漏油解決
方案一:從業(yè)務(wù)場(chǎng)景消除分布式事務(wù)
1、思路:核心業(yè)務(wù)先處理,其他業(yè)務(wù)異步處理
方案二:柔性分布式事務(wù)
柔性分布式事務(wù)
通用處理思路
1、本地事務(wù)->短事務(wù)
2、分布式事務(wù)->長(zhǎng)事務(wù)
3、轉(zhuǎn)變成多個(gè)短事務(wù)
4、案例
A[下單]->B[減庫(kù)存]->C[支付]
A->DB1
B->DB2
C->DB3
A/B/C都成功
A/B 成功,C失敗 補(bǔ)償
業(yè)務(wù)場(chǎng)景
1、異步場(chǎng)景
基于MQ驅(qū)動(dòng)分布事務(wù)
2、同步場(chǎng)景
基于異步補(bǔ)償分布
異步場(chǎng)景分布式事務(wù)設(shè)計(jì)
異步場(chǎng)景
商品交易
下單、支付

方案一:業(yè)務(wù)方提供本地操作成功回查功能
1、事務(wù)消息:MQ提供類(lèi)似X/Open XA 的分布式事務(wù)功能,通過(guò)MQ事務(wù)消息能達(dá)到分布式事務(wù)的最終一致
2、半消息:暫不能投遞的消息,發(fā)送方已經(jīng)將消息成功發(fā)送到了MQ服務(wù)端,但是服務(wù)端未收到生產(chǎn)者對(duì)該消息的二次確認(rèn),此時(shí)該消息被標(biāo)記成“暫不投遞”狀態(tài),處于該種狀態(tài)下的消息即半消息
3、消息回查:由于網(wǎng)絡(luò)閃斷、生產(chǎn)者應(yīng)用重啟等原因,導(dǎo)致某條事務(wù)消息二次確認(rèn)丟失,MQ服務(wù)端通過(guò)掃描發(fā)現(xiàn)某條消息長(zhǎng)期處于“半消息”時(shí),需要主動(dòng)向消息生產(chǎn)者詢問(wèn)該消息的最終狀態(tài)(Coommit或者是Rollback),即消息回查
MQ分布式事務(wù)設(shè)計(jì)方案圖:

異步場(chǎng)景分布式事務(wù)設(shè)計(jì)
方案一:業(yè)務(wù)方提供本地操作成功回查功能
MQ分布式事務(wù)消息設(shè)計(jì)
a、MQ事務(wù)消息設(shè)計(jì)事務(wù)消息作為一種異步確保型事務(wù),將兩個(gè)事務(wù)分支通過(guò)MQ進(jìn)行異步解耦,MQ事務(wù)消息的設(shè)計(jì)流程同樣借鑒了兩階段提交理論,整體交互流程如下圖所示:

1、事務(wù)發(fā)起方首先發(fā)送prepare消息到MQ;
2、在發(fā)送prepare消息成功后執(zhí)行本地事務(wù);
3、根據(jù)本地事務(wù)執(zhí)行結(jié)果返回commit或者是rollback;
4、如果消息是rollback,MQ將刪除該prepare消息不進(jìn)行下發(fā),如果是commit消息,MQ將會(huì)消息發(fā)送給consumer端;
5、如果執(zhí)行本地事務(wù)過(guò)程中,執(zhí)行端掛掉,或者超時(shí),MQ服務(wù)器端將不停的詢問(wèn)producer來(lái)獲取事務(wù)狀態(tài);
6、Consumer端的消費(fèi)成功機(jī)制有MQ保證;
該方案的優(yōu)缺點(diǎn):
優(yōu)點(diǎn):通用
缺點(diǎn):
1、業(yè)務(wù)方需提供回查接口,對(duì)業(yè)務(wù)侵入大;
2、發(fā)送消息非冪等;
3、消費(fèi)端需要處理冪等;
消息不冪等的意思就是:
1、發(fā)送Half消息到MQ Server 成功;
2、第二步的時(shí)候Half 消息 ack MQ 發(fā)送方失?。?此時(shí)生產(chǎn)者會(huì)重復(fù)第一步,再次發(fā)送Half消息到MQ Server;
3、發(fā)送多次就會(huì)處理多次,所以存在消息不冪等的問(wèn)題。
方案二
本地操作和發(fā)送消息通過(guò)本地事務(wù)強(qiáng)一致性
1、本地事務(wù)操作表
2、本地事務(wù)消息表
mqMessages(msgid,content,topic,status);
操作表和消息表放在同一個(gè)庫(kù),所以是Local Transaction 事務(wù)

比如:下訂單流程,我們將下訂單和下訂單產(chǎn)生的消息放到同一個(gè)本地事務(wù)里面執(zhí)行。
方案二優(yōu)缺點(diǎn):
1、發(fā)送端消息不冪等
At least once (至少一次,肯定都是這個(gè),下面兩個(gè)不要用)
Only once(只有一次)
At more once (最多一次)
2、消費(fèi)端處理消息冪等
a、分布式鎖
3、A->B->C
3.1、A/B成功,C失??;
比如: A->DB1 并寫(xiě)MQ;然后 MQ->B->DB2; MQ->C->DB3;
1、如果很幸運(yùn),B成功了,C也成功了,事務(wù)就成功了。
2、如果B成功了,C失敗了,這時(shí)候C發(fā)一條會(huì)滾消息MQ;
3、然后這個(gè)補(bǔ)償消息MQ要被A和B消費(fèi),這時(shí)候又回到了1的步驟。這樣就會(huì)出現(xiàn)死循環(huán)。如下圖所示

a、記錄錯(cuò)誤日志
b、報(bào)警
c、人工介入
4、優(yōu)點(diǎn):
業(yè)務(wù)侵入小;
其實(shí)現(xiàn)在大部分走的是這種模式。
實(shí)際異步場(chǎng)景使用較多的是方案二:本地事務(wù)消息表
基于半消息實(shí)現(xiàn)的“方案一”使用的比較少,現(xiàn)在開(kāi)源的RockectMQ就是半消息的實(shí)現(xiàn)方案。
會(huì)滾MQ需要串行嗎?
如果MQ的處理結(jié)果要告訴別人就要串行,否則無(wú)需串行,可以同時(shí)處理多個(gè)MQ會(huì)滾。
轉(zhuǎn)錢(qián)有凍結(jié)狀態(tài),所以不一定最后一步做轉(zhuǎn)錢(qián)動(dòng)作,轉(zhuǎn)錢(qián)凍結(jié)狀態(tài)是無(wú)法提現(xiàn)的。
如果用戶將平臺(tái)的庫(kù)存占住而不支付,怎么處理?
不支付是有時(shí)間限制的,比如30分鐘,或者2個(gè)小時(shí),這時(shí)候可以發(fā)送一個(gè)延遲消息,如果不支付就會(huì)被清空掉。
或者定時(shí)作業(yè),定時(shí)掃描未支付的訂單。
對(duì)于分布式事務(wù),要盡可能得減少網(wǎng)絡(luò)交互,網(wǎng)絡(luò)交互越多,發(fā)生事務(wù)處理失敗或者事務(wù)會(huì)滾的概率越大。