分布式事务(4)---RocketMQ实现分布式事务项目
RocketMQ实现分布式事务
有关RocketMQ实现分布式事务前面写了一篇博客
下面就这个项目做个整体简单介绍,并在文字最下方附上项目Github地址。
一、项目概述
1、技术架构
项目总体技术选型
SpringCloud(Finchley.RELEASE) + SpringBoot2.0.4 + Maven3.5.4 + RocketMQ4.3 +MySQL + lombok(插件)
有关SpringCloud主要用到以下四个组建
Eureka Server +config-server(配置中心)+ Eureka Client + Feign(服务间调用)
配置中心是用MySQL存储数据。
2、项目整体结构
config-service  # 配置中心
eureka          # 注册中心
service-order   #订单微服务
service-produce #商品微服务
各服务的启动顺序就安装上面的顺序启动。
大致流程
启动后,配置中心、订单微服务、商品微服务都会将信息注册到注册中心。
如果访问:localhost:7001(注册中心地址),以上服务都出现说明启动成功。

3、分布式服务流程
用户在订单微服务下单后,会去回调商品微服务去减库存。这个过程需要事务的一致性。

4、测试流程
页面输入:
http://localhost:9001/api/v1/order/save?userId=1&productId=1&total=4
订单微服务执行情况(订单服务事务执行成功)

商品微服务执行情况(商品服务事务执行成功)

当然你也可以通过修改参数来模拟分布式事务出现的各种情况。
二、MQ中生产者核心代码
这里展示下,生产者发送消息核心代码。
@Slf4j
@Component
public class TransactionProducer {
    /**
     * 需要自定义事务监听器 用于 事务的二次确认 和 事务回查
     */
    private TransactionListener transactionListener ;
    /**
     * 这里的生产者和之前的不一样
     */
    private TransactionMQProducer producer = null;
    /**
     * 官方建议自定义线程 给线程取自定义名称 发现问题更好排查
     */
    private ExecutorService executorService = new ThreadPoolExecutor(2, 5, 100, TimeUnit.SECONDS,
            new ArrayBlockingQueue<Runnable>(2000), new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            thread.setName("client-transaction-msg-check-thread");
            return thread;
        }
    });
    public TransactionProducer(@Autowired Jms jms, @Autowired ProduceOrderService produceOrderService) {
        transactionListener = new TransactionListenerImpl(produceOrderService);
        // 初始化 事务生产者
        producer = new TransactionMQProducer(jms.getOrderTopic());
        // 添加服务器地址
        producer.setNamesrvAddr(jms.getNameServer());
        // 添加事务监听器
        producer.setTransactionListener(transactionListener);
        // 添加自定义线程池
        producer.setExecutorService(executorService);
        start();
    }
    public TransactionMQProducer getProducer() {
        return this.producer;
    }
    /**
     * 对象在使用之前必须要调用一次,只能初始化一次
     */
    public void start() {
        try {
            this.producer.start();
        } catch (MQClientException e) {
            e.printStackTrace();
        }
    }
    /**
     * 一般在应用上下文,使用上下文监听器,进行关闭
     */
    public void shutdown() {
        this.producer.shutdown();
    }
}
/**
 * @author xub
 * @Description: 自定义事务监听器
 * @date 2019/7/15 下午12:20
 */
@Slf4j
class TransactionListenerImpl implements TransactionListener {
    @Autowired
    private ProduceOrderService produceOrderService ;
    public TransactionListenerImpl( ProduceOrderService produceOrderService) {
        this.produceOrderService = produceOrderService;
    }
    @Override
    public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        log.info("=========本地事务开始执行=============");
        String message = new String(msg.getBody());
        JSONObject jsonObject = JSONObject.parseObject(message);
        Integer productId = jsonObject.getInteger("productId");
        Integer total = jsonObject.getInteger("total");
        int userId = Integer.parseInt(arg.toString());
        //模拟执行本地事务begin=======
        /**
         * 本地事务执行会有三种可能
         * 1、commit 成功
         * 2、Rollback 失败
         * 3、网络等原因服务宕机收不到返回结果
         */
        log.info("本地事务执行参数,用户id={},商品ID={},销售库存={}",userId,productId,total);
        int result = produceOrderService.save(userId, productId, total);
        //模拟执行本地事务end========
        //TODO 实际开发下面不需要我们手动返回,而是根据本地事务执行结果自动返回
        //1、二次确认消息,然后消费者可以消费
        if (result == 0) {
            return LocalTransactionState.COMMIT_MESSAGE;
        }
        //2、回滚消息,Broker端会删除半消息
        if (result == 1) {
            return LocalTransactionState.ROLLBACK_MESSAGE;
        }
        //3、Broker端会进行回查消息
        if (result == 2) {
            return LocalTransactionState.UNKNOW;
        }
        return LocalTransactionState.COMMIT_MESSAGE;
    }
    /**
     * 只有上面接口返回 LocalTransactionState.UNKNOW 才会调用查接口被调用
     *
     * @param msg 消息
     * @return
     */
    @Override
    public LocalTransactionState checkLocalTransaction(MessageExt msg) {
        log.info("==========回查接口=========");
        String key = msg.getKeys();
        //TODO 1、必须根据key先去检查本地事务消息是否完成。
        /**
         * 因为有种情况就是:上面本地事务执行成功了,但是return LocalTransactionState.COMMIT_MESSAG的时候
         * 服务挂了,那么最终 Brock还未收到消息的二次确定,还是个半消息 ,所以当重新启动的时候还是回调这个回调接口。
         * 如果不先查询上面本地事务的执行情况 直接在执行本地事务,那么就相当于成功执行了两次本地事务了。
         */
        // TODO 2、这里返回要么commit 要么rollback。没有必要在返回 UNKNOW
        return LocalTransactionState.COMMIT_MESSAGE;
    }
}
三、MQ消费端核心代码
这里展示下,消费端消费消息核心代码。消费端和普通消费一样。
@Slf4j
@Component
public class OrderConsumer {
    private DefaultMQPushConsumer consumer;
    private String consumerGroup = "produce_consumer_group";
    public OrderConsumer(@Autowired Jms jms,@Autowired ProduceService produceService) throws MQClientException {
        //设置消费组
        consumer = new DefaultMQPushConsumer(consumerGroup);
        // 添加服务器地址
        consumer.setNamesrvAddr(jms.getNameServer());
        // 添加订阅号
        consumer.subscribe(jms.getOrderTopic(), "*");
        // 监听消息
        consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
            MessageExt msg = msgs.get(0);
            String message = new String(msgs.get(0).getBody());
            JSONObject jsonObject = JSONObject.parseObject(message);
            Integer productId = jsonObject.getInteger("productId");
            Integer total = jsonObject.getInteger("total");
            String key = msg.getKeys();
            log.info("消费端消费消息,商品ID={},销售数量={}",productId,total);
            try {
                produceService.updateStore(productId, total, key);
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            } catch (Exception e) {
                log.info("消费失败,进行重试,重试到一定次数 那么将该条记录记录到数据库中,进行如果处理");
                e.printStackTrace();
                return ConsumeConcurrentlyStatus.RECONSUME_LATER;
            }
        });
        consumer.start();
        System.out.println("consumer start ...");
    }
}
至于完整的项目地址见GitHub。
如果对您能有帮助,就给个星星吧,哈哈!
GitHubd地址 https://github.com/yudiandemingzi/spring-cloud-rocketmq-transaction.git
晚安!
只要自己变优秀了,其他的事情才会跟着好起来(上将8)
												
											分布式事务(4)---RocketMQ实现分布式事务项目的更多相关文章
- 分布式事务(3)---RocketMQ实现分布式事务原理
		
分布式事务(3)-RocketMQ实现分布式事务原理 之前讲过有关分布式事务2PC.3PC.TCC的理论知识,博客地址: 1.分布式事务(1)---2PC和3PC原理 2.分布式事务(2)---TCC ...
 - 【RocketMQ】【分布式事务】使用RocketMQ实现分布式事务
		
参考地址:https://blog.csdn.net/zyw23zyw23/article/details/79070044 视频地址:https://v.youku.com/v_show/id_XO ...
 - SpringCloud+RocketMQ实现分布式事务
		
随着互联网公司的微服务越来越多,分布式事务已经成为了我们的经常使用的.所以我们来一步一步的实现基于RocketMQ的分布式事务.接下来,我们将要做的主题写出来. RocketMQ的分布式事务结构和说明 ...
 - RocketMQ实现分布式事务
		
相关文章:http://www.uml.org.cn/zjjs/201810091.asp(深入理解分布式事务,高并发下分布式事务的解决方案) 三种分布式事务: 1.基于XA协议的两阶段提交 2.消息 ...
 - Redis的Java客户端Jedis的八种调用方式(事务、管道、分布式)介绍
		
jedis是一个著名的key-value存储系统,而作为其官方推荐的java版客户端jedis也非常强大和稳定,支持事务.管道及有jedis自身实现的分布式. 在这里对jedis关于事务.管道和分布式 ...
 - 分布式事务(二)Java事务API(JTA)规范
		
一.引子 既然出现了分布式场景(DTP模型), 大java也及时制定出一套规范来给各大应用服务器.数据库/mq等厂商使用,以方便管理互通--->JTA闪亮登场.JTA(Java Transact ...
 - Zookeeper系列二:分布式架构详解、分布式技术详解、分布式事务
		
一.分布式架构详解 1.分布式发展历程 1.1 单点集中式 特点:App.DB.FileServer都部署在一台机器上.并且访问请求量较少 1.2 应用服务和数据服务拆分 特点:App.DB.Fi ...
 - 9、redis之事务2-Jedis的八种调用方式(事务、管道、分布式)介绍
		
1.普通同步 @Test public void test1Normal() { Jedis jedis = new Jedis("localhost"); long start ...
 - 【转载】Redis的Java客户端Jedis的八种调用方式(事务、管道、分布式…)介绍
		
转载地址:http://blog.csdn.net/truong/article/details/46711045 关键字:Redis的Java客户端Jedis的八种调用方式(事务.管道.分布式…)介 ...
 
随机推荐
- x名称空间中的内容
			
原文:x名称空间中的内容 x名称空间映射的是http://schemas.microsoft.com/winfx/2006/xaml,它包含的类均与XAML的解析有关,下面分三部分介绍 一:x名称空间 ...
 - liunx 详细常用操作
			
图片来自: http://www.cnblogs.com/zhangsf/archive/2013/06/13/3134409.html 公司新员工学习有用到,Vim官网的手册又太大而全,而网上各方资 ...
 - WPF将点列连接成光滑曲线——贝塞尔曲线
			
原文:WPF将点列连接成光滑曲线--贝塞尔曲线 背景 最近在写一个游戏场景编辑器,虽然很水,但是还是遇到了不少问题.连接离散个点列成为光滑曲线就是一个问题.主要是为了通过关键点产生2D的赛道场景.总之 ...
 - Win8Metro(C#)数字图像处理--2.32图像曝光算法
			
原文:Win8Metro(C#)数字图像处理--2.32图像曝光算法  [函数名称] 图像曝光函数ExposureProcess(WriteableBitmap src,int exposureV ...
 - Win10《芒果TV》更新v3.8.70周年版:升级高级配色、自动切换夜间模式
			
官宣!光阴似箭,已过三年时光,我们一起从最初的青涩变成更加成熟起来,同呼吸共命运,匠心依旧,感恩有你,Win10版<芒果TV>全平台同步更新周年版v3.8.70,升级高级配色,新增自动切换 ...
 - Android开发参考资料
			
Android入门第九篇之AlertDialog: http://blog.csdn.net/hellogv/article/details/5955959 Android的AlertDialog详解 ...
 - SQL Server 将某一列的值拼接成字符串
			
名称 海鲜水产 水果蔬菜 海参 肉禽蛋 牛排 腊味 生鲜食品 将以上一列变成: 生鲜食品,海鲜水产,水果蔬菜,海参,牛排,肉禽蛋,腊味 sql for xml path('')
 - CEGUI 0.7.7 VS2010+SP3 编译过程
			
1 在官方网站http://www.cegui.org.uk/ 下载最新的CEGUI 源代码 版本是0.7.7 2 下载编译需要用到的依赖文件包 将解压后的文件夹 Dependencies 和CEGU ...
 - VM中linux和windows主机进行串口通信
			
最近在做关于AIS的内容.为了对AIS电文进行解码,串口收发. 数据有PC机模拟发送,为了调试方便,不用次次将程序放到开发板上运行,所以利用pc主机和虚拟机进行串口通信模拟该过程. 首先需要用到一个软 ...
 - 配置 ClientIDMode  控件ID生成规则
			
废话不说先例子: <asp:GridView ID="grd" runat="server"AutoGenerateColumns="False ...