案例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账户余额有无够10000

String 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账户10000

sql = "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账户增加10000

sql = "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 transaction

updateA setamount=amount-10000where userId=1;

updateBsetamount=amount+10000where userId=1;

Endtransaction

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)。

Begintransaction

updateA set amount=amount-10000where 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

queue

Begin transaction

select count(*) as cnt from message_apply where msg_id=msg.msg_id;

ifcnt==0

then

update B set amount=amount+10000 where userId=1;

insert into message_apply(msg_id) values(msg.msg_id);

End

transaction

commit;

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如何设计转账业务_Java实现转账业务相关推荐

  1. java课程设计象棋_java课程设计 中国象棋

    [实例简介] 内附eclipse项目,可运行jar包,和课程设计报告,觉得让你一下子看懂 [实例截图] [核心代码] P17-象棋java课程设计 └── P17-象棋java课程设计 ├── res ...

  2. java课程设计 计算器_java课程设计-保存计算过程的计算器

    java课程设计-保存计算过程的计算器 编号: <面向对象程序设计(JAVA) > 课程设计(论文)档案 题 目: 保存计算过程的计算器 学 院: 信息学院 专 业: 姓 名: 学 号: ...

  3. java课程设计日历_java课程设计日历记事本赵锐.doc

    java课程设计日历记事本赵锐.doc 2本科生课程设计课程名称JAVA程序设计课程设计题目日历记事本学号201440930252学生姓名赵锐所在专业2014计算机学院所在班级信工2班成绩课程设计时间 ...

  4. java课程设计电子相册_java课程设计基于Java的电子相册系统设计与实现.pdf

    您所在位置:网站首页 > 海量文档 &nbsp>&nbsp计算机&nbsp>&nbspJava java课程设计基于Java的电子相册系统设计与实现. ...

  5. java课程设计电子相册_java课程设计 基于java的电子相册系统设计与实现.pdf

    java课程设计 基于java的电子相册系统设计与实现.pdf 还剩 2页未读, 继续阅读 下载文档到电脑,马上远离加班熬夜! 亲,喜欢就下载吧,价低环保! 内容要点: 基于Java的电子相册系统设计 ...

  6. 万年历java课程设计报告_java万年历课程设计报告2010

    一.课程设计任务及要求 (1)设计任务: 编写一个Java 万年历程序,实现日期和星期的查询. (2)设计要求: 1.主界面采用边界布局,北面是一个设置年月的控制面板,中间是日历绘制区: 2.单击&q ...

  7. java课程设计 成绩_Java课程设计—学生成绩管理系统(201521123004-林艺如)

    1.团队课程设计博客 2.个人负责模块或任务说明 ①.Menu Menu.jsp 在页面中给出提示,用HTML的,与下一个跳转页面进行连接,即点击后进入下一个页面 MenuTeacher.jsp 利用 ...

  8. java课程设计 华容道_Java课设-数字华容道

    #2019-2020学年第一学期Java课设 #数字华容道 ##一:团队成员介绍及任务分配 ####王鑫杰 201821123112 组长 dao模式结合数据库 登录验证 排行榜展示 ####林炜 2 ...

  9. java课程设计拼图_java拼图游戏课程设计报告

    java拼图游戏课程设计报告 砾寸椒涩藕矾糯陋捕炬洁困喘港划舟逃豺涌锤芳喜胺递龚乏埔跺摩实阿信颊立蹲稿船纽臃瘪自康嘱脖究绢术拱虑犹犀棉宜炙转鸦半甘哨疗墓暑蛊渤幽峭咀豺虫拘召饭莽畜穗篷姿钟逻捞跨瀑拿丈土 ...

  10. java课程设计斗地主_Java课程设计---web版斗地主

    一. 团队课程设计博客链接 https://www.cnblogs.com/lanxiang/p/10293812.html 二.个人负责模块和任务说明 负责前后端数据传输 JSP界面的设计 根据后台 ...

最新文章

  1. Linux下使用ssh动态验证码登陆机器
  2. matlab 恶俗的缩写combntns()
  3. .NET程序员的C情结(二)
  4. 循环神经网络的数据预处理
  5. 瑞幸咖啡的每一个环节,都蕴含着增长知识点
  6. CS这么难申,小哥哥你怎么拿到全美最高额度奖学金的?
  7. 法嵌入互操作类型“SHDocVw.ShellWindowsClass”请改用适用的接口-解决方法
  8. Postfix:Sql_select option missing问题解决及原因
  9. LeetCode 243. 最短单词距离
  10. SDOI 2017R2游记
  11. Spring 框架RCE 安全漏洞及解决方式
  12. wine linux 目录,wine的安装与microsoft office在linux上的运行
  13. python入门笔记——类和对象③(案例:自动随机文字游戏——决战紫禁之巅)
  14. 数学的三大核心领域——分析学范畴
  15. mysql1273,phpmysql错误 – #1273 – #1273 – 未知排序规则:’utf8mb4_general_ci’
  16. 2020中国大学生程序设计竞赛(CCPC) - 网络选拔赛 1005 Lunch (杭电 6892)
  17. hutool生成二维码
  18. 【gitee报用户名和密码错误[session-694cc0ab] chenbingxin: Incorrect username or password (access token)】
  19. python爬取cctalk视频_python爬虫urllib使用和进阶 | Python爬虫实战二
  20. 火车头采集器超级详细图文使用指导(面向新手)

热门文章

  1. ASP.NET实现PDF大文件的浏览
  2. CSP信息学奥赛知识总结
  3. 地理信息系统(GIS)系列——ArcGIS API for JavaScript 3.9(2)
  4. 在C#应用程序中嵌入暴风影音播放器
  5. python 爬取直播_python 斗鱼直播间爬取代码
  6. EF There is already an open DataReader associated with this Command
  7. win10设置自定html背景,win10开始菜单背景和图标自定义的方法
  8. tensorflow下载mnist数据集
  9. java环境安装菜鸟教程,window系统安装Java 配置环境变量 | 菜鸟教程 JDK配置
  10. 新版 世界地图 中文版地图 国界地图 高清全彩矢量地图 CDR 2021年整理制作