【原】ActiveMq實現分佈式事務一致性

  前言:關於分佈式事務話題一直是很有爭議的話題,在本文中經過ActiveMq 實現分佈式事務作一個簡單的demo;同時也讓本身能在實踐中能夠獲取經驗和對分佈式事務本身的一些思考。

     

      1.本地事務

                咱們一般只需藉助開發平臺中特有數據訪問技術和框架(例如Spring、JDBC、ADO.NET),結合關係型數據庫自帶的事務管理機制來實現事務性的需求。例如A給B轉帳100元併發送100代金券,無論是服務器掛掉仍是轉帳失敗拋出異常,咱們最終都要保證這個流程要麼都成功要麼都失敗,不然會出現數據異常。java

   2.分佈式事務

                餘額表和代金券表分佈在不一樣的節點的數據庫,轉帳和發放代金券是不一樣的應用,它們之間通訊可能經過rpc,httpclient,mq;假設這時候A服務給B轉帳成功,可是發放代金券失敗,咱們應該如何處理呢?筆者在如今公司項目裏就有不少這樣的問題,咱們是和第三方常常有數據交互,那麼調用第三方的接口進行劃撥操做,有可能在第三方劃撥成功可是消息丟失(網絡異常、服務器掛掉、某些人新加的不合理代碼致使異常回滾等等)web

   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 }

       消息表主要用處是:  

  •   假如咱們消息投遞到消息中間件後,消費者那邊出現異常,雖然信息已經被消費者消費了,但因爲代碼或宕機致使消費端數據事務沒有成功提交,若是沒有消息表,咱們將會丟失這一條數據。有了消息表後咱們能夠查詢到有哪些是屬於未成功派發的數據,這時候能夠經過輪詢或者是其餘方式再次把這批未成功消費的數據從新派發出去。spring

  • 根據上述代碼及註釋,咱們來分析下可能的狀況:sql

    1. 操做數據庫成功,向MQ中投遞消息也成功,皆大歡喜。數據庫

    2. 操做數據庫失敗,不會向MQ中投遞消息了。服務器

    3. 操做數據庫成功,可是向MQ中投遞消息時失敗,向外拋出了異常,剛剛執行的更新數據庫的操做將被回滾。網絡

    4. 操做數據庫成功,投遞MQ消息成功,消費異常,數據未更新,經過掃描消息表再次把數據取出進行消費。

    從上面分析的幾種狀況來看,貌似問題都不大的。那麼咱們來分析下消費者端面臨的問題:session

    1. 消息出列後,消費者對應的業務操做要執行成功。若是業務執行失敗,消息不能失效或者丟失。須要保證消息與業務操做一致。併發

    2. 儘可能避免消息重複消費,消費前先查詢一下是否消費成功,必定要有一個標識標明,若是重複消費,也不能所以影響業務結果,保證冪等性。mvc

 

           時序圖:

 

相關文章
相關標籤/搜索