分库分表的事务处理机制
轉(zhuǎn)載自?分庫分表的事務(wù)處理機(jī)制
分布式事務(wù)
?
由于我們將單表的數(shù)據(jù)切片后存儲(chǔ)在多個(gè)數(shù)據(jù)庫甚至多個(gè)數(shù)據(jù)庫實(shí)例中,所以依靠數(shù)據(jù)庫本身的事務(wù)機(jī)制不能滿足所有場(chǎng)景的需要。但是,我們推薦在一個(gè)數(shù)據(jù)庫實(shí)例中的操作盡可能使用本地事務(wù)來保證一致性,跨數(shù)據(jù)庫實(shí)例的一系列更新操作需要根據(jù)事務(wù)路由在不同的數(shù)據(jù)源中完成,各個(gè)數(shù)據(jù)源之間的更新操作需要通過分布式事務(wù)處理。
?
這里只介紹實(shí)現(xiàn)分布式操作一致性的幾個(gè)主流思路,保證分布式事務(wù)一致性的具體方法請(qǐng)參考《分布式服務(wù)架構(gòu):原理、設(shè)計(jì)與實(shí)戰(zhàn)》中第2章的內(nèi)容。
主流的分布式事務(wù)解決方案有三種:兩階段提交協(xié)議、最大努力保證模式和事務(wù)補(bǔ)償機(jī)制。
?
1兩階段提交協(xié)議
?
兩階段提交協(xié)議將分布式事務(wù)分為兩個(gè)階段,一個(gè)是準(zhǔn)備階段,一個(gè)是提交階段,兩個(gè)階段都由事務(wù)管理器發(fā)起。基于兩階段提交協(xié)議,事務(wù)管理器能夠最大限度地保證跨數(shù)據(jù)庫操作的事務(wù)的原子性,是分布式系統(tǒng)環(huán)境下最嚴(yán)格的事務(wù)實(shí)現(xiàn)方法。符合J2EE規(guī)范的AppServer(例如:Websphere、Weblogic、
Jboss等)對(duì)關(guān)系型數(shù)據(jù)庫數(shù)據(jù)源和消息隊(duì)列都實(shí)現(xiàn)了兩階段提交協(xié)議,只需在使用時(shí)配置即可。如圖3-9所示。
?
?
?
但是,兩階段提交協(xié)議也帶來了性能方面的問題,難于進(jìn)行水平伸縮,因?yàn)樵谔峤皇聞?wù)的過程中,事務(wù)管理器需要和每個(gè)參與者進(jìn)行準(zhǔn)備和提交的操作的協(xié)調(diào),在準(zhǔn)備階段鎖定資源,在提交階段消費(fèi)資源,但是由于參與者較多,鎖定資源和消費(fèi)資源之間的時(shí)間差被拉長,導(dǎo)致響應(yīng)速度較慢,在此期間產(chǎn)生死鎖或者不確定結(jié)果的可能性較大。因此,在互聯(lián)網(wǎng)行業(yè)里,為了追求性能的提升,很少使用兩階段提交協(xié)議。
?
另外,由于兩階段提交協(xié)議是阻塞協(xié)議,在極端情況下不能快速響應(yīng)請(qǐng)求方,因此有人提出了三階段提交協(xié)議,解決了兩階段提交協(xié)議的阻塞問題,但仍然需要事務(wù)管理器在參與者之間協(xié)調(diào),才能完成一個(gè)分布式事務(wù)。
?
?
2最大努力保證模式
?
這是一種非常通用的保證分布式一致性的模式,很多開發(fā)人員一直在使用,但是并未意識(shí)到這是一種模式。最大努力保證模式適用于對(duì)一致性要求并不十分嚴(yán)格但是對(duì)性能要求較高的場(chǎng)景。
?
具體的實(shí)現(xiàn)方法是,在更新多個(gè)資源時(shí),將多個(gè)資源的提交盡量延后到最后一刻處理,這樣的話,如果業(yè)務(wù)流程出現(xiàn)問題,則所有的資源更新都可以回滾,事務(wù)仍然保持一致。唯一可能出現(xiàn)問題的情況是在提交多個(gè)資源時(shí)發(fā)生了系統(tǒng)問題,比如網(wǎng)絡(luò)問題等,但是這種情況是非常罕見的,一旦出現(xiàn)這種情況,就需要進(jìn)行實(shí)時(shí)補(bǔ)償,將已提交的事務(wù)進(jìn)行回滾,這和我們常說的TCC模式有些類似。
?
下面是使用最大努力保證模式的一個(gè)樣例,在該樣例中涉及兩個(gè)操作,一個(gè)是從消息隊(duì)列消費(fèi)消息,一個(gè)是更新數(shù)據(jù)庫,需要保證分布式的一致性。
-
開始消息事務(wù)。
-
開始數(shù)據(jù)庫事務(wù)。
-
接收消息。
-
更新數(shù)據(jù)庫。
-
提交數(shù)據(jù)庫事務(wù)。
-
提交消息事務(wù)。
這時(shí),從第1步到第4步并不是很關(guān)鍵,關(guān)鍵的是第5步和第6步,需要將其放在最后一起提交,盡最大努力保證前面的業(yè)務(wù)處理的一致性。到了第5步和第6步,業(yè)務(wù)邏輯處理完成,這時(shí)只可能發(fā)生系統(tǒng)錯(cuò)誤,如果第5步失敗,則可以將消息隊(duì)列和數(shù)據(jù)庫事務(wù)全部回滾,保持一致。如果第5步成功,第6步遇到了網(wǎng)絡(luò)超時(shí)等問題,則這是唯一可能產(chǎn)生問題的情況,在這種情況下,消息的消費(fèi)過程并沒有被提交到消息隊(duì)列,消息隊(duì)列可能會(huì)重新發(fā)送消息給其他消息處理服務(wù),這會(huì)導(dǎo)致消息被重復(fù)消費(fèi),但是可以通過冪等處理來保證消除重復(fù)消息帶來的影響。
?
當(dāng)然,在使用這種模式時(shí),我們要充分考慮每個(gè)資源的提交順序。我們?cè)谏a(chǎn)實(shí)踐中遇到的一種反模式,就是在數(shù)據(jù)庫事務(wù)中嵌套遠(yuǎn)程調(diào)用,而且遠(yuǎn)程調(diào)用是耗時(shí)任務(wù),導(dǎo)致數(shù)據(jù)庫事務(wù)被拉長,最后拖垮數(shù)據(jù)庫。因此,上面的案例涉及的是消息事務(wù)嵌套數(shù)據(jù)庫事務(wù),在這里必須進(jìn)行充分評(píng)估和設(shè)計(jì),才可以規(guī)避事務(wù)風(fēng)險(xiǎn)。
?
3事務(wù)補(bǔ)償機(jī)制
?
顯然,在對(duì)性能要求很高的場(chǎng)景中,兩階段提交協(xié)議并不是一種好方案,最大努力保證模式也會(huì)使多個(gè)分布式操作互相嵌套,有可能互相影響。這里,我們給出事務(wù)補(bǔ)償機(jī)制,其性能很高,并且能夠盡最大可能地保證事務(wù)的最終一致性。
?
在數(shù)據(jù)庫分庫分表后,如果涉及的多個(gè)更新操作在某一個(gè)數(shù)據(jù)庫范圍內(nèi)完成,則可以使用數(shù)據(jù)庫內(nèi)的本地事務(wù)保證一致性;對(duì)于跨庫的多個(gè)操作,可通過補(bǔ)償和重試,使其在一定的時(shí)間窗口內(nèi)完成操作,這樣就可以實(shí)現(xiàn)事務(wù)的最終一致性,突破事務(wù)遇到問題就滾回的傳統(tǒng)思路。
?
如果采用事務(wù)補(bǔ)償機(jī)制,則在遇到問題時(shí),我們需要記錄遇到問題的環(huán)境、信息、步驟、狀態(tài)等,后續(xù)通過重試機(jī)制使其達(dá)到最終一致性,詳細(xì)內(nèi)容可以參考《分布式服務(wù)架構(gòu):原理、設(shè)計(jì)與實(shí)戰(zhàn)》第2章,徹底理解ACID原理、CAP理論、BASE原理、最終一致性模式等內(nèi)容。
?
事務(wù)路由
?
無論使用上面哪種方法實(shí)現(xiàn)分布式事務(wù),都需要對(duì)分庫分表的多個(gè)數(shù)據(jù)源路由事務(wù),一般通過對(duì)Spring環(huán)境的配置,為不同的數(shù)據(jù)源配置不同的事務(wù)管理器(TransactionManager),這樣,如果更新操作在一個(gè)數(shù)據(jù)庫實(shí)例內(nèi)發(fā)生,便可以使用數(shù)據(jù)源的事務(wù)來處理。對(duì)于跨數(shù)據(jù)源的事務(wù),可通過在應(yīng)用層使用最大努力保證模式和事務(wù)補(bǔ)償機(jī)制來達(dá)成事務(wù)的一致性。當(dāng)然,有時(shí)我們需要通過編寫程序來選擇數(shù)據(jù)庫的事務(wù)管理器,根據(jù)實(shí)現(xiàn)方式的不同,可將事務(wù)路由具體分為以下三種。
?
1自動(dòng)提交事務(wù)路由
自動(dòng)提交事務(wù)通過依賴JDBC數(shù)據(jù)源的自動(dòng)提交事務(wù)特性,對(duì)任何數(shù)據(jù)庫進(jìn)行更新操作后會(huì)自動(dòng)提交事務(wù),不需要開發(fā)人員手工操作事務(wù),也不需要配置事務(wù),實(shí)現(xiàn)起來很簡(jiǎn)單,但是只能滿足簡(jiǎn)單的業(yè)務(wù)邏輯需求。
?
在通常情況下,JDBC在連接創(chuàng)建后默認(rèn)設(shè)置自動(dòng)提交為true,當(dāng)然,也可以在獲取連接后手工修改這個(gè)屬性,代碼如下:
?
connnection conn =?null; ? try{ ?conn = getConnnection(); ?conn.setAutoCommit(true); ?// 數(shù)據(jù)庫操作……………………………conn.commit(); ? }catch(Throwable e){ ?if(conn!=null){ ?try?{ ?conn.rollback(); ?}?catch?(SQLException e1) { ?e1.printStackTrace(); ?} ?} ?throw?new?RuntimeException(e); ? }finally{ ?if(conn!=null){ ?try?{ ?conn.close(); ?}?catch?(SQLException e) { ?e.printStackTrace(); ?} ?} ? }?
我們基本不需要使用原始的JDBC API來改變這些屬性,這些操作一般都會(huì)被封裝在我們使用的框架中。3.6節(jié)介紹的開源數(shù)據(jù)庫分庫分表框架dbsplit默認(rèn)使用的就是這種模式。
?
2可編程事務(wù)路由
?
我們?cè)趹?yīng)用中通常采用Spring的聲明式的事務(wù)來管理數(shù)據(jù)庫事務(wù),在分庫分表時(shí),事務(wù)處理是個(gè)問題,在一個(gè)需要開啟事務(wù)的方法中,需要?jiǎng)討B(tài)地確定開啟哪個(gè)數(shù)據(jù)庫實(shí)例的事務(wù),也就是說在每個(gè)開啟事務(wù)的方法調(diào)用前就必須確定開啟哪個(gè)數(shù)據(jù)源的事務(wù)。下面使用偽代碼來說明如何實(shí)現(xiàn)一個(gè)可編程事務(wù)路由的小框架。
?
首先,通過Spring配置文件展示可編程事務(wù)小框架是怎么使用的:
<?xml version="1.0?> <beans><bean?id="sharding-db-trx0"class="org.springframework.jdbc.datasource.Data SourceTransactionManager"><property?name="dataSource"><ref?bean="sharding-db0"?/></property></bean><bean?id="sharding-db-trx1"class="org.springframework.jdbc.datasource.DataSourceTransactionMana ger"><property?name="dataSource"><ref?bean="sharding-db1"?/></property></bean><bean?id="sharding-db-trx2"class="org.springframework.jdbc.datasource.DataSourceTransactionMana ger"><property?name="dataSource"><ref?bean="sharding-db2"?/></property></bean><bean?id="sharding-db-trx3"class="org.springframework.jdbc.datasource.DataSourceTransactionMana ger"><property?name="dataSource"><ref?bean="sharding-db3"?/></property></bean><bean?id="shardingTransactionManager"?class="com.robert.dbsplit.core. ShardingTransactionManager"><property?name="proxyTransactionManagers"><map?value-type="org.springframework.transaction.PlatformTran sactionManager"><entry?key="sharding0"?value-ref="sharding-db-trx0"?/><entry?key="sharding1"?value-ref="sharding-db-trx1"?/><entry?key="sharding2"?value-ref="sharding-db-trx2"?/><entry?key="sharding3"?value-ref="sharding-db-trx3"?/></map></property></bean><aop:config><aop:advisor?advice-ref="txAdvice"?pointcut="execution(* com.robert.biz.*insert(..))"/><aop:advisor?advice-ref="txAdvice"?pointcut="execution(* com.robert.biz.*update(..))"/><aop:advisor?advice-ref="txAdvice"?pointcut="execution(* com.robert.biz.*delete(..))"/></aop:config><tx:advice?id="txAdvice"?transaction-manager="shardingTransactionManager"><tx:attributes><tx:method?name="*"?rollback-for="java.lang.Exception"/></tx:attributes></tx:advice></beans>?
這里使用Spring環(huán)境的aop和tx標(biāo)簽來攔截com.robert.biz包下的所有插入、更新和刪除的方法,當(dāng)指定的包的方法被調(diào)用時(shí),就會(huì)使用Spring提供的事務(wù)Advice,Spring的事務(wù)Advice(tx:advice)會(huì)使用事務(wù)管理器來控制事務(wù),如果某個(gè)方法發(fā)生了異常,那么Spring的事務(wù)Advice就會(huì)使shardingTransactionManager回滾相應(yīng)的事務(wù)。
?
我們看到shardingTransactionManager的類型是ShardingTransactionManager,這個(gè)類型是我們開發(fā)的一個(gè)組合的事務(wù)管理器,這個(gè)事務(wù)管理器聚合了所有分片數(shù)據(jù)庫的事務(wù)管理器對(duì)象,然后根據(jù)某個(gè)標(biāo)記來路由到不同的事務(wù)管理器中,這些事務(wù)管理器用來控制各個(gè)分片的數(shù)據(jù)源的事務(wù)。
?
這里的標(biāo)記是什么呢?我們?cè)谡{(diào)用方法時(shí),會(huì)提前把分片的標(biāo)記放進(jìn)ThreadLocal中,然后在ShardingTransactionManager的getTransaction方法被調(diào)用時(shí),取得ThreadLocal中存的標(biāo)記,最后根據(jù)標(biāo)記來判斷使用哪個(gè)分片數(shù)據(jù)庫的事務(wù)管理器對(duì)象。
?
為了通過標(biāo)記路由到不同的事務(wù)管理器,我們?cè)O(shè)計(jì)了一個(gè)專門的ShardingContextHolder類,在該類的內(nèi)部使用了一個(gè)ThreadLocal類來指定分片數(shù)據(jù)庫的關(guān)鍵字,在ShardingTransaction Manager中通過取得這個(gè)標(biāo)記來選擇具體的分片數(shù)據(jù)庫的事務(wù)管理器對(duì)象。因此,這個(gè)類提供了setShard和getShard的方法,setShard用于使用者編程指定使用哪個(gè)分片數(shù)據(jù)庫的事務(wù)管理器,而getShard用于ShardingTransactionManager獲取標(biāo)記并取得分片數(shù)據(jù)庫的事務(wù)管理器對(duì)象。相關(guān)代碼如下:
?
public?class?ShardingContextHolder<T> {private?static?final ThreadLocal shardHolder =?new?ThreadLocal();public?static?<T>?void?setShard(T shard)?{Validate.notNull(shard,?"請(qǐng)指定某個(gè)分片數(shù)據(jù)庫!");shardHolder.set(shard);}public?static?<T>?T?getShard()?{return?(T) shardHolder.get();} }?
有了ShardingContextHolder類后,我們就可以在ShardingTransactionManager中根據(jù)給定的分片配置將事務(wù)操控權(quán)路由到不同分片的數(shù)據(jù)庫的事務(wù)管理器上,實(shí)現(xiàn)很簡(jiǎn)單,如果在ThreadLocal中存儲(chǔ)了某個(gè)分片數(shù)據(jù)庫的事務(wù)管理器的關(guān)鍵字,就使用那個(gè)分片的數(shù)據(jù)庫的事務(wù)管理器:
?
public?class?ShardingTransactionManager?implements?PlatformTransactionManager?{private?Map<Object, PlatformTransactionManager> proxyTransactionManagers =new?HashMap<Object, PlatformTransactionManager>();protected?PlatformTransactionManager?getTargetTransactionManager()?{Object shard = ShardingContextHolder.getShard();Validate.notNull(shard,?"必須指定一個(gè)路由的shard!");return?targetTransactionManagers.get(shard);}public?void?setProxyTransactionManagers(Map<Object, PlatformTransaction Manager> targetTransactionManagers)?{this.targetTransactionManagers = targetTransactionManagers;}public?void?commit(TransactionStatus status)?throws?TransactionException?{getProxyTransactionManager().commit(status);}public?TransactionStatus?getTransaction(TransactionDefinition definition)?throws?TransactionException?{return?getProxyTransactionManager().getTransaction(definition);}public?void?rollback(TransactionStatus status)?throws?TransactionException?{getProxyTransactionManager().rollback(status);} }?
有了這些使用類,我們的可編程事務(wù)路由小框架就實(shí)現(xiàn)了,這樣在某個(gè)具體的服務(wù)開始之前,我們就可以使用如下代碼來控制使用某個(gè)分片的數(shù)據(jù)庫的事務(wù)管理器了:
RoutingContextHolder.setShard("sharding0"); return?userService.create(user);?
3聲明式事務(wù)路由
在上一小節(jié)實(shí)現(xiàn)了可編程事務(wù)路由的小框架,這個(gè)小框架通過讓開發(fā)人員在ThreadLocal中指定數(shù)據(jù)庫分片并編程實(shí)現(xiàn)。大多數(shù)分庫分表框架會(huì)實(shí)現(xiàn)聲明式事務(wù)路由,也就是在實(shí)現(xiàn)的服務(wù)方法上直接聲明事務(wù)的處理注解,注解包含使用哪個(gè)數(shù)據(jù)庫分片的事務(wù)管理器的信息,這樣,開發(fā)人員就可以專注于業(yè)務(wù)邏輯的實(shí)現(xiàn),把事務(wù)處理交給框架來實(shí)現(xiàn)。
?
下面是筆者在實(shí)際的線上項(xiàng)目中實(shí)現(xiàn)的聲明式事務(wù)路由的一個(gè)使用實(shí)例:
?
@TransactionHint(table =?"INVOICE", keyPath =?"0.accountId")public?void?persistInvoice(Invoice invoice)?{// Save invoice to DBthis.createInvoice(invoice);for?(InvoiceItem invoiceItem : invoice.getItems()) {invoiceItem.setInvId(invoice.getId());invoiceItemService.createInvoiceItem(invoice.getAccountId(), invoiceItem);}// Save invoice to cacheinvoiceCacheService.set(invoice.getAccountId(), invoice.getInvPeriodStart().getTime(), invoice.getInvPeriodEnd().getTime(),invoice);// Update last invoice date to AccountAccount account =?new?Account();account.setId(invoice.getAccountId());account.setLstInvDate(invoice.getInvPeriodEnd());accountService.updateAccount(account);}?
在這個(gè)實(shí)例中,我們開發(fā)了一個(gè)持久發(fā)票的服務(wù)方法。持久發(fā)票的服務(wù)方法用來保存發(fā)票信息和發(fā)票項(xiàng)的詳情信息,這里,發(fā)票與發(fā)票項(xiàng)這兩個(gè)領(lǐng)域?qū)ο缶哂懈缸咏Y(jié)構(gòu)關(guān)系。由于在設(shè)計(jì)過程中通過賬戶ID對(duì)這個(gè)父子表進(jìn)行分庫分表,因此在進(jìn)行事務(wù)路由時(shí),也需要通過賬戶ID控制使用哪個(gè)數(shù)據(jù)庫分片的事務(wù)管理器。在這個(gè)實(shí)例中,我們配置了?TransactionHint,TransactionHint的聲明如下:
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public?@interface?TransactionHint {String?table()?default?"";String?keyPath()?default?""; }可以看到,TransactionHint包含了兩個(gè)屬性,第1個(gè)屬性table指定這次操作涉及分片的數(shù)據(jù)庫表,第2個(gè)屬性指定這次操作根據(jù)哪個(gè)參數(shù)的哪個(gè)字段進(jìn)行分片路由。該實(shí)例通過table指定了INVOICE表,并通過keyPath指定了使用第1個(gè)參數(shù)的字段accountId作為路由的關(guān)鍵字。
?
這里的實(shí)現(xiàn)與可編程事務(wù)路由的小框架實(shí)現(xiàn)類似,在方法persistInvoice被調(diào)用時(shí),根據(jù)TransactionHint提供的操作的數(shù)據(jù)庫表名稱,在Spring環(huán)境的配置中找到這個(gè)表的分庫分表的配置信息,例如:一共分了多少個(gè)數(shù)據(jù)庫實(shí)例、數(shù)據(jù)庫和表。
?
下面是在Spring環(huán)境中配置的INVOICE表和INVOICE_ITEM表的具體信息,我們看到它們一共使用了兩個(gè)數(shù)據(jù)庫實(shí)例,每個(gè)實(shí)例有兩個(gè)庫,每個(gè)庫有8個(gè)表,使用水平下標(biāo)策略。配置如下:
?
<bean?name="billingInvSplitTable"?class="com.robert.dbsplit.core.Split Table"init-method="init"><property?name="dbNamePrefix"?value="billing_inv"/><property?name="tableNamePrefix"?value="INVOICE"/><property?name="dbNum"?value="2"/><property?name="tableNum"?value="8"/><property?name="splitStrategyType"?value="HORIZONTAL"/><property?name="splitNodes"><list><ref?bean="splitNode0"/><ref?bean="splitNode1"/></list></property><property?name="readWriteSeparate"?value="true"/></bean><bean?name="billingInvItemSplitTable"?class="com.robert.dbsplit.core.SplitTable"init-method="init"><property?name="dbNamePrefix"?value="billing_inv"/><property?name="tableNamePrefix"?value="INVOICE_ITEM"/><property?name="dbNum"?value="2"/><property?name="tableNum"?value="8"/><property?name="splitStrategyType"?value="HORIZONTAL"/><property?name="splitNodes"><list><ref?bean="splitNode0"/><ref?bean="splitNode1"/></list></property><property?name="readWriteSeparate"?value="true"/></bean>然后,在方法被調(diào)用時(shí)通過AOP進(jìn)行攔截,根據(jù)TransactionHint配置的路由的主鍵信息keyPath ="0.accountId",得知這次根據(jù)第0個(gè)參數(shù)Invoice的accountID字段來路由,根據(jù)Invoice的accountID的值來計(jì)算這次持久發(fā)票表具體涉及哪個(gè)數(shù)據(jù)庫分片,然后把這個(gè)數(shù)據(jù)庫分片的信息保存到ThreadLocal中。具體的實(shí)現(xiàn)代碼如下:
?
SimpleSplitJdbcTemplate simpleSplitJdbcTemplate =(SimpleSplitJdbcTemplate) ReflectionUtil.getFieldValue(field SimpleSplitJdbcTemplate, invocation.getThis());Method method = invocation.getMethod(); // Convert to th method of implementation class method = targetClass.getMethod(method.getName(), method.getParameter Types());TransactionHint[] transactionHints = method.getAnnotationsByType (TransactionHint.class); if?(transactionHints ==?null?|| transactionHints.length <?1)throw?new?IllegalArgumentException("The method "?+ method +?" includes illegal transaction hint."); TransactionHint transactionHint = transactionHints[0];String?tableName = transactionHint.table(); String?keyPath = transactionHint.keyPath();String[] parts = keyPath.split("\\."); int paramIndex = Integer.valueOf(parts[0]);Object[] params = invocation.getArguments(); Object?splitKey = params[paramIndex];if?(parts.length >?1) {String[] paths = Arrays.copyOfRange(parts,?1, parts.length);splitKey = ReflectionUtil.getFieldValueByPath(splitKey, paths); }SplitNode splitNode = simpleSplitJdbcTemplate.decideSplitNode(tableName, splitKey);ThreadContextHolder.INST.setContext(splitNode);ThreadContextHolder是一個(gè)單例的對(duì)象,在該對(duì)象里封裝了一個(gè)ThreadLocal,用來存儲(chǔ)某個(gè)方法在某個(gè)線程下關(guān)聯(lián)的分片信息:
?
public?class?ThreadContextHolder<T> {public?static?final ThreadContextHolder<SplitNode> INST =?new?ThreadContextHolder<SplitNode>();private?ThreadLocal<T> contextHolder =?new?ThreadLocal<T>();public?T?getContext()?{return?contextHolder.get();}public?void?setContext(T context)?{contextHolder.set(context);} }?
接下來與可編程式事務(wù)路由類似,實(shí)現(xiàn)一個(gè)定制化的事務(wù)管理器,在獲取目標(biāo)事務(wù)管理器時(shí),通過我們?cè)赥hreadLocal中保存的數(shù)據(jù)庫分片信息,獲得這個(gè)分片數(shù)據(jù)庫的事務(wù)管理器,然后返回:
?
public?class?RoutingTransactionManager?implements?PlatformTransactionManager?{protected?PlatformTransactionManager?getTargetTransactionManager()?{SplitNode splitNode = ThreadContextHolder.INST.getContext();return?splitNode.getPlatformTransactionManager();}public?void?commit(TransactionStatus status)?throws?TransactionException?{getTargetTransactionManager().commit(status);}public?TransactionStatus?getTransaction(TransactionDefinition definition)?throws?TransactionException?{return?getTargetTransactionManager().getTransaction(definition);}public?void?rollback(TransactionStatus status)?throws?TransactionException?{getTargetTransactionManager().rollback(status);} }?
本節(jié)介紹的開源數(shù)據(jù)庫分庫分表框架dbsplit是一個(gè)分庫分表的簡(jiǎn)單示例實(shí)現(xiàn),在筆者所工作的公司內(nèi)部有內(nèi)部版本,在內(nèi)部版本中實(shí)現(xiàn)了聲明式事務(wù)路由,但是這部分功能并沒有開源到dbsplit項(xiàng)目,原因是有些與業(yè)務(wù)結(jié)合的邏輯無法分離。如果感興趣,則可以加入我們的開源項(xiàng)目開發(fā)中。
?
?
總結(jié)
以上是生活随笔為你收集整理的分库分表的事务处理机制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: dnf 电脑配置(dnf配置电脑配置)
- 下一篇: java各种集合的线程安全