热门标签 | 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--本地消息表(事务最终一致方案)

 


推荐阅读
  • MySQL语句大全:创建、授权、查询、修改等【MySQL】的使用方法详解
    本文详细介绍了MySQL语句的使用方法,包括创建用户、授权、查询、修改等操作。通过连接MySQL数据库,可以使用命令创建用户,并指定该用户在哪个主机上可以登录。同时,还可以设置用户的登录密码。通过本文,您可以全面了解MySQL语句的使用方法。 ... [详细]
  • 本文由编程笔记小编整理,介绍了PHP中的MySQL函数库及其常用函数,包括mysql_connect、mysql_error、mysql_select_db、mysql_query、mysql_affected_row、mysql_close等。希望对读者有一定的参考价值。 ... [详细]
  • 本文详细介绍了SQL日志收缩的方法,包括截断日志和删除不需要的旧日志记录。通过备份日志和使用DBCC SHRINKFILE命令可以实现日志的收缩。同时,还介绍了截断日志的原理和注意事项,包括不能截断事务日志的活动部分和MinLSN的确定方法。通过本文的方法,可以有效减小逻辑日志的大小,提高数据库的性能。 ... [详细]
  • PHP设置MySQL字符集的方法及使用mysqli_set_charset函数
    本文介绍了PHP设置MySQL字符集的方法,详细介绍了使用mysqli_set_charset函数来规定与数据库服务器进行数据传送时要使用的字符集。通过示例代码演示了如何设置默认客户端字符集。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • 如何在php中将mysql查询结果赋值给变量
    本文介绍了在php中将mysql查询结果赋值给变量的方法,包括从mysql表中查询count(学号)并赋值给一个变量,以及如何将sql中查询单条结果赋值给php页面的一个变量。同时还讨论了php调用mysql查询结果到变量的方法,并提供了示例代码。 ... [详细]
  • MyBatis多表查询与动态SQL使用
    本文介绍了MyBatis多表查询与动态SQL的使用方法,包括一对一查询和一对多查询。同时还介绍了动态SQL的使用,包括if标签、trim标签、where标签、set标签和foreach标签的用法。文章还提供了相关的配置信息和示例代码。 ... [详细]
  • Java学习笔记之使用反射+泛型构建通用DAO
    本文介绍了使用反射和泛型构建通用DAO的方法,通过减少代码冗余度来提高开发效率。通过示例说明了如何使用反射和泛型来实现对不同表的相同操作,从而避免重复编写相似的代码。该方法可以在Java学习中起到较大的帮助作用。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • 本文介绍了Oracle数据库中tnsnames.ora文件的作用和配置方法。tnsnames.ora文件在数据库启动过程中会被读取,用于解析LOCAL_LISTENER,并且与侦听无关。文章还提供了配置LOCAL_LISTENER和1522端口的示例,并展示了listener.ora文件的内容。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 本文介绍了游标的使用方法,并以一个水果供应商数据库为例进行了说明。首先创建了一个名为fruits的表,包含了水果的id、供应商id、名称和价格等字段。然后使用游标查询了水果的名称和价格,并将结果输出。最后对游标进行了关闭操作。通过本文可以了解到游标在数据库操作中的应用。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • ALTERTABLE通过更改、添加、除去列和约束,或者通过启用或禁用约束和触发器来更改表的定义。语法ALTERTABLEtable{[ALTERCOLUMNcolu ... [详细]
  • Day2列表、字典、集合操作详解
    本文详细介绍了列表、字典、集合的操作方法,包括定义列表、访问列表元素、字符串操作、字典操作、集合操作、文件操作、字符编码与转码等内容。内容详实,适合初学者参考。 ... [详细]
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社区 版权所有