案例1:A给B转10000块钱

1.准备工作:在数据库先建一张表(t_account):

id name balance


1 A 20000

2 B 0


2.模拟银行转账:

实现的步骤:

(1).查询A的账户余额

SELECT * FROM t_account WHERE name=‘A’ AND balance>=10000

=10000:执行转账 GOTO(2)

<10000:亲,余额不足

(2).从A的账户上扣除10000块

UPDATE t_account SET balance=balance-10000 WHERE name=‘A’

(3).中途一切正常,GOTO(4)

中途异常(如停电),整个事务回滚(rollback)到最初状态,即返回从A账户上扣除的1000给A

(4).在赵敏的账户上面加10000块

UPDATE t_account SET balance=balance+10000 WHERE name=‘赵敏’

(5).提交事务(commit)

在程序中如何操作事务(操作事务的模板)

try{

//1.DML操作默认是自动提交的,要使用事务,就应该将事务提交方式改变为手动提交

Connection对象.setAutoCommit(false);

//操作1

//操作2

//异常

//操作3

//2.提交事务(使所有上一次提交/回滚后进行的更改成为持久更改(即一旦提交,便不能更改),并释放此Connection 对象当前持有的所有数据库锁。)

Connection对象.commit();

}catch(Exception e){

e.printStackTrace();

//3.回滚事务(取消在当前事务中进行的所有更改,并释放此Connection 对象当前持有的所有数据库锁)

Connection对象.rollback();

}
模拟银行转账代码:

AccountTest
//模拟银行转账(A转账给B)
public class AccountTest {

@Test
public void testAccount() throws Exception {Connection conn = null;PreparedStatement ps = null;ResultSet result = null;try{//查询A账户余额有无够10000String sql = "select * from account where name = ? and balance >= ?";conn = DBUtils.getConnection();//将事务设置为手动提交conn.setAutoCommit(false);ps = conn.prepareStatement(sql);ps.setString(1, "A");ps.setInt(2, 10000);result = ps.executeQuery();if(!result.next()){throw new RuntimeException("您的余额不足");}//扣除A账户10000sql = "update account set balance = balance - ? where name = ?";ps = conn.prepareStatement(sql);ps.setInt(1, 10000);ps.setString(2, "A");ps.executeUpdate();//模拟中途断电异常//System.out.println(1/0);//B账户增加10000sql = "update account set balance = balance + ? where name = ?";ps = conn.prepareStatement(sql);ps.setInt(1, 10000);ps.setString(2, "B");ps.executeUpdate();conn.commit();//提交(要整个事务都完成才能提交,因为提交之后就不能改变了)}catch(Exception e){e.printStackTrace();conn.rollback();}DBUtils.close(conn, ps, result);
}

}

案例:2.以支付宝转账余额宝为例,假设有

支付宝账户表:A(id,userId,amount)
余额宝账户表:B(id,userId,amount)
用户的userId=1;
从支付宝转账1万块钱到余额宝的动作分为两步:

1)支付宝表扣除1万:update A set amount=amount-10000 where userId=1;
2)余额宝表增加1万:update B set amount=amount+10000 where userId=1;
如何确保支付宝余额宝收支平衡呢?

有人说这个很简单嘛,可以用事务解决。

Begin  transactionupdate A   set amount=amount-10000    where userId=1;
update  B   set amount=amount+10000   where userId=1;End transaction
commit;

非常正确,如果你使用spring的话一个注解就能搞定上述事务功能。

@Transactional(rollbackFor=Exception.class)public void update() {//更新A表updateATable();//更新B表updateBTable();}

如果系统规模较小,数据表都在一个数据库实例上,上述本地事务方式可以很好地运行,但是如果系统规模较大,比如支付宝账户表和余额宝账户表显然不会在同一个数据库实例上,他们往往分布在不同的物理节点上,这时本地事务已经失去用武之地。

既然本地事务失效,分布式事务自然就登上舞台。
2 分布式事务—两阶段提交协议
两阶段提交协议(Two-phase Commit,2PC)经常被用来实现分布式事务。一般分为协调器C和若干事务执行者Si两种角色,这里的事务执行者就是具体的数据库,协调器可以和事务执行器在一台机器上。

1) 我们的应用程序(client)发起一个开始请求到TC;

2) TC先将消息写到本地日志,之后向所有的Si发起消息。以支付宝转账到余额宝为例,TC给A的prepare消息是通知支付宝数据库相应账目扣款1万,TC给B的prepare消息是通知余额宝数据库相应账目增加1w。为什么在执行任务前需要先写本地日志,主要是为了故障后恢复用,本地日志起到现实生活中凭证 的效果,如果没有本地日志(凭证),出问题容易死无对证;

3) Si收到消息后,执行具体本机事务,但不会进行commit,如果成功返回,不成功返回。同理,返回前都应把要返回的消息写到日志里,当作凭证。

4) TC收集所有执行器返回的消息,如果所有执行器都返回yes,那么给所有执行器发生送commit消息,执行器收到commit后执行本地事务的commit操作;如果有任一个执行器返回no,那么给所有执行器发送abort消息,执行器收到abort消息后执行事务abort操作。

注:TC或Si把发送或接收到的消息先写到日志里,主要是为了故障后恢复用。如某一Si从故障中恢复后,先检查本机的日志,如果已收到,则提交,如果则回滚。如果是,则再向TC询问一下,确定下一步。如果什么都没有,则很可能在阶段Si就崩溃了,因此需要回滚。

现如今实现基于两阶段提交的分布式事务也没那么困难了,如果使用Java,那么可以使用开源软件atomikos(http://www.atomikos.com/)来快速实现。

不过但凡使用过的上述两阶段提交的同学都可以发现性能实在是太差,根本不适合高并发的系统。为什么?

  • 1)两阶段提交涉及多次节点间的网络通信,通信时间太长!
  • 2)事务时间相对于变长了,锁定的资源的时间也变长了,造成资源等待时间也增加好多!

正是由于分布式事务存在很严重的性能问题,大部分高并发服务都在避免使用,往往通过其他途径来解决数据一致性问题。
3 使用消息队列来避免分布式事务
如果仔细观察生活的话,生活的很多场景已经给了我们提示。

比如在北京很有名的姚记炒肝点了炒肝并付了钱后,他们并不会直接把你点的炒肝给你,而是给你一张小票,然后让你拿着小票到出货区排队去取。为什么他们要将付钱和取货两个动作分开呢?原因很多,其中一个很重要的原因是为了使他们接待能力增强(并发量更高)。

还是回到我们的问题,只要这张小票在,你最终是能拿到炒肝的。同理转账服务也是如此,当支付宝账户扣除1万后,我们只要生成一个凭证(消息)即可,这个凭证(消息)上写着“让余额宝账户增加 1万”,只要这个凭证(消息)能可靠保存,我们最终是可以拿着这个凭证(消息)让余额宝账户增加1万的,即我们能依靠这个凭证(消息)完成最终一致性。
3.1 如何可靠保存凭证(消息)
有两种方法:

3.1.1 业务与消息耦合的方式
支付宝在完成扣款的同时,同时记录消息数据,这个消息数据与业务数据保存在同一数据库实例里(消息记录表表名为message)。

Begin    transactionupdate   A set amount=amount-10000  where userId=1;insert into message(userId, amount,status) values(1,10000,1);End transaction
commit;

上述事务能保证只要支付宝账户里被扣了钱,消息一定能保存下来。

当上述事务提交成功后,我们通过实时消息服务将此消息通知余额宝,余额宝处理成功后发送回复成功消息,支付宝收到回复后删除该条消息数据。
3.1.2 业务与消息解耦方式
上述保存消息的方式使得消息数据和业务数据紧耦合在一起,从架构上看不够优雅,而且容易诱发其他问题。为了解耦,可以采用以下方式。

1)支付宝在扣款事务提交之前,向实时消息服务请求发送消息,实时消息服务只记录消息数据,而不真正发送,只有消息发送成功后才会提交事务;

2)当支付宝扣款事务被提交成功后,向实时消息服务确认发送。只有在得到确认发送指令后,实时消息服务才真正发送该消息;

3)当支付宝扣款事务提交失败回滚后,向实时消息服务取消发送。在得到取消发送指令后,该消息将不会被发送;

4)对于那些未确认的消息或者取消的消息,需要有一个消息状态确认系统定时去支付宝系统查询这个消息的状态并进行更新。为什么需要这一步骤,举个例子:假设在第2步支付宝扣款事务被成功提交后,系统挂了,此时消息状态并未被更新为“确认发送”,从而导致消息不能被发送。

优点:消息数据独立存储,降低业务系统与消息系统间的耦合;

缺点:一次消息发送需要两次请求;业务处理服务需要实现消息状态回查接口。
3.2 如何解决消息重复投递的问题
还有一个很严重的问题就是消息重复投递,以我们支付宝转账到余额宝为例,如果相同的消息被重复投递两次,那么我们余额宝账户将会增加2万而不是1万了。

为什么相同的消息会被重复投递?比如余额宝处理完消息msg后,发送了处理成功的消息给支付宝,正常情况下支付宝应该要删除消息msg,但如果支付宝这时候悲剧的挂了,重启后一看消息msg还在,就会继续发送消息msg。

解决方法很简单,在余额宝这边增加消息应用状态表(message_apply),通俗来说就是个账本,用于记录消息的消费情况,每次来一个消息,在真正执行之前,先去消息应用状态表中查询一遍,如果找到说明是重复消息,丢弃即可,如果没找到才执行,同时插入到消息应用状态表(同一事务)。

for each
msg in
queueBegin transactionselect count(*) as cnt from message_apply where msg_id=msg.msg_id;if cnt==0
thenupdate B set amount=amount+10000 where userId=1;insert into message_apply(msg_id) values(msg.msg_id);Endtransactioncommit;

ebay的研发人员其实在2008年就提出了应用消息状态确认表来解决消息重复投递的问题:http://queue.acm.org/detail.cfm?id=1394128。
补充:

之前看多阿里大神程立的一个关于分布式事务的文档,目前使用较多的分布式事务解决方案有几种:
一、结合MQ消息中间件实现的可靠消息最终一致性
二、TCC补偿性事务解决方案
三、最大努力通知型方案

第一种方案:可靠消息最终一致性,需要业务系统结合MQ消息中间件实现,在实现过程中需要保证消息的成功发送及成功消费。即需要通过业务系统控制MQ的消息状态
第二种方案:TCC补偿性,分为三个阶段TRYING-CONFIRMING-CANCELING。每个阶段做不同的处理。
TRYING阶段主要是对业务系统进行检测及资源预留
CONFIRMING阶段是做业务提交,通过TRYING阶段执行成功后,再执行该阶段。默认如果TRYING阶段执行成功,CONFIRMING就一定能成功。
CANCELING阶段是回对业务做回滚,在TRYING阶段中,如果存在分支事务TRYING失败,则需要调用CANCELING将已预留的资源进行释放。
第三种方案:最大努力通知xing型,这种方案主要用在与第三方系统通讯时,比如:调用微信或支付宝支付后的支付结果通知。这种方案也是结合MQ进行实现,例如:通过MQ发送http请求,设置最大通知次数。达到通知次数后即不再通知。
具体的案例你也可以参考下这篇博客,它上面的这个案例就是结合电商支付做的系统分布式事务实现案例:http://www.roncoo.com/article/detail/124243

基于事务消息的MQ方案是目前公认的较为理想的分布式事务解决方案,各大电商都在应用这一方案。种方式适合的业务场景广泛,而且比较可靠。不过这种方式技术实现的难度比较大。目前主流的开源MQ(ActiveMQ、RabbitMQ、Kafka)均未实现对事务消息的支持,所以需二次开发或者新造轮子。
参考文献
Dan Pritchett《Base: An Acid Alternative》
程立,大规模SOA系统中的分布式事务处理
《Mysql两阶段提交》

Java实现转账业务相关推荐

  1. java框架三层架构是_MVC框架模式和Javaweb经典三层架构

    一.MVC设计模式 首先我们需要知道MVC模式并不是javaweb项目中独有的,MVC是一种软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model).视图(View)和控制器(Co ...

  2. java实现aop的几种方式_SpringAOP 的三种实现方式

    引入aop相关的jar包 org.springframework spring-aop 5.1.12.RELEASE org.aspectj aspectjweaver 1.9.4 TransferS ...

  3. java文件下载大小限制,完整PDF

    美团面试经历(还原最真实的4面) 美团一面: 1.自我介绍 2.聊项目相关 介绍项目 怎么保证redis与Mysql的数据一致性 见你写了个加随机数预防缓存雪崩,解释一下 未改进和改进后的Jmter测 ...

  4. Java多线程学习二十三:什么是阻塞队列

    阻塞队列的作用 阻塞队列,也就是 BlockingQueue,它是一个接口,如代码所示: public interface BlockingQueue<E> extends Queue&l ...

  5. java编程式事务_Spring编程式和声明式事务实例讲解

    Spring事务管理 Spring支持两种方式的事务管理: 编程式事务管理: 通过Transaction Template手动管理事务,实际应用中很少使用, 使用XML配置声明式事务: 推荐使用(代码 ...

  6. Java程序员的春天!java第三方线程池

    前言 算法血拼:Google+百度+Alibaba+字节+Tencent+网易+360+拼夕夕+美团 不知不觉双11就来了,轰轰烈烈的秋招也完美结束了,不知算法与数据结构成为了多少小伙伴进击大厂的绊脚 ...

  7. Java学习——JDBC之从导Jar包到封装

    书籍就像一盏神灯,它照亮人们最遥远.最黯淡的生活道路.--乌皮特 前言:下面内容是本人Java初学者的学习内容 前提准备:已经安装好mysql数据库,若没有请看Mysql8.0的下载安装 需要学习SQ ...

  8. 二、Java框架之Spring注解开发

    文章目录 1. IOC/DI注解开发 1.1 Component注解 @Component @Controller @Service @Repository 1.2 纯注解开发模式 1.3 注解开发b ...

  9. Java TDD介绍-1

    原文:Introduction in Java TDD – part 1 翻译:get-set 欢迎来到关于测试驱动开发(TDD)的简介.我们会谈到关于Java和JUnit在TDD中的应用,不过这些都 ...

最新文章

  1. linux c 判断路径是 目录还是文件
  2. 动态内存分配到底为谁分配内存空间【浅谈动态内存的一个实例】
  3. matlab超出维度,求助。。。matlab索引超出维度要怎么修改。。。谢谢
  4. 吴军《智能时代》序言汉译英练习
  5. Spring+CXF的WebServices简单示例
  6. 基于CNN的中文文本分类算法(可应用于垃圾文本过滤、情感分析等场景)
  7. c++整人小程序无限弹窗(附源码)
  8. 如何检测手机号名字男女
  9. android fps 性能分析,Android性能测试关注的指标整理
  10. 【基础】SAP 新增计量单位
  11. Discuz采集之Discuz论坛采集全网文章采集伪原创发布方法(图文)
  12. QNX 7.1 交叉编译 cron
  13. Mac环境下简化ssh连接vlab口令实现免密登录(UNSW)
  14. Java实战项目《瑞吉外卖》
  15. 【零基础教学】Unet局域网联机的实现——最基础的Unity联网实现方式(2)
  16. 2022,我们追逐群星,也在追逐AIGC的无尽可能
  17. 安装Arch(含输入法配置)
  18. 你见过最奇葩的代码提交信息是什么?别再为写commit message头疼了!
  19. 粉刺黑头实际上就是黑头粉刺,是粉刺的一种类型,挤出后形如小虫,表面发黑。下面给大家带来除粉刺黑头的偏方,希望对大家有所帮助。
  20. 2006中国企业500强名单

热门文章

  1. 认识LTE平台(一):LTE的物理层
  2. Cadence Allegro导出Gerber步骤
  3. Enterprise JavaBeans导论[转]
  4. 基于Dotnetty和Protobuf的Unity客户端开发(一)
  5. python保留小数点后位数_Python保留指定位数的小数
  6. 蘑菇云【行空板Python入门教程】第三课:多功能提醒器
  7. 天池竞赛-淘宝穿衣搭配(数据预处理部分)
  8. 通用汽车公司选择Qt在其“软件定义汽车”的整车开发流程中发挥突出作用
  9. 基于串级PID的平衡小车
  10. python播放音频文件_详解使用pygame播放一段MP3音频文件