咱們一般只需藉助開發平臺中特有數據訪問技術和框架(例如Spring、JDBC、ADO.NET),結合關係型數據庫自帶的事務管理機制來實現事務性的需求。例如A給B轉帳100元併發送100代金券,無論是服務器掛掉仍是轉帳失敗拋出異常,咱們最終都要保證這個流程要麼都成功要麼都失敗,不然會出現數據異常。java
餘額表和代金券表分佈在不一樣的節點的數據庫,轉帳和發放代金券是不一樣的應用,它們之間通訊可能經過rpc,httpclient,mq;假設這時候A服務給B轉帳成功,可是發放代金券失敗,咱們應該如何處理呢?筆者在如今公司項目裏就有不少這樣的問題,咱們是和第三方常常有數據交互,那麼調用第三方的接口進行劃撥操做,有可能在第三方劃撥成功可是消息丟失(網絡異常、服務器掛掉、某些人新加的不合理代碼致使異常回滾等等)web
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 }
假如咱們消息投遞到消息中間件後,消費者那邊出現異常,雖然信息已經被消費者消費了,但因爲代碼或宕機致使消費端數據事務沒有成功提交,若是沒有消息表,咱們將會丟失這一條數據。有了消息表後咱們能夠查詢到有哪些是屬於未成功派發的數據,這時候能夠經過輪詢或者是其餘方式再次把這批未成功消費的數據從新派發出去。spring
根據上述代碼及註釋,咱們來分析下可能的狀況:sql
操做數據庫成功,向MQ中投遞消息也成功,皆大歡喜。數據庫
操做數據庫失敗,不會向MQ中投遞消息了。服務器
操做數據庫成功,可是向MQ中投遞消息時失敗,向外拋出了異常,剛剛執行的更新數據庫的操做將被回滾。網絡
從上面分析的幾種狀況來看,貌似問題都不大的。那麼咱們來分析下消費者端面臨的問題:session
消息出列後,消費者對應的業務操做要執行成功。若是業務執行失敗,消息不能失效或者丟失。須要保證消息與業務操做一致。併發
儘可能避免消息重複消費,消費前先查詢一下是否消費成功,必定要有一個標識標明,若是重複消費,也不能所以影響業務結果,保證冪等性。mvc