热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

分布式事务解决方案3本地消息表(事务最终一致方案)

一、本地消息表原理

1、本地消息表方案介绍

本地消息表的最终一致方案

采用BASE原理,保证事务最终一致

 在一致性方面,允许一段时间内的不一致,但最终会一致。

在实际系统中,要根据具体情况,判断是否采用。(有些场景对一致性要求较高,谨慎使用)

 

2、本地消息表的使用场景

基于本地消息表的方案中,将本事务外操作,记录在消息表中

其他事务,提供操作接口

定时任务轮询本地消息表,将未执行的消息发送给操作接口。

操作接口处理成功,返回成功标识,处理失败,返回失败标识。

定时任务接到标识,更新消息的状态

定时任务按照一定的周期反复执行

对于屡次失败的消息,可以设置最大失败次数

超过最大失败次数的消息,不进行接口调用

等待人工处理

例如使用支付宝的支付场景,系统生成订单,支付宝系统支付成功后,调用我们系统提供的回调接口,回调接口更新订单状态为已支付。回调通知执行失败,支付宝会过一段时间再次调用。

 

3、本地消息表架构图

分布式事务解决方案3--本地消息表(事务最终一致方案)

 

4、优缺点

优点: 避免了分布式事务,实现了最终一致性

缺点: 注意重试时的幂等性操作

 

 

二、本地消息表数据库设计

整体工程复用前面的my-tcc-demo

 1、两台数据库 134和129。user_134 创建支付消息表payment_msg, user_129数据库创建订单表t_order

分布式事务解决方案3--本地消息表(事务最终一致方案)

 

 

2、使用MyBatis-generator 生成数据库映射文件,生成后的结构如下图所示

分布式事务解决方案3--本地消息表(事务最终一致方案)

 

三、支付接口

1、创建支付服务PaymentService 

@Service
public class PaymentService {

    @Resource
    private AccountAMapper accountAMapper;

    @Resource
    private PaymentMsgMapper paymentMsgMapper;


    /**
     * 支付接口
     * @param userId 用户Id
     * @param orderId 订单Id
     * @param amount 支付金额
     * @return 0: 成功; 1:用户不存在  2:余额不足
     */
    @Transactional(transactiOnManager= "tm134")
    public int payment(int userId, int orderId, BigDecimal amount){

        //支付操作
        AccountA accountA = accountAMapper.selectByPrimaryKey(userId);
        if(accountA == null){
            return  1;
        }
        if(accountA.getBalance().compareTo(amount) <0){
            return 2;
        }

        accountA.setBalance(accountA.getBalance().subtract(amount));
        accountAMapper.updateByPrimaryKey(accountA);

        PaymentMsg paymentMsg = new PaymentMsg();
        paymentMsg.setOrderId(orderId);
        paymentMsg.setStatus(0); //未发送
        paymentMsg.setFailCnt(0); //失败次数
        paymentMsg.setCreateTime(new Date());
        paymentMsg.setCreateUser(userId);
        paymentMsg.setUpdateTime(new Date());
        paymentMsg.setUpdateUser(userId);

        paymentMsgMapper.insertSelective(paymentMsg);

        return  0;


    }
}

  

2、创建Controller层

@RestController
public class PaymentController {

    @Autowired
    private PaymentService paymentService;

    //localhost:8080/payment?userId=1&orderId=10010&amount=200
    @RequestMapping("payment")
    public String payment(int userId, int orderId, BigDecimal amount){
        int result = paymentService.payment(userId, orderId,amount);
        return  "支付结果:" + result;
    }
}

  

3、调用接口

localhost:8080/payment?userId=1&orderId=10010&amount=200

分布式事务解决方案3--本地消息表(事务最终一致方案)

 

 查看表。账号表account_a 扣掉了200元, 支付消息表插入了一条支付记录。

分布式事务解决方案3--本地消息表(事务最终一致方案)

 

 

四、订单操作接口

1、创建订单服务

@Service
public class OrderService {

    @Resource
    OrderMapper orderMapper;

    /**
     * 订单回调接口
     * @param orderId
     * @return 0:成功 1:订单不存在
     */
    public int handleOrder(int orderId){
        Order order = orderMapper.selectByPrimaryKey(orderId);
        if(order == null){
            return  1;
        }
        order.setOrderStatus(1); //已支付
        order.setUpdateTime(new Date());
        order.setUpdateUser(0); //系统更新
        orderMapper.updateByPrimaryKey(order);

        return  0;

    }
}

  

2、创建Controller

@RestController
public class OrderController {

    @Autowired
    private OrderService orderService;

    //localhost:8080/handlerOrder?orderId=10010
    @RequestMapping("handlerOrder")
    public String handlerOrder( int orderId){
        try {
            int result =  orderService.handleOrder(orderId);
            if(result == 0){
                return  "success";
            }
            return  "fail";
        }catch (Exception e){
            return  "fail";
        }

    }
}  

调用方式: localhost:8080/handlerOrder?orderId=10010

 

五、定时任务

1、增加注解EnableScheduling

@SpringBootApplication
@EnableScheduling //表明项目中可以使用定时任务
public class MyTccDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyTccDemoApplication.class, args);
    }

}

  

 

2、创建服务OrderSchedule 

@Service
public class OrderSchedule {

    @Resource
    private PaymentMsgMapper paymentMsgMapper;

    //给订单处理接口发送通知
    @Scheduled(cron = "0/10 * * * * ?")
    public void orderNotify() throws IOException {

        List list = paymentMsgMapper.selectUnSendMsgList();
        if (list == null || list.size() == 0) {
            return;
        }

        for (PaymentMsg paymentMsg : list) {
            int orderId = paymentMsg.getOrderId();
            CloseableHttpClient httpClient = HttpClientBuilder.create().build();
            HttpPost httpPost = new HttpPost("http://localhost:8080/handlerOrder");
            NameValuePair orderIdPair = new BasicNameValuePair("orderId", orderId + "");
            List nvlist = new ArrayList<>();
            nvlist.add(orderIdPair);
            HttpEntity httpEntity = new UrlEncodedFormEntity(nvlist);
            httpPost.setEntity(httpEntity);
            CloseableHttpResponse respOnse=    httpClient.execute(httpPost);
            String s = EntityUtils.toString(response.getEntity());
            if("success".equals(s)){
                paymentMsg.setStatus(1); //发送成功
                paymentMsg.setUpdateTime(new Date());
                paymentMsg.setUpdateUser(0); //系统更新
                paymentMsgMapper.updateByPrimaryKey(paymentMsg);
            }else {
                int failCnt = paymentMsg.getFailCnt();
                failCnt ++;
                paymentMsg.setFailCnt(failCnt);
                if(failCnt > 5){

                    paymentMsg.setStatus(2); //超过5次,改成失败
                }
                paymentMsg.setUpdateUser(0); //系统更新
                paymentMsg.setUpdateTime(new Date());
                paymentMsgMapper.updateByPrimaryKey(paymentMsg);
            }

        }
    }

}

  

 

  

3、模拟

1) 将订单表的状态改成0: 未支付

分布式事务解决方案3--本地消息表(事务最终一致方案)

 

 2) 清空消息表

分布式事务解决方案3--本地消息表(事务最终一致方案)

 

 

3) 将UserID为1的用户金额改成1000

分布式事务解决方案3--本地消息表(事务最终一致方案)

 

 

4) 调用支付接口

http://localhost:8080/payment?userId=1&orderId=10010&amount=200

 支付成功后,用户A的金额变成了800,并在支付消息表中生成了一条支付记录。

定时任务查询支付消息表,查找未支付的支付消息记录,然后调用订单操作接口。订单操作接口调用后,将订单状态改成1:成功。订单操作接口返回成功后,则将支付消息的状态改成已支付。

 

5、处理失败模拟

在handleOrder方法中抛出异常。

分布式事务解决方案3--本地消息表(事务最终一致方案)

 


推荐阅读
  • 本文介绍如何使用 Python 的 DOM 和 SAX 方法解析 XML 文件,并通过示例展示了如何动态创建数据库表和处理大量数据的实时插入。 ... [详细]
  • 开机自启动的几种方式
    0x01快速自启动目录快速启动目录自启动方式源于Windows中的一个目录,这个目录一般叫启动或者Startup。位于该目录下的PE文件会在开机后进行自启动 ... [详细]
  • 本文详细介绍了 InfluxDB、collectd 和 Grafana 的安装与配置流程。首先,按照启动顺序依次安装并配置 InfluxDB、collectd 和 Grafana。InfluxDB 作为时序数据库,用于存储时间序列数据;collectd 负责数据的采集与传输;Grafana 则用于数据的可视化展示。文中提供了 collectd 的官方文档链接,便于用户参考和进一步了解其配置选项。通过本指南,读者可以轻松搭建一个高效的数据监控系统。 ... [详细]
  • Spring Boot 中配置全局文件上传路径并实现文件上传功能
    本文介绍如何在 Spring Boot 项目中配置全局文件上传路径,并通过读取配置项实现文件上传功能。通过这种方式,可以更好地管理和维护文件路径。 ... [详细]
  • 本文介绍如何在将数据库从服务器复制到本地时,处理因外键约束导致的数据插入失败问题。 ... [详细]
  • MySQL Decimal 类型的最大值解析及其在数据处理中的应用艺术
    在关系型数据库中,表的设计与SQL语句的编写对性能的影响至关重要,甚至可占到90%以上。本文将重点探讨MySQL中Decimal类型的最大值及其在数据处理中的应用技巧,通过实例分析和优化建议,帮助读者深入理解并掌握这一重要知识点。 ... [详细]
  • 未定义的打字稿记录:探索其成因与解决方案 ... [详细]
  • MySQL的查询执行流程涉及多个关键组件,包括连接器、查询缓存、分析器和优化器。在服务层,连接器负责建立与客户端的连接,查询缓存用于存储和检索常用查询结果,以提高性能。分析器则解析SQL语句,生成语法树,而优化器负责选择最优的查询执行计划。这一流程确保了MySQL能够高效地处理各种复杂的查询请求。 ... [详细]
  • 浏览器作为我们日常不可或缺的软件工具,其背后的运作机制却鲜为人知。本文将深入探讨浏览器内核及其版本的演变历程,帮助读者更好地理解这一关键技术组件,揭示其内部运作的奥秘。 ... [详细]
  • 本文详细探讨了几种常用的Java后端开发框架组合及其具体应用场景。通过对比分析Spring Boot、MyBatis、Hibernate等框架的特点和优势,结合实际项目需求,为开发者提供了选择合适框架组合的参考依据。同时,文章还介绍了这些框架在微服务架构中的应用,帮助读者更好地理解和运用这些技术。 ... [详细]
  • SecureCRT是一款功能强大的终端仿真软件,支持SSH1和SSH2协议,适用于在Windows环境下高效连接和管理Linux服务器。该工具不仅提供了稳定的连接性能,还具备丰富的配置选项,能够满足不同用户的需求。通过SecureCRT,用户可以轻松实现对远程Linux系统的安全访问和操作。 ... [详细]
  • 为了确保iOS应用能够安全地访问网站数据,本文介绍了如何在Nginx服务器上轻松配置CertBot以实现SSL证书的自动化管理。通过这一过程,可以确保应用始终使用HTTPS协议,从而提升数据传输的安全性和可靠性。文章详细阐述了配置步骤和常见问题的解决方法,帮助读者快速上手并成功部署SSL证书。 ... [详细]
  • 本文详细介绍了在 Oracle 数据库中使用 MyBatis 实现增删改查操作的方法。针对查询操作,文章解释了如何通过创建字段映射来处理数据库字段风格与 Java 对象之间的差异,确保查询结果能够正确映射到持久层对象。此外,还探讨了插入、更新和删除操作的具体实现及其最佳实践,帮助开发者高效地管理和操作 Oracle 数据库中的数据。 ... [详细]
  • 在C#中开发MP3播放器时,我正在考虑如何高效存储元数据以便快速检索。选择合适的数据结构,如字典或数组,对于优化性能至关重要。字典能够提供快速的键值对查找,而数组则在连续存储和遍历方面表现优异。根据具体需求,合理选择数据结构将显著提升应用的响应速度和用户体验。 ... [详细]
  • 利用Flask框架进行高效Web应用开发
    本文探讨了如何利用Flask框架高效开发Web应用,以满足特定业务需求。具体案例中,一家餐厅希望每天推出不同的特色菜,并通过网站向顾客展示当天的特色菜。此外,还增加了一个介绍页面,在bios路径下详细展示了餐厅主人、厨师和服务员的背景和简介。通过Flask框架的灵活配置和简洁代码,实现了这一功能,提升了用户体验和餐厅的管理水平。 ... [详细]
author-avatar
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有