一款蛋糕食品类企业手机网站源码,wordpress英文别名,北京广告公司,免费网页源代码网站前阵子从支付宝转账1万块钱到余额宝#xff0c;这是日常生活的一件普通小事#xff0c;但作为互联网研发人员的职业病#xff0c;我就思考支付宝扣除1万之后#xff0c;如果系统挂掉怎么办#xff0c;这时余额宝账户并没有增加1万#xff0c;数据就会出现不一致状况了。 …前阵子从支付宝转账1万块钱到余额宝这是日常生活的一件普通小事但作为互联网研发人员的职业病我就思考支付宝扣除1万之后如果系统挂掉怎么办这时余额宝账户并没有增加1万数据就会出现不一致状况了。
上述场景在各个类型的系统中都能找到相似影子比如在电商系统中当有用户下单后除了在订单表插入一条记录外对应商品表的这个商品数量必须减1吧怎么保证在搜索广告系统中当用户点击某广告后除了在点击事件表中增加一条记录外还得去商家账户表中找到这个商家并扣除广告费吧怎么保证等等相信大家或多或多少都能碰到相似情景。
本质上问题可以抽象为当一个表数据更新后怎么保证另一个表的数据也必须要更新成功。
1 本地事务
还是以支付宝转账余额宝为例假设有
支付宝账户表AiduserIdamount余额宝账户表BiduserIdamount用户的userId1
从支付宝转账1万块钱到余额宝的动作分为两步
1支付宝表扣除1万update A set amountamount-10000 where userId1;2余额宝表增加1万update B set amountamount10000 where userId1;
如何确保支付宝余额宝收支平衡呢
有人说这个很简单嘛可以用事务解决。 BegintransactionupdateA setamountamount-10000where userId1;updateB setamountamount10000where userId1;Endtransactioncommit; 非常正确如果你使用spring的话一个注解就能搞定上述事务功能。 Transactional(rollbackForException.class)publicvoid
update() {updateATable();
//更新A表updateBTable();
//更新B表} 如果系统规模较小数据表都在一个数据库实例上上述本地事务方式可以很好地运行但是如果系统规模较大比如支付宝账户表和余额宝账户表显然不会在同一个数据库实例上他们往往分布在不同的物理节点上这时本地事务已经失去用武之地。
既然本地事务失效分布式事务自然就登上舞台。
2 分布式事务—两阶段提交协议
两阶段提交协议Two-phase Commit2PC经常被用来实现分布式事务。一般分为协调器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(http://www.atomikos.com/)来快速实现。
不过但凡使用过的上述两阶段提交的同学都可以发现性能实在是太差根本不适合高并发的系统。为什么
1两阶段提交涉及多次节点间的网络通信通信时间太长2事务时间相对于变长了锁定的资源的时间也变长了造成资源等待时间也增加好多
正是由于分布式事务存在很严重的性能问题大部分高并发服务都在避免使用往往通过其他途径来解决数据一致性问题。
3 使用消息队列来避免分布式事务
如果仔细观察生活的话生活的很多场景已经给了我们提示。
比如在北京很有名的姚记炒肝点了炒肝并付了钱后他们并不会直接把你点的炒肝给你而是给你一张小票然后让你拿着小票到出货区排队去取。为什么他们要将付钱和取货两个动作分开呢原因很多其中一个很重要的原因是为了使他们接待能力增强并发量更高。
还是回到我们的问题只要这张小票在你最终是能拿到炒肝的。同理转账服务也是如此当支付宝账户扣除1万后我们只要生成一个凭证消息即可这个凭证消息上写着“让余额宝账户增加 1万”只要这个凭证消息能可靠保存我们最终是可以拿着这个凭证消息让余额宝账户增加1万的即我们能依靠这个凭证消息完成最终一致性。
3.1 如何可靠保存凭证消息
有两种方法
3.1.1 业务与消息耦合的方式
支付宝在完成扣款的同时同时记录消息数据这个消息数据与业务数据保存在同一数据库实例里消息记录表表名为message。 BegintransactionupdateA setamountamount-10000where userId1;insertinto message(userId, amount,status) values(1,
10000,
1);Endtransactioncommit; 上述事务能保证只要支付宝账户里被扣了钱消息一定能保存下来。
当上述事务提交成功后我们通过实时消息服务将此消息通知余额宝余额宝处理成功后发送回复成功消息支付宝收到回复后删除该条消息数据。
3.1.2 业务与消息解耦方式
上述保存消息的方式使得消息数据和业务数据紧耦合在一起从架构上看不够优雅而且容易诱发其他问题。为了解耦可以采用以下方式。
1支付宝在扣款事务提交之前向实时消息服务请求发送消息实时消息服务只记录消息数据而不真正发送只有消息发送成功后才会提交事务
2当支付宝扣款事务被提交成功后向实时消息服务确认发送。只有在得到确认发送指令后实时消息服务才真正发送该消息
3当支付宝扣款事务提交失败回滚后向实时消息服务取消发送。在得到取消发送指令后该消息将不会被发送
4对于那些未确认的消息或者取消的消息需要有一个消息状态确认系统定时去支付宝系统查询这个消息的状态并进行更新。为什么需要这一步骤举个例子假设在第2步支付宝扣款事务被成功提交后系统挂了此时消息状态并未被更新为“确认发送”从而导致消息不能被发送。
优点消息数据独立存储降低业务系统与消息系统间的耦合
缺点一次消息发送需要两次请求业务处理服务需要实现消息状态回查接口。
3.2 如何解决消息重复投递的问题
还有一个很严重的问题就是消息重复投递以我们支付宝转账到余额宝为例如果相同的消息被重复投递两次那么我们余额宝账户将会增加2万而不是1万了。
为什么相同的消息会被重复投递比如余额宝处理完消息msg后发送了处理成功的消息给支付宝正常情况下支付宝应该要删除消息msg但如果支付宝这时候悲剧的挂了重启后一看消息msg还在就会继续发送消息msg。
解决方法很简单在余额宝这边增加消息应用状态表message_apply通俗来说就是个账本用于记录消息的消费情况每次来一个消息在真正执行之前先去消息应用状态表中查询一遍如果找到说明是重复消息丢弃即可如果没找到才执行同时插入到消息应用状态表同一事务。 foreach
msg inqueueBegintransactionselectcount(*) ascnt from message_apply where msg_idmsg.msg_id;ifcnt0thenupdateB setamountamount10000where userId1;insertinto message_apply(msg_id) values(msg.msg_id);Endtransactioncommit; ebay的研发人员其实在2008年就提出了应用消息状态确认表来解决消息重复投递的问题http://queue.acm.org/detail.cfm?id1394128。 补充:
之前看多阿里大神程立的一个关于分布式事务的文档目前使用较多的分布式事务解决方案有几种 一、结合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方案是目前公认的较为理想的分布式事务解决方案各大电商都在应用这一方案。种方式适合的业务场景广泛而且比较可靠。不过这种方式技术实现的难度比较大。目前主流的开源MQActiveMQ、RabbitMQ、Kafka均未实现对事务消息的支持所以需二次开发或者新造轮子。 参考文献
Dan Pritchett《Base: An Acid Alternative》程立大规模SOA系统中的分布式事务处理《Mysql两阶段提交》
转自:http://blog.csdn.net/forearrow/article/details/47778497