基于RocketMQ解决分布式事务

  • 什么是分布式事务 
     
     

    在分布式系统中,因为跨服务调用接口,存在多个不同的事务,每个事务都互不影响。就存在分布式事务的问题 
     
  • 分布式事务解决思路
     
  1. 使用强一致性  
     表示a系统更新的数据,b系统必须同时更新到。
     
  2. 使用最终一致性
     表示a系统更新的数据,b系统可以暂时不更新,但是最终还是要同步。
     本文采用的时最终一致性解决分布式事务。
  • RocketMQ事务消息机制
  1.  事务消息 也称半消息 
     生产者发送一条事务消息,该消息不可以被消息。
     待生产者发送commit或rollback指令后,rocketMQ会将消息删除、或投递消费者
  2. 代码思路 
    // 生产者发送事务消息  该消息不可被消费
    rocketMQTemplate.sendMessageInTransaction("tx_groups_order", "my_topic", message, null);
    
    // COMMIT:即生产者通知Rocket该消息可以消费
    RocketMQLocalTransactionState.COMMIT;
    // ROLLBACK:即生产者通知Rocket将该消息删除
    RocketMQLocalTransactionState.ROLLBACK;
    // UNKNOWN:即生产者通知Rocket继续查询该消息的状态
    RocketMQLocalTransactionState.UNKNOWN;
  •  采用RocketMQ解决分布式事务思路
     
     
  1.   实现思路
     1.  发送事务消息,该消息不可消费
     2.  返回事务消息是否发送成功 
     3.  执行本地事务,将订单消息保存到数据库
     4. 将本地事务结果返回给Rocket
     5.  如果本地事务没有将结果返回给Rocket,则Rocket定时检查
     6. 如果本地事务提交commit,则Rocket则将该消息设置为可以消费
    思路:  保证订单要先保存到数据库且成功,消费者才可以拿到消息
  •   基于rocket事务消息代码实现
     
    生产者
  1.   创建订单项目
     在application.properties配置如下:
    spring.datasource.url=jdbc:mysql://127.0.0.1:3306/tbl_order?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC 
    spring.datasource.username=root
    spring.datasource.password=123456
    spring.datasource.driver-class-name=com.mysql.jdbc.Driver
    server.port=2018
    // rocket的nameserver地址
    rocketmq.name-server=192.168.2.115:9876
    rocketmq.producer.group=groups_order
    
    手动事务工具类
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.datasource.DataSourceTransactionManager;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.TransactionStatus;
    import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
    
    // 执行手动事务
    @Service
    public class TransationalUtils {
        @Autowired
        public DataSourceTransactionManager transactionManager;
    
        public TransactionStatus begin() {
            TransactionStatus transaction = transactionManager.getTransaction(new DefaultTransactionAttribute());
            return transaction;
        }
    
        public void commit(TransactionStatus transaction) {
            transactionManager.commit(transaction);
    
        }
    
        public void rollback(TransactionStatus transaction) {
            transactionManager.rollback(transaction);
        }
    
    }

     

  2.   生产者发送事务消息
    import com.alibaba.fastjson.JSONObject;
    import com.pitch.entity.Order;
    import com.pitch.mapper.OrderMapper;
    import org.apache.rocketmq.spring.core.RocketMQTemplate;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.messaging.Message;
    import org.springframework.messaging.support.MessageBuilder;
    import org.springframework.stereotype.Service;
    import java.util.List;
    
    /**
     * @author xiaobo
     * @Description OrderService
     * @createTime 2020-03-25 20:08
     */
    @Service
    public class OrderService {
    
        @Autowired
        private OrderMapper orderMapper;
        @Autowired
        private RocketMQTemplate rocketMQTemplate;
    
        public List<Order> findAll(){
            return orderMapper.findAll();
        }
    
        public void saveOrder(Order order) throws Exception{
            // 封装订单消息
            String msg = JSONObject.toJSONString(order);
            MessageBuilder<String> stringMessageBuilder = MessageBuilder.withPayload(msg);
            stringMessageBuilder.setHeader("msg", msg);
            Message message = stringMessageBuilder.build();
            // 发送事务消息   且该消息不允许消费    tx_groups_order: 指定版事务消息组
            rocketMQTemplate.sendMessageInTransaction("tx_groups_order",
                    "my_topic", message, null);
        }
    }
    将订单消息以事务消息先发送给Rocket,此时该消费未保存到数据库。
  3.   生产者本地事务方法
    import com.alibaba.fastjson.JSONObject;
    import com.pitch.entity.Order;
    import com.pitch.mapper.OrderMapper;
    import com.pitch.utils.utils.TransationalUtils;
    import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener;
    import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener;
    import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.messaging.Message;
    import org.springframework.messaging.MessageHeaders;
    import org.springframework.stereotype.Component;
    import org.springframework.transaction.TransactionStatus;
    
    /**
     * @author xiaobo
     * @Description ProducerListener
     * @createTime 2020-03-25 21:55
     */
    @Component
    @RocketMQTransactionListener(txProducerGroup = "tx_groups_order") // tx_groups_order:对应发送时的事务消息组
    public class ProducerOrderListener implements RocketMQLocalTransactionListener {
    
        @Autowired
        private OrderMapper orderMapper;
        @Autowired
        private TransationalUtils transationalUtils;
        // 执行本地事务
        @Override
        public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) {
            // 获取订单的半消息
            MessageHeaders headers = message.getHeaders();
            Object object = headers.get("msg");
            Order order = JSONObject.parseObject((String)object, Order.class);
            TransactionStatus begin = null;
            try {
                // 开启事务
                begin = transationalUtils.begin();
                // 添加订单
                int result = orderMapper.insert(order);
                // 提交事务
                transationalUtils.commit(begin);
                // 通知broker 将半消息设置为可以消费
                return null;
            } catch (Exception e) {
                if (begin != null) {
                    // 如果出现异常 ,则回滚事务
                    transationalUtils.rollback(begin);
                    // // 通知broker 将消息删除
                    return RocketMQLocalTransactionState.ROLLBACK;
                }
            }
            return null;
        }
        // 检查事务是否提交   --当本地事务没有返回结果给rocket,rocket就会定时通过该方法检查
        @Override
        public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
            // 获取订单的半消息
            MessageHeaders headers = message.getHeaders();
            Object object = headers.get("msg");
            if (object == null) {
                // 如果不存在, 则通知broker删除消息
                return RocketMQLocalTransactionState.ROLLBACK;
            }
            // 获取消息中订单id
            Order order = JSONObject.parseObject((String) object, Order.class);
            Long id = order.getId();
            // 通过id查询订单是否下单成功
            Order result = orderMapper.findById(id);
            if (result == null) {
                // 如果为null, 返回UNKNOWN, broker会再次重试(默认15次)
                return RocketMQLocalTransactionState.UNKNOWN;
            }
            // 通过broker, 该半消息可以消费
            return RocketMQLocalTransactionState.COMMIT;
        }
    }
    executeLocalTransaction():  
          通过本地事务方法,将订单保存到数据库。
          如保存成功则通知RocketMQ将改消息消费,否则删除该消息。 
     
    checkLocalTransaction 
          如果RocketMQ没有收到本地事务的返回结果,则RocketMQ通过该方法主动查询判断。

     消费者

  1.   创建派单项目
     application.propreties配置如下: 
     
    spring.datasource.url=jdbc:mysql://127.0.0.1:3306/tbl_send?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC 
    spring.datasource.username=root
    spring.datasource.password=123456
    spring.datasource.driver-class-name=com.mysql.jdbc.Driver
    server.port=2019
    rocketmq.name-server=192.168.2.115:9876
    rocketmq.producer.group=groups_send
  2.   消费者代码
     
    import com.alibaba.fastjson.JSONObject;
    import com.pitch.service.SendService;
    import org.apache.rocketmq.common.message.MessageExt;
    import org.apache.rocketmq.spring.annotation.ConsumeMode;
    import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
    import org.apache.rocketmq.spring.core.RocketMQListener;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    /**
     * @author xiaobo
     * @Description UserConsumer
     * @createTime 2020-03-24 19:59
     */
    @Component
    // topic:主题   pitch_groups:组    ConsumeMode.ORDERLY: 一个队列对应一个线程  consumeThreadMax:消费者最大线程为1
    @RocketMQMessageListener(topic = "my_topic", consumerGroup = "groups_order"
            , consumeMode = ConsumeMode.ORDERLY, consumeThreadMax = 1)
    public class SendConsumer implements RocketMQListener<MessageExt> {
        @Autowired
        private SendService sendService;
        @Override
        public void onMessage(MessageExt m) {
            // 获取订单消息
            String result = new String(m.getBody());
            JSONObject json = JSONObject.parseObject(result);
            Long id = json.getLong("id");
            // 开始派单
            sendService.saveSend(id);
            System.out.println("用户:"+Thread.currentThread().getName()+"开始派单,"+"--订单消息:"+result);
        }
    
    }
  3. 测试  
     

     
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值