事務(wù)是我們平時(shí)項(xiàng)目中對數(shù)據(jù)操作最為直接、常用的方式,現(xiàn)在無論是大小公司都離不開對事務(wù)的操作,伴隨業(yè)務(wù)的提升,客戶量的積累也大大增加了對事務(wù)管理的難度。
(相關(guān)資料圖)
在本章節(jié)中將會講到如下內(nèi)容:
1、線上環(huán)境對roll back only 的處理2、線上環(huán)境對嵌套事務(wù)的解決方案3、11個(gè)demo分析事務(wù)失效的場景4、分布式事務(wù)5、事務(wù)也能異步1、線上環(huán)境對roll back only 的處理與產(chǎn)生
org.springframework.dao.CannotAcquireLockException: ### Error updating database. Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction### The error may involve xxxMapper.insert-Inline### The error occurred while setting parameters### SQL: INSERT INTO xxx### Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction; Lock wait timeout exceeded; try restarting transaction; nested exception is com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction產(chǎn)生原因:
事務(wù)嵌套,內(nèi)層事務(wù)將異常捕獲未拋出。
2、線上環(huán)境對嵌套事務(wù)的解決方案
優(yōu)化點(diǎn)可以從以下幾點(diǎn)進(jìn)行考慮:
最為直接的方法便是去掉嵌套事務(wù),在controller層統(tǒng)一決定異常處理
對于類似開發(fā)過程中,需考慮將相關(guān)方法長事務(wù)中查詢方法剔除,將方法內(nèi)事務(wù)縮短為最小事務(wù)
出現(xiàn)突發(fā)情況,應(yīng)提供最為簡單有效的方案,讓業(yè)務(wù)正常操作,不受影響
開發(fā)應(yīng)對當(dāng)時(shí)的技術(shù)方案告知相關(guān)測試
在代碼層面,后續(xù)代碼需要前面操作事務(wù)釋放鎖
無需等待插入結(jié)果 直接插入后續(xù)數(shù)據(jù)
將查詢放在事務(wù)外面盡量將大事務(wù)變?yōu)樾∈聞?wù)
捕獲異常 自動(dòng)重試
但是短時(shí)間內(nèi)我還沒有時(shí)間進(jìn)行整改,在不影響主流程的情況下未進(jìn)行整改,但我后續(xù)才知道大錯(cuò)特錯(cuò)。
排查
@timestamp September 1st 2021, 10:20:24.637# @version 1t LOG_DATEFORMAT_PATTERN yyyy-MM-dd HH:mm:ss.SSSt LOG_LEVEL_PATTERN %5pt _id VMaGt _index applog-2021.09.01# _score 1t _type doct appindex applogt appname appt host 10.0.74.157t level ERROR# level_value 40,000t logger_name ExceptionLogCollectort message 未知異常[500] => Transaction rolled back because it has been marked as rollback-only# port 10,792t stack_trace org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-onlyat org.springframework.transaction.support.AbstractPlatformTransactionManager.processRollback(AbstractPlatformTransactionManager.java:873) ~[spring-tx-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:710) ~[spring-tx-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:533) ~[spring-tx-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:304) ~[spring-tx-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98) ~[spring-tx-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688) ~[spring-aop-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]spring-tx-5.1.4.RELEASE.jar-org.springframework.transaction.interceptor.TransactionInterceptor#事務(wù)攔截器avatarspring事務(wù)分為聲明式事務(wù)和編程式事務(wù),若目標(biāo)方法存在事務(wù),spring會對bean生成一個(gè)代理對象,從日志來看是cglib的
入口98行springaop事務(wù)增強(qiáng) TransactionAspectSupport在事務(wù)中的調(diào)用,執(zhí)行代理類的目標(biāo)方法觸發(fā)invoke
@Nullableprotected Object invokeWithinTransaction(Method method, @Nullable Class> targetClass, final InvocationCallback invocation) throws Throwable方法為protected的,根據(jù)源代碼注釋解析if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager))
如果事務(wù)屬性為null 且事務(wù)類型是CallbackPreferringPlatformTransactionManager進(jìn)入304行commitTransactionAfterReturning(txInfo);方法
意為事務(wù)成功后執(zhí)行,有異常不執(zhí)行,沒有事務(wù)不執(zhí)行,也就是為后面的事務(wù)方法異常時(shí)沒執(zhí)行進(jìn)行了鋪墊,533行
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());事務(wù)進(jìn)行commit時(shí)進(jìn)行判斷
如果不是進(jìn)行全局事務(wù)提交 但是是RollbackOnly的話
走processRollback處理實(shí)際回滾
@Override public final void commit(TransactionStatus status) throws TransactionException { if (status.isCompleted()) { throw new IllegalTransactionStateException( "Transaction is already completed - do not call commit or rollback more than once per transaction"); } DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status; if (defStatus.isLocalRollbackOnly()) { if (defStatus.isDebug()) { logger.debug("Transactional code has requested rollback"); } processRollback(defStatus, false); return; } if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) { if (defStatus.isDebug()) { logger.debug("Global transaction is marked as rollback-only but transactional code requested commit"); } 日志追蹤的710行-----記住此處傳true processRollback(defStatus, true); return; } processCommit(defStatus); }private void processRollback(DefaultTransactionStatus status, boolean unexpected) { try { 入?yún)閠rue boolean unexpectedRollback = unexpected; try { triggerBeforeCompletion(status); if (status.hasSavepoint()) { if (status.isDebug()) { logger.debug("Rolling back transaction to savepoint"); } status.rollbackToHeldSavepoint(); } else if (status.isNewTransaction()) { if (status.isDebug()) { logger.debug("Initiating transaction rollback"); } doRollback(status); } else { // Participating in larger transaction if (status.hasTransaction()) { if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) { if (status.isDebug()) { logger.debug("Participating transaction failed - marking existing transaction as rollback-only"); } doSetRollbackOnly(status); } else { if (status.isDebug()) { logger.debug("Participating transaction failed - letting transaction originator decide on rollback"); } } } else { logger.debug("Should roll back transaction but cannot - no transaction available"); } // Unexpected rollback only matters here if we"re asked to fail early if (!isFailEarlyOnGlobalRollbackOnly()) { unexpectedRollback = false; } } } catch (RuntimeException | Error ex) { triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); throw ex; } triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); 日志追蹤的873行 拋出異常 // Raise UnexpectedRollbackException if we had a global rollback-only marker if (unexpectedRollback) { throw new UnexpectedRollbackException( "Transaction rolled back because it has been marked as rollback-only"); } } finally { cleanupAfterCompletion(status); } }事務(wù)這里場景和傳播行為相關(guān)知識點(diǎn)太多了,這個(gè)后續(xù)接著分析
但就此場景將偽代碼貼一下
try { methodA() }catch { } @Transactional(rollbackFor = Exception.class) public methodA() { methodB() } @Transactional(rollbackFor = Exception.class) public methodB() { try { methodC() } catch { } } methodC() { 當(dāng)C方法拋出異常時(shí) }不知道大家對于rpc行為調(diào)用的接口是如何處理的,我們以前是將rpc調(diào)用的接口有Biz接收進(jìn)來,進(jìn)行參數(shù)處理,領(lǐng)域模型轉(zhuǎn)換后,調(diào)取service進(jìn)行內(nèi)部數(shù)據(jù)處理的,但此時(shí)的接口在主流程上會伴隨著另一個(gè)第三方接口的寫操作,需進(jìn)行事務(wù)處理,那么內(nèi)層service接口為什么還要進(jìn)行事務(wù)管理?在設(shè)計(jì)上理應(yīng)不對rpc接口操作的service進(jìn)行開放調(diào)用的,但業(yè)務(wù)上區(qū)分不同場景,不同供應(yīng)商,不同酒店等對接口進(jìn)行了反射調(diào)用,或者app調(diào)用,導(dǎo)致內(nèi)層service也進(jìn)行了事務(wù)操作,那么問題來了,嵌套事務(wù)時(shí),如果內(nèi)層事務(wù)注解取消不拋出
UnexpectedRollbackException,實(shí)際此方法內(nèi)并沒有完全執(zhí)行完,
我希望是怎樣的?我希望在保持事務(wù)原子性的前提,內(nèi)層事務(wù)回滾則整個(gè)全局事務(wù)回滾,且不報(bào)此異常
第一種方法isGlobalRollbackOnParticipationFailure方法,讓主事務(wù)來決定是否回滾,
改動(dòng)成本大
而在Springaop中,被攔截的方法需要顯式的拋出異常,并不能經(jīng)過任何處理,這樣aop才能進(jìn)行回滾,默認(rèn)aop是只catchruntimeException的異常 第二種方法可以在catch塊里加上 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() 手動(dòng)回滾 即便上層事務(wù)發(fā)生了異常,也想要最終提交整個(gè)事務(wù)呢?如果有這樣的需求的話,可以給事務(wù)管理器配置一個(gè)參數(shù) setGlobalRollbackOnParticipationFailure(false); # 改動(dòng)成本大
解決方案:在內(nèi)層方法中不進(jìn)行方法的try catch,有異常操作時(shí)在外層事務(wù)進(jìn)行處理,且可決定是否回滾,特定的異常也再次處理
回顧:事務(wù)的失效場景(事務(wù)不生效和事務(wù)不回滾)
3、11個(gè)demo分析事務(wù)失效的場景
@Slf4j@Servicepublic class DemoService {@Autowiredprivate Test1Mapper test1Mapper;@Autowiredprivate TestMapper testMapper;@Autowiredprivate InvalidTransactionService invalidTransactionService;@Autowiredprivate ExecutorService executorService;@Autowiredprivate DemoService _self;@Autowiredprivate ValidTransactionService validTransactionService;@Autowiredprivate RequireNewTransactionService requireNewTransactionService;/******************************************************** * 事務(wù)不生效場景1 * 相當(dāng)于調(diào)用this調(diào)用,沒有產(chǎn)生代理對象調(diào)用,解決方法,自己把自己注入以后調(diào)用 ********************************************************/public void demo1() {invalidTransaction();//TODO other logic code here}@Transactionalpublic void invalidTransaction() {TestDO test = new TestDO();test.setName("11111");testMapper.insert(test);Test1DO test1 = new Test1DO();test1.setCust("2222");test1Mapper.insert(test1);throw new WMSException(ErrorCodeEnum.BD10001001.code(),"事務(wù)不生效場景1");}/******************************************************** * 事務(wù)不生效場景二 * 這個(gè)例子的目的是為了catch住內(nèi)層事務(wù)的異常,讓外層事務(wù)成功,但是實(shí)際上沒有內(nèi)外層事務(wù)都回滾了 * * 這里A和B都受事務(wù)控制,并且是處于同一個(gè)事務(wù)的。 * A調(diào)用B,A中抓了B的異常,當(dāng)B發(fā)生異常的時(shí)候,B的操作應(yīng)該回滾,但是A吃了異常,A方法中沒有產(chǎn)生異常,所以A的操作又應(yīng)該提交,二者是相互矛盾的。 * spring的事務(wù)關(guān)聯(lián)攔截器在抓到B的異常后就會標(biāo)記rollback-only為true,當(dāng)A執(zhí)行完準(zhǔn)備提交后,發(fā)現(xiàn)rollback-only為true,也會回滾,并拋出異常告訴調(diào)用者。 * * 報(bào)錯(cuò)提示:Transaction rolled back because it has been marked as rollback-only * * 如果想使外層事務(wù)生效可以把內(nèi)層事務(wù)傳播特性修改為:@Transactional(propagation = Propagation.REQUIRES_NEW) * ********************************************************/@Transactionalpublic void demo2() {TestDO test = new TestDO();test.setName("3333");testMapper.insert(test);try {invalidTransactionService.transaction();}catch (Exception e) {log.error("服務(wù)異常,異常被捕獲", e);}}/******************************************************** * 事務(wù)不生效場景三 * * 因?yàn)殚_了線程異步執(zhí)行,等于事務(wù)完全在兩個(gè)線程內(nèi),不在一個(gè)線程,所以即使拋錯(cuò),也是一個(gè)生效一個(gè)不生效, * 事務(wù)沒有回滾 * ********************************************************/@Transactionalpublic void demo3() {TestDO test = new TestDO();test.setName("5555");testMapper.insert(test);executorService.execute(() -> {Test1DO test1 = new Test1DO();test1.setCust("6666");test1Mapper.insert(test1);});throw new WMSException(ErrorCodeEnum.BD10001001.code(),"事務(wù)不生效場景3");}/******************************************************** * 事務(wù)不生效場景八 * Spring默認(rèn)情況下會對運(yùn)行期例外(RunTimeException)進(jìn)行事務(wù)回滾。這個(gè)例外是unchecked,如果遇到checked意外就不回滾。 * Exception包含RuntimeException體系和其他非RuntimeException的體系 * Error和RuntimeException及其子類成為未檢查異常(unchecked),其它異常成為已檢查異常(checked)。 * spring聲明式事務(wù)管理默認(rèn)對非檢查型異常和運(yùn)行時(shí)異常進(jìn)行事務(wù)回滾,而對檢查型異常則不進(jìn)行回滾操作 * * *那么什么是檢查型異常什么又是非檢查型異常呢? * 1.繼承自runtimeexception或error的是非檢查型異常,而繼承自exception的則是檢查型異常(當(dāng)然,runtimeexception本身也是exception的子類)。 * 2.對非檢查型類異常可以不用捕獲,而檢查型異常則必須用try語句塊進(jìn)行處理或者把異常交給上級方法處理總之就是必須寫代碼處理它。所以必須在service捕獲異常,然后再次拋出,這樣事務(wù)方才起效。 * * @throws IOException * ********************************************************/@Transactionalpublic void demo8() throws IOException {TestDO test = new TestDO();test.setName("11111");testMapper.insert(test);Test1DO test1 = new Test1DO();test1.setCust("2222");test1Mapper.insert(test1);throw new IOException("事務(wù)不生效場景8");}/******************************************************** * 事務(wù)不生效場景九 * @throws IOException * ********************************************************/public void demo9(){invalidTransaction2();}@Transactionalprivate void invalidTransaction2() {TestDO test = new TestDO();test.setName("11111");testMapper.insert(test);Test1DO test1 = new Test1DO();test1.setCust("2222");test1Mapper.insert(test1);throw new WMSException("事務(wù)不生效場景9");}/******************************************************** * 事務(wù)生效場景1 * ********************************************************/public void demo4() {_self.invalidTransaction();//TODO other logic code here}/******************************************************** * 事務(wù)生效場景二 * * 因?yàn)閮?nèi)層沒有事務(wù)控制,所以內(nèi)層報(bào)錯(cuò),不會混回滾,同樣外層catch住,所以外層業(yè)務(wù)成功 ********************************************************/@Transactionalpublic void demo5() {TestDO test = new TestDO();test.setName("7777");testMapper.insert(test);try {validTransactionService.transaction();}catch (Exception e) {log.error("服務(wù)異常,異常被捕獲", e);}}/******************************************************** * 事務(wù)生效場景三 * *內(nèi)層事務(wù)配置的是REQUIRES_NEW,表示自己用自己的,不和外層有牽連,內(nèi)層如果報(bào)錯(cuò),事務(wù)會回滾 * 外層如果catch住了,就可以正常執(zhí)行,外層生效,內(nèi)層回滾 ********************************************************/@Transactionalpublic void demo6() {TestDO test = new TestDO();test.setName("9999");testMapper.insert(test);try {requireNewTransactionService.transactionWithException();}catch (Exception e) {log.error("服務(wù)異常,異常被捕獲", e);}}/******************************************************** * 獨(dú)立事務(wù) * 內(nèi)外層事務(wù)獨(dú)立,內(nèi)層操作未報(bào)錯(cuò),事務(wù)正常執(zhí)行,外層有錯(cuò),事務(wù)回滾。 ********************************************************/@Transactionalpublic void demo7() {TestDO test = new TestDO();test.setName("9999");testMapper.insert(test);requireNewTransactionService.transaction();throw new WMSException(ErrorCodeEnum.BD10001001.code(),"獨(dú)立事務(wù)");}}4、分布式事務(wù)以及分布式事務(wù)嵌套
一次業(yè)務(wù)操作需要跨多個(gè)數(shù)據(jù)源或需要垮多個(gè)系統(tǒng)進(jìn)行遠(yuǎn)程調(diào)用,就會產(chǎn)生分布式事務(wù)問題
全局事務(wù)一致性問題
全局事務(wù)id+三組件 tc+tm+rm
Seata(AT 模式)的默認(rèn)全局隔離級別是 讀未提交(Read Uncommitted)
Seata 是 Simple Extensible Autonomous Transaction Architecture 的簡寫,由 feascar 改名而來。
AT模式 默認(rèn)
TCC模式
XA模式
SAGA模式 長事務(wù)解決方案
XID 由ip 端口號 加全局事務(wù)id生成
關(guān)于分布式事務(wù),工程領(lǐng)域主要討論的是強(qiáng)一致性和最終一致性的解決方案。典型方案包括:
兩階段提交(2PC, Two-phase Commit)方案
eBay 事件隊(duì)列方案
TCC 補(bǔ)償模式
緩存數(shù)據(jù)最終一致性
一致性理論
分布式事務(wù)的目的是保障分庫數(shù)據(jù)一致性,而跨庫事務(wù)會遇到各種不可控制的問題,如個(gè)別節(jié)點(diǎn)永久性宕機(jī),像單機(jī)事務(wù)一樣的ACID是無法奢望的。另外,業(yè)界著名的CAP理論也告訴我們,對分布式系統(tǒng),需要將數(shù)據(jù)一致性和系統(tǒng)可用性、分區(qū)容忍性放在天平上一起考慮。
兩階段提交協(xié)議(簡稱2PC)是實(shí)現(xiàn)分布式事務(wù)較為經(jīng)典的方案,但2PC 的可擴(kuò)展性很差,在分布式架構(gòu)下應(yīng)用代價(jià)較大,eBay 架構(gòu)師Dan Pritchett 提出了BASE 理論,用于解決大規(guī)模分布式系統(tǒng)下的數(shù)據(jù)一致性問題。BASE 理論告訴我們:可以通過放棄系統(tǒng)在每個(gè)時(shí)刻的強(qiáng)一致性來換取系統(tǒng)的可擴(kuò)展性。
CAP理論在分布式系統(tǒng)中,一致性(Consistency)、可用性(Availability)和分區(qū)容忍性(Partition Tolerance)3 個(gè)要素最多只能同時(shí)滿足兩個(gè),不可兼得。
其中,分區(qū)容忍性又是不可或缺的。
avatar
一致性:分布式環(huán)境下多個(gè)節(jié)點(diǎn)的數(shù)據(jù)是否強(qiáng)一致??捎眯裕悍植际椒?wù)能一直保證可用狀態(tài)。當(dāng)用戶發(fā)出一個(gè)請求后,服務(wù)能在有限時(shí)間內(nèi)返回結(jié)果。分區(qū)容忍性:特指對網(wǎng)絡(luò)分區(qū)的容忍性。舉例:Cassandra、Dynamo
等,默認(rèn)優(yōu)先選擇AP,弱化C;HBase、MongoDB 等,默認(rèn)優(yōu)先選擇CP,弱化A。
BASE理論核心思想:
基本可用(BasicallyAvailable):指分布式系統(tǒng)在出現(xiàn)故障時(shí),允許損失部分的可用性來保證核心可用。
軟狀態(tài)(SoftState):指允許分布式系統(tǒng)存在中間狀態(tài),該中間狀態(tài)不會影響到系統(tǒng)的整體可用性。
最終一致性(EventualConsistency):指分布式系統(tǒng)中的所有副本數(shù)據(jù)經(jīng)過一定時(shí)間后,最終能夠達(dá)到一致的狀態(tài)。
一致性模型數(shù)據(jù)的一致性模型可以分成以下 3 類:強(qiáng)一致性:數(shù)據(jù)更新成功后,任意時(shí)刻所有副本中的數(shù)據(jù)都是一致的,一般采用同步的方式實(shí)現(xiàn)。 弱一致性:數(shù)據(jù)更新成功后,系統(tǒng)不承諾立即可以讀到最新寫入的值,也不承諾具體多久之后可以讀到。 最終一致性:弱一致性的一種形式,數(shù)據(jù)更新成功后,系統(tǒng)不承諾立即可以返回最新寫入的值,但是保證最終會返回上一次更新操作的值。 分布式系統(tǒng)數(shù)據(jù)的強(qiáng)一致性、弱一致性和最終一致性可以通過Quorum NRW算法分析。分布式事務(wù)解決方案2PC方案——強(qiáng)一致性2PC的核心原理是通過提交分階段和記日志的方式,記錄下事務(wù)提交所處的階段狀態(tài),在組件宕機(jī)重啟后,可通過日志恢復(fù)事務(wù)提交的階段狀態(tài),并在這個(gè)狀態(tài)節(jié)點(diǎn)重試,如Coordinator重啟后,通過日志可以確定提交處于Prepare還是PrepareAll狀態(tài),若是前者,說明有節(jié)點(diǎn)可能沒有Prepare成功,或所有節(jié)點(diǎn)Prepare成功但還沒有下發(fā)Commit,狀態(tài)恢復(fù)后給所有節(jié)點(diǎn)下發(fā)RollBack;若是PrepareAll狀態(tài),需要給所有節(jié)點(diǎn)下發(fā)Commit,數(shù)據(jù)庫節(jié)點(diǎn)需要保證Commit冪等。avatar2PC方案的問題:同步阻塞。數(shù)據(jù)不一致。單點(diǎn)問題。升級的3PC方案旨在解決這些問題,主要有兩個(gè)改進(jìn):增加超時(shí)機(jī)制。兩階段之間插入準(zhǔn)備階段。但三階段提交也存在一些缺陷,要徹底從協(xié)議層面避免數(shù)據(jù)不一致,可以采用Paxos或者Raft算法。eBay 事件隊(duì)列方案——最終一致性eBay 的架構(gòu)師Dan Pritchett,曾在一篇解釋BASE 原理的論文《Base:An AcidAlternative》中提到一個(gè)eBay分布式系統(tǒng)一致性問題的解決方案。它的核心思想是將需要分布式處理的任務(wù)通過消息或者日志的方式來異步執(zhí)行,消息或日志可以存到本地文件、數(shù)據(jù)庫或消息隊(duì)列,再通過業(yè)務(wù)規(guī)則進(jìn)行失敗重試,它要求各服務(wù)的接口是冪等的。描述的場景為,有用戶表user和交易表transaction,用戶表存儲用戶信息、總銷售額和總購買額,交易表存儲每一筆交易的流水號、買家信息、賣家信息和交易金額。如果產(chǎn)生了一筆交易,需要在交易表增加記錄,同時(shí)還要修改用戶表的金額。avatar論文中提出的解決方法是將更新交易表記錄和用戶表更新消息放在一個(gè)本地事務(wù)來完成,為了避免重復(fù)消費(fèi)用戶表更新消息帶來的問題,增加一個(gè)操作記錄表updates_applied來記錄已經(jīng)完成的交易相關(guān)的信息。這個(gè)方案的核心在于第二階段的重試和冪等執(zhí)行。失敗后重試,這是一種補(bǔ)償機(jī)制,它是能保證系統(tǒng)最終一致的關(guān)鍵流程。
TCC (Try-Confirm-Cancel)補(bǔ)償模式——最終一致性
某業(yè)務(wù)模型如圖,由服務(wù) A、服務(wù)B、服務(wù)C、服務(wù)D 共同組成的一個(gè)微服務(wù)架構(gòu)系統(tǒng)。服務(wù)A 需要依次調(diào)用服務(wù)B、服務(wù)C 和服務(wù)D
共同完成一個(gè)操作。當(dāng)服務(wù)A 調(diào)用服務(wù)D 失敗時(shí),若要保證整個(gè)系統(tǒng)數(shù)據(jù)的一致性,就要對服務(wù)B 和服務(wù)C 的invoke
操作進(jìn)行回滾,執(zhí)行反向的revert 操作?;貪L成功后,整個(gè)微服務(wù)系統(tǒng)是數(shù)據(jù)一致的。
avatar
實(shí)現(xiàn)關(guān)鍵要素:服務(wù)調(diào)用鏈必須被記錄下來。每個(gè)服務(wù)提供者都需要提供一組業(yè)務(wù)邏輯相反的操作,互為補(bǔ)償,同時(shí)回滾操作要保證冪等。必須按失敗原因執(zhí)行不同的回滾策略。
緩存數(shù)據(jù)最終一致性
在我們的業(yè)務(wù)系統(tǒng)中,緩存(Redis 或者M(jìn)emcached)通常被用在數(shù)據(jù)庫前面,作為數(shù)據(jù)讀取的緩沖,使得I/O
操作不至于直接落在數(shù)據(jù)庫上。以商品詳情頁為例,假如賣家修改了商品信息,并寫回到數(shù)據(jù)庫,但是這時(shí)候用戶從商品詳情頁看到的信息還是從緩存中拿到的過時(shí)數(shù)據(jù),這就出現(xiàn)了緩存系統(tǒng)和數(shù)據(jù)庫系統(tǒng)中的數(shù)據(jù)不一致的現(xiàn)象。
要解決該場景下緩存和數(shù)據(jù)庫數(shù)據(jù)不一致的問題我們有以下兩種解決方案:為緩存數(shù)據(jù)設(shè)置過期時(shí)間。當(dāng)緩存中數(shù)據(jù)過期后,業(yè)務(wù)系統(tǒng)會從數(shù)據(jù)庫中獲取數(shù)據(jù),并將新值放入緩存。這個(gè)過期時(shí)間就是系統(tǒng)可以達(dá)到最終一致的容忍時(shí)間。更新數(shù)據(jù)庫數(shù)據(jù)后同時(shí)清除緩存數(shù)據(jù)。數(shù)據(jù)庫數(shù)據(jù)更新后,同步刪除緩存中數(shù)據(jù),使得下次對商品詳情的獲取直接從數(shù)據(jù)庫中獲取,并同步到緩存。
常用組件: Seata,Sega,Atomikos
avatar
TC (Transaction Coordinator) - 事務(wù)協(xié)調(diào)者
維護(hù)全局和分支事務(wù)的狀態(tài),驅(qū)動(dòng)全局事務(wù)提交或回滾。
TM (Transaction Manager) - 事務(wù)管理器
定義全局事務(wù)的范圍:開始全局事務(wù)、提交或回滾全局事務(wù)。
RM (Resource Manager) - 資源管理器
管理分支事務(wù)處理的資源,與TC交談以注冊分支事務(wù)和報(bào)告分支事務(wù)的狀態(tài),并驅(qū)動(dòng)分支事務(wù)提交或回滾。
avatar
安裝
關(guān)鍵注解全局@GlobalTranstional
1.更改事務(wù)組名稱service
2.store更改mode 修改db
3.執(zhí)行sql
4.修改注冊進(jìn)nacos
5.啟動(dòng)seata-server.bat
如何保證分布唯一全局id的生成
5、分布式事務(wù)異步方案
看下分布式事務(wù)的異步問題,根據(jù)事務(wù)的xid搭配future在切面里對注解進(jìn)行處理,實(shí)現(xiàn)異步+分布式事務(wù)的并存
注意事項(xiàng)
這個(gè)依賴只是用來解決部分問題,不是解決全部問題
這個(gè)僅用于TM端,不要用來RM端(其實(shí)要實(shí)現(xiàn)RM端的話,可以仿照SeataAsyncAspect,寫一個(gè)aspect,很簡單的)
不要進(jìn)行事務(wù)嵌套,不支持事務(wù)嵌套?。?!
確保異步的多個(gè)操作之間是沒有先后順序的
這個(gè)是一個(gè)私人包裝處理,僅供參考,還未應(yīng)用到生產(chǎn)環(huán)境
—-待續(xù)