文档章节

分布式事务的一个CASE

恋空御月
 恋空御月
发布于 2016/07/14 15:15
字数 2587
阅读 18
收藏 5
点赞 1
评论 0

前阵子从支付宝转账1万块钱到余额宝,这是日常生活的一件普通小事,但作为互联网研发人员的职业病,我就思考支付宝扣除1万之后,如果系统挂掉怎么办,这时余额宝账户并没有增加1万,数据就会出现不一致状况了。

上述场景在各个类型的系统中都能找到相似影子,比如在电商系统中,当有用户下单后,除了在订单表插入一条记录外,对应商品表的这个商品数量必须减1吧,怎么保证?!在搜索广告系统中,当用户点击某广告后,除了在点击事件表中增加一条记录外,还得去商家账户表中找到这个商家并扣除广告费吧,怎么保证?!等等,相信大家或多或多少都能碰到相似情景。

本质上问题可以抽象为:当一个表数据更新后,怎么保证另一个表的数据也必须要更新成功。

1、 本地事务

还是以支付宝转账余额宝为例,假设有

  • 支付宝账户表: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来快速实现。

 

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

 

  1. 两阶段提交涉及多次节点间的网络通信,通信时间太长!

     

  2. 事务时间相对于变长了,锁定的资源的时间也变长了,造成资源等待时间也增加好多!

 

正是由于分布式事务存在很严重的性能问题,大部分高并发服务都在避免使用,往往通过其他途径来解决数据一致性问题。

 

3 、使用消息队列来避免分布式事务

 

如果仔细观察生活的话,生活的很多场景已经给了我们提示。

 

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

 

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

 

3.1 如何可靠保存凭证(消息)

 

有两种方法:

 

3.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;

 

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

 

当上述事务提交成功后,我们通过实时消息服务将此消息通知余额宝,余额宝处理成功后发送回复成功消息,支付宝收到回复后删除该条消息数据。

 

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;

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;

 

ebay的研发人员其实在2008年就提出了应用消息状态确认表来解决消息重复投递的问题。

本文转载自:https://yq.aliyun.com/articles/57665?spm=5176.100239.bloglist.5.thMb66

共有 人打赏支持
恋空御月
粉丝 8
博文 130
码字总数 91979
作品 0
浦东
程序员
蚂蚁金服:消息队列事务型消息原理浅析

在金融级分布式架构的领域内,消息队列是普遍被应用的异步通信产品,本文主要分为以下几个小结,循序渐进的对消息队列产品事务型消息设计原理进行分析和阐述: 消息队列简介 消息队列应用实例...

技术小能手 ⋅ 05/23 ⋅ 0

sharding-jdbc 事务支持部分观后感

官网说明支持的事务 图就不上了,官网有http://shardingjdbc.io/docs/02-guide/transaction/ 几个关键字,, 开始撸代码官网demo,柔性事务管理器,见名知意。功能同spring,事务管理器大同小异...

脑丨残 ⋅ 01/15 ⋅ 0

分布式事务系列(3.2)jotm分布式事务源码分析

1 系列目录 - 分布式事务系列(开篇)提出疑问和研究过程- 分布式事务系列(1.1)Spring事务管理器PlatformTransactionManager源码分析- 分布式事务系列(1.2)Spring事务体系- 分布式事务系...

乒乓狂魔 ⋅ 2015/05/28 ⋅ 2

一篇超详细的 TiKV 招聘广告

上周写了一篇 PD 的招聘广告,想想还是应该写一下 TiKV,毕竟谁叫 TiKV 也缺人了。这里,我仍然会详细的说明 TiKV 主要是干啥的,以及我们要做的事情,这样你大概就知道我们做的东西靠不靠谱...

siddontang ⋅ 01/13 ⋅ 0

【分布式事务系列六】jotm分布式事务源码分析

分布式事务 【分布式事务系列一】提出疑问和研究过程 【分布式事务系列二】Spring事务管理器PlatformTransactionManager 【分布式事务系列三】Spring的事务体系 【分布式事务系列四】分布式事...

陶邦仁 ⋅ 2015/12/18 ⋅ 0

深度揭秘腾讯云新一代企业级HTAP数据库TBase核心概念

腾讯云PostgreSQL-XZ(PGXZ)经过公司内部多年业务的打磨,在2017年改名为TBase后,正式对外推出,目前已在政务、医疗、公安、消防、电信、金融等行业等行业的解决方案中大量应用。TBase以其...

wx5aea96220aa4a ⋅ 05/10 ⋅ 0

MySQL · 特性分析 · MySQL 5.7 外部XA Replication实现及缺陷分析

MySQL 5.7 外部XA Replication实现及缺陷分析 MySQL 5.7增强了分布式事务的支持,解决了之前客户端退出或者服务器关闭后prepared的事务回滚和服务器宕机后binlog丢失的情况。 为了解决之前的...

阿里云RDS-数据库内核组 ⋅ 2017/11/07 ⋅ 0

干货|一篇文章带你学习分布式事务

小蚂蚁说: 分布式事务是企业集成中的一个技术难点,也是每一个分布式系统架构中都会涉及到的一个东西,特别是在这几年越来越火的微服务架构中,几乎可以说是无法避免,本文就围绕分布式事务...

兔子酱 ⋅ 05/28 ⋅ 0

分布式数据库事务

在分布式数据库环境中,一个数据库事务可以更新多个场地上的数据,这种数据库事务称为分布式事务。   分布式事务必须满足传统事务的特性,即原子性,一致性,分离性和持久性。但是分布式事...

pricker ⋅ 2015/07/18 ⋅ 0

Java中的事务——JDBC事务和JTA事务

我的博客中曾经关于事务有过很多讨论,之前的事务介绍基本都是数据库层面的事务,本文来介绍一下J2EE中和事务相关的内容,在阅读本文之前,希望读者对分布式有一定的了解。 关于事务的基础知...

小神神的大草原 ⋅ 2016/10/20 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

个人博客的运营模式能否学习TMALL天猫质量为上?

心情随笔|个人博客的运营模式能否学习TMALL天猫质量为上? 中国的互联网已经发展了很多年了,记得在十年前,个人博客十分流行,大量的人都在写博客,而且质量还不错,很多高质量的文章都是在...

原创小博客 ⋅ 今天 ⋅ 0

JavaScript零基础入门——(十一)JavaScript的DOM操作

JavaScript零基础入门——(十一)JavaScript的DOM操作 大家好,欢迎回到我们的JavaScript零基础入门。最近有些同学问我说,我讲的的比书上的精简不少。其实呢,我主要讲的是我在开发中经常会...

JandenMa ⋅ 今天 ⋅ 0

volatile和synchronized的区别

volatile和synchronized的区别 在讲这个之前需要先了解下JMM(Java memory Model :java内存模型):并发过程中如何处理可见性、原子性、有序性的问题--建立JMM模型 详情请看:https://baike.b...

MarinJ_Shao ⋅ 今天 ⋅ 0

深入分析Kubernetes Critical Pod(一)

Author: xidianwangtao@gmail.com 摘要:大家在部署Kubernetes集群AddOn组件的时候,经常会看到Annotation scheduler.alpha.kubernetes.io/critical-pod"="",以表示这是一个关键服务,那你知...

WaltonWang ⋅ 今天 ⋅ 0

原子性 - synchronized关键词

原子性概念 原子性提供了程序的互斥操作,同一时刻只能有一个线程能对某块代码进行操作。 原子性的实现方式 在jdk中,原子性的实现方式主要分为: synchronized:关键词,它依赖于JVM,保证了同...

dotleo ⋅ 今天 ⋅ 0

【2018.06.22学习笔记】【linux高级知识 14.4-15.3】

14.4 exportfs命令 14.5 NFS客户端问题 15.1 FTP介绍 15.2/15.3 使用vsftpd搭建ftp

lgsxp ⋅ 今天 ⋅ 0

JeeSite 4.0 功能权限管理基础(Shiro)

Shiro是Apache的一个开源框架,是一个权限管理的框架,实现用户认证、用户授权等。 只要有用户参与一般都要有权限管理,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户...

ThinkGem ⋅ 昨天 ⋅ 0

python f-string 字符串格式化

主要内容 从Python 3.6开始,f-string是格式化字符串的一种很好的新方法。与其他格式化方式相比,它们不仅更易读,更简洁,不易出错,而且速度更快! 在本文的最后,您将了解如何以及为什么今...

阿豪boy ⋅ 昨天 ⋅ 0

Python实现自动登录站点

如果我们想要实现自动登录,那么我们就需要能够驱动浏览器(比如谷歌浏览器)来实现操作,ChromeDriver 刚好能够帮助我们这一点(非谷歌浏览器的驱动有所不同)。 一、确认软件版本 首先我们...

blackfoxya ⋅ 昨天 ⋅ 0

线性回归原理和实现基本认识

一:介绍 定义:线性回归在假设特证满足线性关系,根据给定的训练数据训练一个模型,并用此模型进行预测。为了了解这个定义,我们先举个简单的例子;我们假设一个线性方程 Y=2x+1, x变量为商...

wangxuwei ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部