分佈式事務,一言以蔽之,就是不一樣物理服務器上數據庫執行DML SQL語句的一致性問題。 下面的代碼就是一個典型的分佈式事務場景html
1.消息隊列模式:java
事務發起方不用回滾的狀況 (下訂單,減庫存)mysql
2.TCC模式:android
跨行轉帳、支付寶轉到餘額寶sql
3.2PC/3PC:數據庫
2pc 3pc都是XA協議的兩種實現服務器
https://www.javazhiyin.com/31853.htmlapp
建立DataSource,使用阿里的德魯伊
DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl(mysqlSettingParam.getUrl()); dataSource.setUsername(mysqlSettingParam.getUsername()); dataSource.setPassword(mysqlSettingParam.getPassword()); dataSource.setInitialSize(mysqlSettingParam.getInitialSize()); dataSource.setMaxActive(mysqlSettingParam.getMaxActive()); dataSource.setMinIdle(mysqlSettingParam.getMinIdle()); dataSource.setMaxWait(mysqlSettingParam.getMaxWait()); dataSource.setTestWhileIdle(mysqlSettingParam.getTestWhileIdle()); dataSource.setValidationQuery(mysqlSettingParam.getValidQuery());
txManager = new DataSourceTransactionManager(dataSource); DefaultTransactionDefinition def = new DefaultTransactionDefinition(); def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); TransactionStatus status = txManager.getTransaction(def); try { //操做數據庫 adChannelService.insert(adChannelInfo); //進行rpc http調用,模擬分佈式事務場景 //這個http接口實現裏面也有insert、update數據庫邏輯 String str = restTemplate.getForObject("http://127.0.0.1:9109/save", String.class); System.out.println("str="+str); //故意製造異常,也多是RPC請求發生了TimeOutException int i = 1/0; txManager.commit(status); //提交status中綁定的事務 } catch (RuntimeException e) { txManager.rollback(status); //回滾 e.printStackTrace(); }
JTA是Java Transaction API簡稱 ,即Java事務管理器,主要管理本地事務。本地事務是隻同一個數據庫源中,屢次數據庫鏈接之間的事務,使得這多個數據表操做遵循ACID。
若是想讓JTA管理多臺數據庫操做的分佈式事務,須要XA支持,Open Group設計的X / Open分佈式事務處理定義了一種標準通訊架構。容許多個應用程序共享多個資源管理器提供的資源,並容許其工做協調到全局事務中。該XA接口使資源管理者參加事務,執行2PC(兩個階段提交),並在發生故障以後恢復。
2PC(兩段提交)含義:首先, 事務協調員向每一個服務器詢問一遍,要求每一個數據庫都進行precommit的操做和是否可能實現 commit. 若是全部數據庫都贊成commit ,第二段開始。第二段:事務協調員要求每一個數據庫commit數據. 若是任何數據庫否決commit, 那麼全部數據庫將被要求回滾。當事務對某個數據庫鎖定時,這個數據庫或列就不可用,或者響應時間很慢。犧牲可用性了。
JTA + XA可以實現分佈式事務2PC 。
Spring提供了JTA介入方式,可是沒有提供JTA實現,目前JTA實現: Java Open Transaction Manager (JOTM), JBoss TS, Bitronix Transaction Manager (BTM), 和 Atomikos。
byteTCC
在分佈式時代,分庫分表是很常見的,微服務系統中,各個系統一般使用獨立的數據庫,因此,事務很難靠數據庫自己保證,只能靠業務系統來解決。
例如支付寶中的餘額寶、花唄,具體不清楚,但猜想應該就是2個服務,不是同一個數據庫,咱們還花唄的時候一般都是從餘額寶中扣除的,這就是分佈式事務,一個系統中扣減錢,一個系統中增長錢。
下面咱們分析下最終一致性的實現方案,最終一致性一般都是使用消息中間件來實現的,系統結構以下:
用戶向系統A發起轉帳請求,A先在本身的數據庫中扣錢,而後經過消息中間件告訴B應該加錢,B收到後在本身的數據庫中加錢。
這裏有個關鍵問題,A更新數據庫和給消息中間件發消息是2個操做,以下兩個場景怎麼處理:
先更新數據庫,成功了,但發送消息失敗了,重發屢次仍是失敗
先發消息,成功了,但數據庫更新失敗,消息撤不回來了
都是由於這2個操做不是原子的,發作誰都有問題。
那看下這樣作是否能夠,就是把更新數據庫和給消息中間件發消息放到一個事務中,這樣不就原子了嗎?
有問題,例如:
若是消息發送失敗,具體問題出在哪兒?是消息中間件根本就沒收到消息,仍是收到消息後response時出錯了?若是是根本沒收到還好一點,若是是收到了但響應失敗就麻煩了,致使A數據庫回滾,沒有扣錢,但B收到消息了,加錢了。
若是發消息時網絡延遲很高怎麼辦,數據庫事務一直被拖着,性能差,風險高。
因此,放入一個事務中這種方法是不可取的。
爲了保證原子性,能夠變通一下,添加一個消息表,A不直接往消息中間件中發消息,而是把消息寫入消息表,而後經過一個後臺程序不斷的把消息寫入消息中間件。
這個後臺程序源源不斷的把消息表中的消息發到消息中間件,若是失敗就重試,能夠保證:
消息不會丟失
順序不亂
但會有消息重複的狀況,由於消息發送失敗多是寫入失敗,也多是寫入成功但響應失敗,因此消息可能會重複,這個問題須要系統B來處理。
系統B須要考慮2個問題:
消息丟失
B從消息中間件中拿到消息,還沒處理完就宕機了,這條消息怎麼辦?
須要經過ACK機制處理,消費成功的發送ACK,對於沒有ACK的消息,消息中間件會再次推送。
消息重複
ACK機制也存在消息重複的狀況,好比B已經處理完一條消息,發ACK時失敗了,那麼這條消息就還會被推過來。
還有就是上面說的後臺程序發消息時可能重複。
對於重複消息問題,能夠加一個判重表,記錄處理成功的消息,每次收到消息時,先經過判重表判斷一下,若是重複了就不處理,實現冪等性。
這樣,總體結構就變爲:
以上就是經過最終一致性解決分佈式事務問題的基本思路,A 保證消息不丟,B 保證消息不漏、冪等。