SpringCloud Alibaba之Seata分布式事務(wù)
點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號”
優(yōu)質(zhì)文章,第一時間送達(dá)
76套java從入門到精通實(shí)戰(zhàn)課程分享
分布式事務(wù)基礎(chǔ)
A:原子性(Atomicity),一個事務(wù)中的所有操作,要么全部完成,要么全部不完成
C:一致性(Consistency),在一個事務(wù)執(zhí)行之前和執(zhí)行之后數(shù)據(jù)庫都必須處于一致性狀態(tài)
I:隔離性(Isolation),在并發(fā)環(huán)境中,當(dāng)不同的事務(wù)同時操作相同的數(shù)據(jù)時,事務(wù)之間互不影響
D:持久性(Durability),指的是只要事務(wù)成功結(jié)束,它對數(shù)據(jù)庫所做的更新就必須永久地保存下來
分布式事務(wù)
分布式事務(wù)的場景
分布式事務(wù)解決方案
AP: Application 應(yīng)用系統(tǒng) (微服務(wù))
TM: Transaction Manager 事務(wù)管理器 (全局事務(wù)管理)
RM: Resource Manager 資源管理器 (數(shù)據(jù)庫)
階段一: 表決階段,所有參與者都將本事務(wù)執(zhí)行預(yù)提交,并將能否成功的信息反饋發(fā)給協(xié)調(diào)者。
階段二: 執(zhí)行階段,協(xié)調(diào)者根據(jù)所有參與者的反饋,通知所有參與者,步調(diào)一致地執(zhí)行提交或者回滾。
提高了數(shù)據(jù)一致性的概率,實(shí)現(xiàn)成本較低
單點(diǎn)問題: 事務(wù)協(xié)調(diào)者宕機(jī)
同步阻塞: 延遲了提交時間,加長了資源阻塞時間
數(shù)據(jù)不一致: 提交第二階段,依然存在commit結(jié)果未知的情況,有可能導(dǎo)致數(shù)據(jù)不一致
在系統(tǒng)A處理任務(wù)A前,首先向消息中間件發(fā)送一條消息
消息中間件收到后將該條消息持久化,但并不投遞。持久化成功后,向A回復(fù)一個確認(rèn)應(yīng)答
系統(tǒng)A收到確認(rèn)應(yīng)答后,則可以開始處理任務(wù)A
任務(wù)A處理完成后,向消息中間件發(fā)送Commit或者Rollback請求。該請求發(fā)送完成后,對系統(tǒng)A而言,該事務(wù)的處理過程就結(jié)束了
如果消息中間件收到Commit,則向B系統(tǒng)投遞消息;如果收到Rollback,則直接丟棄消息。但是如果消息中間件收不到Commit和Rollback指令,那么就要依靠"超時詢問機(jī)制"。
如果消息中間件收到確認(rèn)應(yīng)答后便認(rèn)為該事務(wù)處理完畢
如果消息中間件在等待確認(rèn)應(yīng)答超時之后就會重新投遞,直到下游消費(fèi)者返回消費(fèi)成功響應(yīng)為止。
處理業(yè)務(wù)的同一事務(wù)中,向本地消息表中寫入一條記錄
準(zhǔn)備專門的消息發(fā)送者不斷地發(fā)送本地消息表中的消息到消息中間件,如果發(fā)送失敗則重試
消息中間件收到消息后負(fù)責(zé)將該消息同步投遞給相應(yīng)的下游系統(tǒng),并觸發(fā)下游系統(tǒng)的任務(wù)執(zhí)行
當(dāng)下游系統(tǒng)處理成功后,向消息中間件反饋確認(rèn)應(yīng)答,消息中間件便可以將該條消息刪除,從而該事務(wù)完成
對于投遞失敗的消息,利用重試機(jī)制進(jìn)行重試,對于重試失敗的,寫入錯誤消息表
消息中間件需要提供失敗消息的查詢接口,下游系統(tǒng)會定期查詢失敗消息,并將其消費(fèi)
優(yōu)點(diǎn):?一種非常經(jīng)典的實(shí)現(xiàn),實(shí)現(xiàn)了最終一致性。
缺點(diǎn):?消息表會耦合到業(yè)務(wù)系統(tǒng)中,如果沒有封裝好的解決方案,會有很多雜活需要處理。
Try:?嘗試待執(zhí)行的業(yè)務(wù):這個過程并未執(zhí)行業(yè)務(wù),只是完成所有業(yè)務(wù)的一致性檢查,并預(yù)留好執(zhí)行所需的全部資源
Confifirm:?確認(rèn)執(zhí)行業(yè)務(wù):確認(rèn)執(zhí)行業(yè)務(wù)操作,不做任何業(yè)務(wù)檢查, 只使用Try階段預(yù)留的業(yè)務(wù)資源。通常情況下,采用TCC則認(rèn)為 Confifirm階段是不會出錯的。即:只要Try成功,Confifirm一定成功。若Confifirm階段真的出錯了,需引入重試機(jī)制或人工處理。
Cancel:?取消待執(zhí)行的業(yè)務(wù):取消Try階段預(yù)留的業(yè)務(wù)資源。通常情況下,采用TCC則認(rèn)為Cancel階段也是一定成功的。若Cancel階段真的出錯了,需引入重試機(jī)制或人工處理
XA是資源層面的分布式事務(wù),強(qiáng)一致性,在兩階段提交的整個過程中,一直會持有資源的鎖。
TCC是業(yè)務(wù)層面的分布式事務(wù),最終一致性,不會一直持有資源的鎖。
優(yōu)點(diǎn):把數(shù)據(jù)庫層的二階段提交上提到了應(yīng)用層來實(shí)現(xiàn),規(guī)避了數(shù)據(jù)庫層的2PC性能低下問題。
缺點(diǎn):TCC的Try、Confifirm和Cancel操作功能需業(yè)務(wù)提供,開發(fā)成本高。
Seata介紹
TC:Transaction Coordinator 事務(wù)協(xié)調(diào)器,管理全局的分支事務(wù)的狀態(tài),用于全局性事務(wù)的提交和回滾。
TM:Transaction Manager 事務(wù)管理器,用于開啟、提交或者回滾全局事務(wù)。
RM:Resource Manager 資源管理器,用于分支事務(wù)上的資源管理,向TC注冊分支事務(wù),上報分支事務(wù)的狀態(tài),接受TC的命令來提交或者回滾分支事務(wù)。
A服務(wù)的TM向TC申請開啟一個全局事務(wù),TC就會創(chuàng)建一個全局事務(wù)并返回一個唯一的XID
A服務(wù)的RM向TC注冊分支事務(wù),并及其納入XID對應(yīng)全局事務(wù)的管轄
A服務(wù)執(zhí)行分支事務(wù),向數(shù)據(jù)庫做操作
A服務(wù)開始遠(yuǎn)程調(diào)用B服務(wù),此時XID會在微服務(wù)的調(diào)用鏈上傳播
B服務(wù)的RM向TC注冊分支事務(wù),并將其納入XID對應(yīng)的全局事務(wù)的管轄
B服務(wù)執(zhí)行分支事務(wù),向數(shù)據(jù)庫做操作
全局事務(wù)調(diào)用鏈處理完畢,TM根據(jù)有無異常向TC發(fā)起全局事務(wù)的提交或者回滾
TC協(xié)調(diào)其管轄之下的所有分支事務(wù), 決定是否回滾
架構(gòu)層次方面,傳統(tǒng)2PC方案的 RM 實(shí)際上是在數(shù)據(jù)庫層,RM本質(zhì)上就是數(shù)據(jù)庫自身,通過XA協(xié)議實(shí)現(xiàn),而 Seata的RM是以jar包的形式作為中間件層部署在應(yīng)用程序這一側(cè)的。
兩階段提交方面,傳統(tǒng)2PC無論第二階段的決議是commit還是rollback,事務(wù)性資源的鎖都要保持到Phase2完成才釋放。而Seata的做法是在Phase1 就將本地事務(wù)提交,這樣就可以省去Phase2持鎖的時間,整體提高效率
Seata實(shí)現(xiàn)分布式事務(wù)控制
@RestController?
@Slf4j?
public?class?OrderController5?{
?@Autowired?
?private?OrderServiceImpl5?orderService;
?//下單?
?@RequestMapping("/order/prod/{pid}")?
?public?Order?order(@PathVariable("pid")?Integer?pid)?{
??log.info("接收到{}號商品的下單請求,接下來調(diào)用商品微服務(wù)查詢此商品信息",?pid);
??return?orderService.createOrder(pid);
?}
}
@Service?
@Slf4j?
public?class?OrderServiceImpl5{
?@Autowired?
?private?OrderDao?orderDao;
?@Autowired?
?private?ProductService?productService;
?@Autowired?
?private?RocketMQTemplate?rocketMQTemplate;
?@GlobalTransactional?
?public?Order?createOrder(Integer?pid)?{
??//1?調(diào)用商品微服務(wù),查詢商品信息?
??Product?product?=?productService.findByPid(pid);
??log.info("查詢到{}號商品的信息,內(nèi)容是:{}",?pid,?JSON.toJSONString(product));
??//2?下單(創(chuàng)建訂單)?
??Order?order?=?new?Order();
??order.setUid(1);
??order.setUsername("測試用戶");
??order.setPid(pid);
??order.setPname(product.getPname());
??order.setPprice(product.getPprice());
??order.setNumber(1);
??orderDao.save(order);
??log.info("創(chuàng)建訂單成功,訂單信息為{}",?JSON.toJSONString(order));
??//3?扣庫存?
??productService.reduceInventory(pid,?order.getNumber());
??//4?向mq中投遞一個下單成功的消息?
??rocketMQTemplate.convertAndSend("order-topic",?order);
??return?order;
?}
}
@FeignClient(value?=?"service-product")?
public?interface?ProductService?{
?//減庫存?
?@RequestMapping("/product/reduceInventory")?
??void?reduceInventory(@RequestParam("pid")?Integer?pid,?@RequestParam("num")?
??int?num);
}
//減少庫存?
@RequestMapping("/product/reduceInventory")?
public?void?reduceInventory(Integer?pid,?int?num)?{
?productService.reduceInventory(pid,?num);
}
@Override?
public?void?reduceInventory(Integer?pid,?int?num)?{
?Product?product?=?productDao.findById(pid).get();
?product.setStock(product.getStock()?-?num);
?//減庫存?
?productDao.save(product);
}
@Override?
public?void?reduceInventory(Integer?pid,?Integer?number)?{
?Product?product?=?productDao.findById(pid).get();
?if?(product.getStock()???throw?new?RuntimeException("庫存不足");
?}
?int?i?=?1?/?0;
?product.setStock(product.getStock()?-?number);
?productDao.save(product);
}
registry?{
?type?=?"nacos"?
?nacos?{?
?serverAddr?=?"localhost"?
?namespace?=?"public"?
?cluster?=?"default"?
?}?
?}
?config?{?
?type?=?"nacos"?
?nacos?{?
?serverAddr?=?"localhost"?
?namespace?=?"public"?
?cluster?=?"default"?
?}?
?}
service.vgroup_mapping.service-product=default?
service.vgroup_mapping.service-order=default
\#?初始化seata?的nacos配置?
?
\#?注意:?這里要保證nacos是已經(jīng)正常運(yùn)行的?
?
cd?conf?
?
nacos-config.sh?127.0.0.1
cd?bin?
?
seata-server.bat?-p?9000?-m?file
CREATE?TABLE?`undo_log`?
(?
`id`?BIGiNT(20)?NOT?NULL?AUTO_INCREMENT,?
`branch_id`?BIGiNT(20)?NOT?NULL,?
`xid`?VARcHAR(100)?NOT?NULL,?
`context`?VARcHAR(128)?NOT?NULL,?
`rollback_info`?LONGBLOB?NOT?NULL,?
`log_status`?iNT(11)?NOT?NULL,?
`log_created`?DATETIME?NOT?NULL,?
`log_modified`?DATETIME?NOT?NULL,?
`ext`?VARcHAR(100)?DEFAULT?NULL,?
PRIMARY?KEY?(`id`),?
UNIQUE?KEY?`ux_undo_log`?(`xid`,?`branch_id`)?
)?ENGINE?=?INNODB?
AUTO_INCREMENT?=?1?
DEFAULT?CHARSET?=?utf8;
? ?
?com.alibaba.cloud ?
?spring-cloud-starter-alibaba-seata ?
?
??
?com.alibaba.cloud ?
?spring-cloud-starter-alibaba-nacos-config ?
?
@Configuration?
public?class?DataSourceProxyConfig?{
?@Bean?
?@ConfigurationProperties(prefix?=?"spring.datasource")?
?public?DruidDataSource?druidDataSource()?{
??return?new?DruidDataSource();
?}
?@Primary?
?@Bean?
?public?DataSourceProxy?dataSource(DruidDataSource?druidDataSource)?{
??return?new?DataSourceProxy(druidDataSource);
?}
}
registry?{
?type?=?"nacos"?
?nacos?{?
?serverAddr?=?"localhost"?
?namespace?=?"public"?
?cluster?=?"default"?
?}?
?}
?config?{?
?type?=?"nacos"?
?nacos?{?
?serverAddr?=?"localhost"?
?namespace?=?"public"?
?cluster?=?"default"
?}?
?}
spring:?
application:?
name:?service-product?
cloud:?
nacos:?
config:?
server-addr:?localhost:8848?#?nacos的服務(wù)端地址?
namespace:?public?
group:?SEATA_GROUP?
alibaba:?
seata:?
tx-service-group:?${
?spring.application.name
}
@GlobalTransactional//全局事務(wù)控制?
?
public?Order?createOrder(Integer?pid)?{}
seata運(yùn)行流程分析
每個RM使用DataSourceProxy連接數(shù)據(jù)庫,其目的是使用ConnectionProxy,使用數(shù)據(jù)源和數(shù)據(jù)連接代理的目的就是在第一階段將undo_log和業(yè)務(wù)數(shù)據(jù)放在一個本地事務(wù)提交,這樣就保存了只要有業(yè)務(wù)操作就一定有undo_log。
在第一階段undo_log中存放了數(shù)據(jù)修改前和修改后的值,為事務(wù)回滾作好準(zhǔn)備,所以第一階段完成就已經(jīng)將分支事務(wù)提交,也就釋放了鎖資源。
TM開啟全局事務(wù)開始,將XID全局事務(wù)id放在事務(wù)上下文中,通過feign調(diào)用也將XID傳入下游分支事務(wù),每個分支事務(wù)將自己的Branch ID分支事務(wù)ID與XID關(guān)聯(lián)。
第二階段全局事務(wù)提交,TC會通知各各分支參與者提交分支事務(wù),在第一階段就已經(jīng)提交了分支事務(wù),這里各各參與者只需要刪除undo_log即可,并且可以異步執(zhí)行,第二階段很快可以完成。
第二階段全局事務(wù)回滾,TC會通知各各分支參與者回滾分支事務(wù),通過 XID 和 Branch ID 找到相應(yīng)的回滾日志,通過回滾日志生成反向的 SQL 并執(zhí)行,以完成分支事務(wù)回滾到之前的狀態(tài),如果回滾失敗則會重試回滾操作
版權(quán)聲明:本文為博主原創(chuàng)文章,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接和本聲明。
本文鏈接:
https://blog.csdn.net/python8989/article/details/113666402
鋒哥最新SpringCloud分布式電商秒殺課程發(fā)布
??????
??長按上方微信二維碼?2 秒
感謝點(diǎn)贊支持下哈?











