利用消息系统,避免分布式事物

一、本地事务
以支付宝转账余额宝为例,假设有 •支付宝账户表: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 update 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() { updateATable(); //更新A表 updateBTable(); //更新B表

—————————————————————————————
2
} 如果系统规模较小,数据表都在一个数据库实例上,上述本地事务方式可以很好地运行,但是如果系统规模较大,比如支付宝账户表和余额宝账户表显然不会在同一个数据库实例上,他们往往分布在不同的物理节点上,这时本地事务已经失去用武之地。既然本地事务失效,分布式事务自然就登上舞台。 二、分布式事务—两阶段提交协议 两阶段提交协议(Two-phase Commit,2PC)经常被用来实现分布式事务。一般分为协调器C和若干事务执行者Si两种角色,这里的事务执行者就是具体的数据库,协调器可以和事务执行器在一台机器上。 1. 我们的应用程序(client)发起一个开始请求到TC; 2. TC先将<prepare>消息写到本地日志,之后向所有的Si发起<prepare>消息。以支付宝转账到余额宝为例,TC给A的prepare消息是通知支付宝数据库相应账目扣款1万,TC给B的prepare消息是通知余额宝数据库相应账目增加1w。为什么在执行任务前需要先写本地日志,主要是为了故障后恢复用,本地日志起到现实生活中凭证 的效果,如果没有本地日志(凭证),出问题容易死无对证; 3. Si收到<prepare>消息后,执行具体本机事务,但不会进行commit,如果成功返回<yes>,不成功返回<no>。同理,返回前都应把要返回的消息写到日志里,当作凭证。 4. TC收集所有执行器返回的消息,如果所有执行器都返回yes,那么给所有执行器发生送commit消息,执行器收到commit后执行本地事务的commit操作;如果有任一个执行器返回no,那么给所有执行器发送abort消息,执行器收到abort消息后执行事务abort操作。 注:TC或Si把发送或接收到的消息先写到日志里,主要是为了故障后恢复用。如某一Si从故障中恢复后,先检查本机的日志,如果已收到<commit >,则提交,如果<abort >则回滚。如果是<yes>,则再向TC询问一下,确定下一步。如果什么都没有,则很可能在<prepare>阶段Si就崩溃了,因此需要回滚。 现如今实现基于两阶段提交的分布式事务也没那么困难了,如果使用java,那么可以使用开源软件atomikos来快速实现。不过但凡使用过的上述两阶段提交的同学都可以发现性能实在是太差,根本不适合高并发的系统。为什么? 两阶段提交涉及多次节点间的网络通信,通信时间太长!
“玩转”Java 系列
—————————————————————————————
3
事务时间相对于变长了,锁定的资源的时间也变长了,造成资源等待时间也增加好多! 正是由于分布式事务存在很严重的性能问题,大部分高并发服务都在避免使用,往往通过其他途径来解决数据一致性问题。
三、使用消息队列来避免分布式事务
如果仔细观察生活的话,生活的很多场景已经给了我们提示。比如在北京很有名的姚记炒肝点了炒肝并付了钱后,他们并不会直接把你点的炒肝给你,而是给你一张小票,然后让你拿着小票到出货区排队去取。为什么他们要将付钱和取货两个动作分开呢?原因很多,其中一个很重要的原因是为了使他们接待能力增强(并发量更高)。还是回到我们的问题,只要这张小票在,你最终是能拿到炒肝的。同理转账服务也是如此,当支付宝账户扣除1万后,我们只要生成一个凭证(消息)即可,这个凭证(消息)上写着“让余额宝账户增加 1万”,只要这个凭证(消息)能可靠保存,我们最终是可以拿着这个凭证(消息)让余额宝账户增加1万的,即我们能依靠这个凭证(消息)完成最终一致性。
1. 如何可靠保存凭证(消息)
有两种方法:
① 业务与消息耦合的方式
支付宝在完成扣款的同时,同时记录消息数据,这个消息数据与业务数据保存在同一数据库实例里(消息记录表表名为message)。
Begin transaction
update A set amount=amount-10000 where userId=1;
insert into message(userId, amount,status) values(1, 10000, 1);
End transaction
commit;
上述事务能保证只要支付宝账户里被扣了钱,消息一定能保存下来。当上述事务提交成功后,我们通过实时消息服务将此消息通知余额宝,余额宝处理成功后发送回复成功消息,支付宝收到回复后删除该条消息数据。
②业务与消息解耦方式
上述保存消息的方式使得消息数据和业务数据紧耦合在一起,从架构上看不够优雅,而且容易诱发其他问题。为了解耦,可以采用以下方式。1)支付宝在扣款事务提交之前,向实时消息服务请求发送消息,实时消息服务只记录消息数据,而不真正发送,只有消息发送成功后才会提交事务;2)当支付宝扣款事务被提交成功后,向实时消息服务确认发送。只有在得到确认发送指令后,实时消息服务才真正发送该消息;3)当支付宝扣款事务提交失败回滚后,向实时消息服务

—————————————————————————————
4
取消发送。在得到取消发送指令后,该消息将不会被发送;4)对于那些未确认的消息或者取消的消息,需要有一个消息状态确认系统定时去支付宝系统查询这个消息的状态并进行更新。为什么需要这一步骤,举个例子:假设在第2步支付宝扣款事务被成功提交后,系统挂了,此时消息状态并未被更新为“确认发送”,从而导致消息不能被发送。优点:消息数据独立存储,降低业务系统与消息系统间的耦合;缺点:一次消息发送需要两次请求;业务处理服务需要实现消息状态回查接口。
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;
if cnt==0 then
update B set amount=amount+10000 where userId=1;
insert into message_apply(msg_id) values(msg.msg_id);
End transaction
commit;

 

本页内容版权归属为原作者,如有侵犯您的权益,请通知我们删除。

Java编程之数组扩容 - 2016-03-23 14:03:51

一、背景        数组在实际的系统开发中用的越来越少了,我们只有在阅读某些开源项目时才会看到数组的使用。在Java中,数组与List、Set、Map等集合类相比,后者使用起来方便,但是在基本数据类型处理方面,数组还是占优势的,而且集合类的底层也都是通过数组实现的。        我们大家都知道,在Java中数组是定长的,一旦初始化以后,就不可以改变其长度,而这在实际应用中是不方便的。举例来说,如果要对班级学生的信息进行统计,因为我们不知道一个班级会有多少学生(随时有入学、退学、转学的学生),所以需要

java 编程学习总结 - 2016-03-23 14:03:45

Java编程中实现函数式编程的方法, 在JDK1.8+使用 lambda 表达式实现,在jdk1.8以前版本中使用 接口 的实现类实现缓存待调用的方法并在需要时调用。 函数式编程主要是简洁,方便处理异常,比较适合于异步编程。   关于最近的基于事件的技术: RxJava: https://github.com/ReactiveX/RxJava 相关文章: https://github.com/lzyzsd/Awesome-RxJava   EventBus:https://github.com/green
1.Core Java,就是Java基础、JDK的类库,很多童鞋都会说,JDK我懂,但是懂还不足够,知其然还要知其所以然,JDK的源代码写的非常好,要经常查看,对使用频繁的类,比如String,集合类(List,Map,Set)等数据结构要知道它们的实现,不同的集合类有什么区别,然后才能知道在一个具体的场合下使用哪个集合类更适合、更高效,这些内容直接看源代码就OK了 2.多线程并发编程,现在并发几乎是写服务端程序必须的技术,那对Java中的多线程就要有足够的熟悉,包括对象锁机制、synchronized关键
获取【下载地址】    QQ: 313596790   【免费支持更新】 支持三大数据库 mysql  oracle  sqlsever    更专业、更强悍、适合不同用户群体 【 新录针对本系统的 视频教程 ,手把手教开发一个模块,快速掌握本系统 】 A 代码生成器(开发利器);       增删改查的处理类,service层,mybatis的xml,SQL( mysql   和oracle)脚本,   jsp页面 都生成    就不用写搬砖的代码了,生成的放到项目里,可以直接运行 B 阿里巴巴数据库连
获取【下载地址】    QQ: 313596790   【免费支持更新】 支持三大数据库 mysql  oracle  sqlsever    更专业、更强悍、适合不同用户群体 【 新录针对本系统的 视频教程 ,手把手教开发一个模块,快速掌握本系统 】 A 代码生成器(开发利器);       增删改查的处理类,service层,mybatis的xml,SQL( mysql   和oracle)脚本,   jsp页面 都生成    就不用写搬砖的代码了,生成的放到项目里,可以直接运行 B 阿里巴巴数据库连
1、以递归方式从指定根节点,获取一个树型结构的数据! /**   * 根据指定的根节点从List中遍历一个子树出来;   *   * @param paramCollect:提供树节点数据的集合;   * @param paramDesList:产生的目标子数的集合;   * @param paramTreeRoot:树根元素;   * @return void:无返回值;   */ private static void getSubTree(ListString paramCollect,    Lis
        在 Effecitve Java 一书的第 48 条中提到了双重检查模式,并指出这种模式在 Java 中通常并不适用。该模式的结构如下所示: public Resource getResource() { if (resource == null) { synchronized(this){ if (resource==null) { resource = new Resource(); } } } return resource;}         该模式是对下面的代码改进: publi
     编程语言中一开始就有goto关键词了。事实上,goto起源于汇编语言的程序控制:“若条件A成立,则跳到这里;否则跳到那里”。       goto语句是在源码级上的跳转,这使其招致了不好的声誉。若一个程序总是从一个地方跳到另一个地方,还有什么办法能识别程序的控制流程呢?       其实真正的问题并不在于使用goto语句,而在于goto的滥用;而且在少数情况下,goto还是组织控制流程的最佳手段。       尽管goto仍是Java中的一个保留字,但在语言中并未使用它;Java没有goto。然
先来看两段代码块: 代码块一:   try {          System.out.println(1/0);          System.out.println(111);        } catch (Exception e) {          System.out.println(22);        } 执行结果:22 代码块二:        try {          System.out.println(1/0);        } catch (Exception e)
线程变量在以下情况下是安全的: 1. 线程私有变量:每一个线程都是独立的生成实例。例如: count是TestRunable的私有变量,a和b都是独立的线程实例,count是线程安全的。 public class TestRunable implements Runnable {private int count=0;@Overridepublic void run() { ...}}public static void main(String[] args) {TestRunable runable=n