mq實現dubbo分散式事務
1.本地事務 我們通常只需藉助開發平臺中特有資料訪問技術和框架(例如Spring、JDBC、ADO.NET),結合關係型資料庫自帶的事務管理機制來實現事務性的需求。例如A給B轉賬100元併發送100代金券,不管是伺服器掛掉還是轉賬失敗丟擲異常,我們最終都要保證這個流程要麼都成功要麼都失敗,否則會出現資料異常。
2.分散式事務 餘額表和代金券表分佈在不同的節點的資料庫,轉賬和發放代金券是不同的應用,它們之間通訊可能通過rpc,httpclient,mq;假設這時候A服務給B轉賬成功,但是發放代金券失敗,我們應該如何處理呢?筆者在現在公司專案裡就有很多這樣的問題,我們是和第三方經常有資料互動,那麼呼叫第三方的介面進行劃撥操作,有可能在第三方劃撥成功但是訊息丟失(網路異常、伺服器掛掉、某些人新加的不合理程式碼導致異常回滾等等)
3.使用訊息佇列ActiveMq實現事務一致性 以下 demo簡單模擬使用者註冊後發放代金券這一過程;流程首先是使用者註冊成功後推送使用者資訊到Active mq,代金券應用中也配置好了Active Mq,但是它是充當消費者的角色,實現代金券訊息監聽,當監聽到訊息後會拉取Active Mq的訊息發執行派發金券動作; 其中使用者註冊是一個應用,發放代金券是另外一個應用,它們之間是通過activemq實現訊息收發。 首先建立2個maven專案,分別叫account和voucher,在這裡我用的是springmvc+jdbc作為專案骨架。 在account專案中我新首先建了一個UserController.java作為註冊的控制層,並提供註冊的方法,如下程式碼示例,其中注意的是增加了一張訊息表,關於為什麼需要訊息表下面會詳細解答。 複製程式碼
1 package com.zdd.mvc; 2 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.jms.core.JmsTemplate; 5 import org.springframework.jms.core.MessageCreator; 6 import org.springframework.stereotype.Controller; 7 import org.springframework.ui.ModelMap; 8 import org.springframework.web.bind.annotation.RequestMapping; 9 import org.springframework.web.bind.annotation.RequestMethod; 10 import org.springframework.web.bind.annotation.ResponseBody; 11 import utils.ActiveMQutil; 12 import utils.JdbcUtil; 13 import utils.Result; 14 15 import javax.jms.JMSException; 16 import javax.jms.Message; 17 import javax.jms.Session; 18 19 /** 20 * Created by dada on 2017/8/25. 21 */ 22 @Controller 23 @RequestMapping("/register") 24 public class UserAccountController { 25 26 @Autowired 27 private JmsTemplate jmsTemplate; 28 29 30 @RequestMapping(method = RequestMethod.GET) 31 public String register() { 32 return "register"; 33 } 34 35 36 @RequestMapping(method = RequestMethod.POST,value = "/doReg") 37 @ResponseBody 38 public Result doReg(final String phone) { 39 JdbcUtil jdbcUtil = null; 40 try{ 41 jdbcUtil = new JdbcUtil(); 42 jdbcUtil.getConnection(); 43 44 jdbcUtil.setAutoCommit(false); //往賬戶表新增一條資料 45 String sql = "insert into account(phone) values ('"+phone+"')"; 46 int row = jdbcUtil.insert(sql); 47 if(row == 1){ 48 //插入到訊息記錄表 49 sql = "insert into message(phone,status) values ('"+phone+"',0)"; 50 int m_row = jdbcUtil.insert(sql); 51 if(m_row == 1){ 52 //成功後傳送佇列 53 jmsTemplate.send("voucher_message", new MessageCreator() { 54 @Override 55 public Message createMessage(Session session) throws JMSException { 56 return session.createTextMessage(phone); 57 } 58 }); 59 } 60 } 61 jdbcUtil.Commit(); 62 63 }catch (RuntimeException e){ 64 e.printStackTrace(); 65 jdbcUtil.rollback();//出現異常事務回滾 66 }finally { 67 if(null != jdbcUtil){ 68 jdbcUtil.releaseConn(); 69 } 70 } 71 Result result = new Result(); 72 return result; 73 } 74 75 }
複製程式碼 訊息表主要用處是: 假如我們訊息投遞到訊息中介軟體後,消費者那邊出現異常,雖然資訊已經被消費者消費了,但由於程式碼或宕機導致消費端資料事務沒有成功提交,如果沒有訊息表,我們將會丟失這一條資料。有了訊息表後我們可以查詢到有哪些是屬於未成功派發的資料,這時候可以通過輪詢或者是其他方式再次把這批未成功消費的資料重新派發出去。
根據上述程式碼及註釋,我們來分析下可能的情況:
操作資料庫成功,向MQ中投遞訊息也成功,皆大歡喜。
操作資料庫失敗,不會向MQ中投遞訊息了。
操作資料庫成功,但是向MQ中投遞訊息時失敗,向外丟擲了異常,剛剛執行的更新資料庫的操作將被回滾。
操作資料庫成功,投遞MQ訊息成功,消費異常,資料未更新,通過掃描訊息表再次把資料取出進行消費。 從上面分析的幾種情況來看,貌似問題都不大的。那麼我們來分析下消費者端面臨的問題:
訊息出列後,消費者對應的業務操作要執行成功。如果業務執行失敗,訊息不能失效或者丟失。需要保證訊息與業務操作一致。
儘量避免訊息重複消費,消費前先查詢一下是否消費成功,一定要有一個標識標明,如果重複消費,也不能因此影響業務結果,保證冪等性。
時序圖: