分布式事务系列 - 解决跨库转账问题

本文内容

  1. 什么是分布式事务
  2. 分布式事务中的难点
  3. 常见的解决方案
  4. 讲解通过可靠消息来解决分布式事务

什么是分布式事务?

有这样一个需求:

小明有两个账户,分别位于A、B两个数据库中,小明需要将A中的资金转到B中。

我们如何实现?

按照下面的方式实现看看有没有问题。

  1. 连接数据库A,获取connA连接
  2. connA打开事务
  3. A库资金减少100
  4. 连接库B,获取connB连接
  5. connB打开事务
  6. B库资金增加100
  7. connA.commit()
  8. connB.commit()

上面操作,正常情况是没有问题。

考虑如下情况:

第7步执行成功之后,网络出问题了,第8步会提交失败,此时的结果是:A库资金减少了100,B库资金却没有增加;这是一个网络问题导致了我们业务失败了,网络因素是程序不可控的一些因素,还有其他的比如运行到7之后,系统突然断电了,也会出现同样的结果。造成了数据错误,对业务影响也是比较大的。

分布式事务可以这么理解:一个业务操作中,会包含很多子业务的,每个子业务都是独立的事务,我们需要考虑的是如何保证这些子业务都成功,或者都失败。

分布式事务中的难点

  1. 分布式事务中,分支可能是各种各样的,可能存在各种异常情况导致有些成功有些却失败了,这些情况需要我们程序能够处理,保证所有的分支要么都成功、要么都失败,不能出现部分成功而部分失败的情况。
  2. 分布式事务中,很难保证多个分支同时成功。每个分支可能都是提供远程接口进行调用,之间存在网络故障的问题,前面的分支调用成功了,但是其他分支由于网络等不可控的因素而调用不成功,此时数据是很难做到同时一致性的。
  3. 实时一致性难以保证。那么我们可以做到最终一致性也是可以的。

什么是最终一致性?

就拿上面的转账来说,A库的资金减少了,由于网络问题,操作B库的connB连接断开了,导致B库资金没有增加;网络问题是可以恢复了,如果网络恢复了,系统能够给B中资金加上,这样最终数据也是正确的;这中间有段时间AB库的资金是不一致的(A库减少了100,B库应该增加100却没有增加,数据是不一致的),但是最终某个时间点数据变为一致了。能够将不一致的时间降到最低是系统需要考虑的问题

分布式事务中,我们可以接受数据在某个时间段之内不一致,但是数据最终在某个时间点是一致的。

常见解决方案

  1. 可靠消息模式
  2. TCC模式实现

分布式事务系列中主要讲这2种方案,这两种方案基本上可以解决大多数常见的分布式事务的问题,所以咱们必须把这两种方式拿下。

下面我们介绍一下使用可靠消息如何实现?

可靠消息模式实现转账操作

在这里插入图片描述
两个微服务
服务A:用于操作A库中的账户
服务B:用于操作B库中的账户

两个服务都是链接独立的数据库,依靠数据库提供的功能,能够保证各自的事务。

对于用户来说过程如下:

  1. 调用服务A,扣款100
  2. 发送扣款成功的消息到消息服务
  3. 返回用户转账已受理

接着

  1. 服务B,拉取到转账消息
  2. B库中给账户+100
  3. 调用消息服务将消息删除
  4. 服务B消费的过程中,比如出现网络、机器重启等原因,导致消费失败,等机器恢复之后,可以再次消费这条消息,重试多次最终会成功

上面整个转账过程中有几点我们需要考虑一下:

  1. 如何确保A服务中扣款成功之后,消息一定能够发送成功;如果消息发送失败而丢失了,后面的业务将没法进行。这块涉及如何发送可靠消息,之前消息系列的文章有介绍,大家可以看一下:聊聊业务系统中投递消息到mq的几种方式
  2. 我们的服务一般都是集群的方式,消息消费的时候,可能会出现一条消息并发消费的情况,并发情况发生的时候,如何确保消费只能够被消费成功一次。如果一条转账消息被成功消费两次,最终B账户中将增加200,导致业务出错。这块可以参考如何保证消息消费的幂等性,这块之前也有讲过,大家也可以看一下:探讨一下实现幂等性的几种方式

依靠消息模式实现分布式事物,比较适合消费者一定会处理成功的场景。比如用户注册发送邮件、发送短信、送积分等。

总结

  1. 本文主要介绍了什么是分布式事务、其中的一些难点
  2. 常见的使用最多的解决方案:异步消息处理分布式事物、tcc模式
  3. tcc模式我们在后面的文章中介绍,目前在我们自己的系统中实现了通用的tcc,已经上线运行,运行也比较稳定
  4. 对分布式事务有兴趣、或有疑问的,可以加我微信itsoku交流
  5. 请关注公众号javacode2018,免费领取年薪40万的资料,更多好文及时推送给您
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
分布式事务问题在Java中可以通过使用分布式事务管理框架来解决,如Atomikos、Bitronix、Narayana等。以下是使用Atomikos实现分布式事务的示例代码: 1. 配置Atomikos 在项目中添加Atomikos的依赖,并在Spring配置文件中添加如下配置: ```xml <bean id="dataSource1" class="com.atomikos.jdbc.AtomikosDataSourceBean"> <property name="uniqueResourceName" value="dataSource1" /> <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" /> <property name="xaProperties"> <props> <prop key="user">root</prop> <prop key="password">root</prop> <prop key="URL">jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8</prop> </props> </property> <property name="poolSize" value="10" /> </bean> <bean id="dataSource2" class="com.atomikos.jdbc.AtomikosDataSourceBean"> <property name="uniqueResourceName" value="dataSource2" /> <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" /> <property name="xaProperties"> <props> <prop key="user">root</prop> <prop key="password">root</prop> <prop key="URL">jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=utf-8</prop> </props> </property> <property name="poolSize" value="10" /> </bean> <bean id="transactionManager" class="com.atomikos.icatch.jta.UserTransactionManager"> <property name="forceShutdown" value="false" /> </bean> <bean id="userTransaction" class="com.atomikos.icatch.jta.J2eeUserTransaction"> <property name="transactionTimeout" value="300" /> </bean> ``` 2. 编写分布式事务代码 在需要执行分布式事务的方法上添加@Transactional注解,并指定transactionManager属性为上述配置文件中定义的transactionManager: ```java @Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Transactional(transactionManager = "transactionManager") public void transfer(String fromUser, String toUser, int amount) { userDao.decreaseBalance(fromUser, amount); userDao.increaseBalance(toUser, amount); } } ``` 3. 测试分布式事务 编写测试代码,调用上述transfer方法,模拟转账操作。如果转账过程中出现异常,事务会自动回滚,保证数据的一致性。 ```java @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath:spring-context.xml" }) public class UserServiceTest { @Autowired private UserService userService; @Test public void testTransfer() { userService.transfer("user1", "user2", 100); } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

路人甲Java

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值