分布式事物处理方式要点

来源:互联网 发布:网络黑分提现 编辑:程序博客网 时间:2024/05/29 02:13

也希望加入我的QQ群一起交流      IT互联网技术交流 368614849


1,柔性事物,二阶段2PC型,补偿型,异步确保型,最大努力通知型。

      2PC适合场景:客户账,收费

异步确保型:会计性,资金订单,通知数据。

核心交易数据分库并分表,消费记录数据分库分表,商户交易数据分库分表。

        保持多个维度的数据集群可以使用MQ异步同步,MQ异步也会导致数据不一致,则引入实时监控服务,实时计算2个维度集群差异,作一致性同步。


2,事务型MQ,在本地逻辑处理前发送MQ,本地逻辑执行成功后,提交commit MQ消息,MQ失败则重试直到成功。也可用MQ定时补偿,单独补偿任务表出来,这样显得更为清晰。

         一致性重试需要解决幂等,可以采用乐观锁,version号去重,乐观锁失败证明版本上升了,失败了走查version再更新,设计较多的去重的可以单独一张表做去重服务,利用DB唯一索引特性,或采用zookeeper和redis分布式锁lock,类似多线程并发锁。

避免XA事务,通信时间较长资源锁定等待久。利用异步做最终一致性方式较好,MQ如支付宝账号扣钱之后只生成1个MQ即可,只要该消息可靠保存,依靠该消息完成最终一致性。

3,微信支付采用postgreSQL数据库,XS在事务管理机制会成为系统瓶颈,全局事务管理器会限制系统的扩展规模,每个request进来后协调节点都会向全局事务管理器GTM申请必须的全局事务ID,全局快照信息,并把这些信息随着SQL本身一起发往DN节点执行,只有主DN才会获取gxid,备DN没有自己的gxid。微信改进后不再从全局事务管理器GTM获取gxid和全局快照信息,每个节点使用自己的本地xid和快照,备DN也可以提供只读服务,先对分布式列计算hash,然后用这个值对集群中的节点个数取模决定哪个节点。

4,一致性补偿服务,需要先要确定失败的步骤和状态,确定补偿的范围,再能提供补偿使用的业务数据和业务流水等更多要素,当客户1个预定请求到达时,补偿协调服务为请求生成1个全局唯一的业务流水号,并在调用各个工作服务的的同时完整记录完整的状态,记录调用的业务流水,记录调用bookhotel业务流水和bookTrain流水再调用服务等,补偿过程服务同样也需要重试机制,要求本身也具有幂等性,由于业务因素失败不需要重试而是数据库等记录异常手动处理,因网络原因则需要重试,500错误的则等一段时间再重试。

5,dubbo分布式事务,使用异常exception抛出,A服务调用B服务,B服务调用C服务,C服务出现异常会抛给B,B异常也会抛给A,C回滚则B也回滚,B回滚则A也回滚。MQ最终一致性可以利用系统控制MQ的消息状态如预备、发送、提交、成功、失败、重试中等。TCC补偿事务则分为try confiirm cancel三个途径,try对业务检测及资源预留confirm做业务提交,cancel做业务回滚,Try节点可能存在失败则调用cancel释放资源。 最大努力通知型多用在第三方系统通讯时,如调用微信支付后结果通知,如通过MQ发送http请求,设置最大通知数,不保证成功,提供结果查询接口,多系统之间同步,Dubbo之间调用也可采用最大通知反馈。

6,用户启动1个事务,transactionManager.begin(),事务管理器在当前线程初始化1个事务实例,threadLocal.set( new TransationImpl()  ),用户调用jdbc/jms/dubbo请求,请求内部初始化1个XAResource实例,XAResource xsResource = new XAResourceImpl();  jdbc/jms/dubbo从当前线程获取事务,transation将当前XAResource注册到事务中,transation.enlistResource(xsResource),用户提交1个事务:transationManager.commit()。事务for循环调用所有注册的XAResource的2阶段提交,Xid xid = new XidImpl(); for(XAResource x : xaResources){ x.prepare(ID), xa.commit(xid, true);  还有些异常流程如rollback和forget等,距离TransationManager tm = new TransationManager (); JNDI并行lockup方式获取,tm.begin() 确定事务, jdbcConn执行update sql,DB写入binlog,但不更新表,jmsMQ发送消息msg但是不执行提交,dubbo invoke调用远程服务,provider缓存请求不执行提交,所有提交后捕获异常rollback。

7,四个dubbo服务,2个失败则等4个任务全执行完毕后回调,定时任务检测4个dubbo服务数据是否一致,一致则全部置为成功,不一致则全部置为失败 。

8,发红包,推送,统计,分订单,支付,网银第三方等异步操作使用最终一致性,重试幂等等,如退款遇到故障会延时退款,保证最终会被完成,不过需要判断退款保持幂等性,如果发生退款失败则放入延时队列,稍后重试。退款操作最终一定会完成。

9,很多交易环节采用MQ来流转,业务操作上先将订单持久化到同一个事务里面,共享订单操作的持久化DB连接,在这个DB连接中将MQ持久化到同一个实例的消息库中,然后在事务提交之江消息发送到MQ server,事务回滚后消息不会发出去,订单持久化,MQ持久化到DB,事务提交MQ发出去,发送成功则删除DB中的消息表记录,如果发送失败,后台有1个任务会将消息表中发送失败的消息重新发送,达到最终一致性,或者producer是发或者补发,提供1个broker反查接口。也可以采用spring等自带的MQ事务进行延时重试等。幂等可以采用状态机,或者携带版本号,和DB中的进行对比,后台扫描制定的时间之内的DB消息表,server段据消息ID做1个去重。

10,A,前者1个嵌入客户端应用的jar负责事务,数据写入处理,后者独立系统,负责异常、事务恢复、2PC需参与者需要一直到持有资源直到整个分布式事务结束,千万级,性能很差,1个业务服务若干个从,主发起完成整个业务,从作TCC型业务操作,业务活动管理器控制业务活动一致性,登记业务活动中的操作,在活动提交时确认所有的2阶段事务的操作,业务取消时可以调用所有2阶段事务cancel操作。

 B,发起支付,增加用户积分,调用dubbo支付扣款,支付状态处理,成功则提交事务,失败则回调订单积分,未知则等到支付系统回调状态。

C,用户信息变更同步至各业务系统activeMQ中,订阅方式保证单次消费。

D,非实时非强一致性的业务采用MQ,可以重试和事务消息。

E,事务提交之前插入1条消息记录,事务提交之后再一步将该消息发出去,成功则将消息记录删除,失败则保留该消息记录,会有1个服务不断扫描消息重新发送。

11,跨银行转账,2个异地service服务,构成1个完整事务,事务补偿即链中每个正向事务必须有1个完全符合回滚的逆向事务,调用本地取款,成功并返回数据已持久化,然后调用异地存款,如失败则调用本地逆向服务,如本地存服务调用失败则必须重试,约定重试次数仍然失败则必须log到完整的不一致信息(也可以MQ),为了避免手工写大量的代码,考虑1个通用的事务管理器,实现事务链和上下文的管理。任务1个正向和逆向均在事务管理器上注册,由事务管理器接管所有在事务补偿和回滚操作。

消息一致性调用本地取款服务,然后发送MQ,MQ消费对消息解析后调用异步存款服务,失败则重试,不得已本地取款一般不可逆。(可以存在真空期时间内事务最终一致性)。幂等性重试重复调用多次产生的业务结果和1次调用一样,因为调用失败异常必须考虑。

12,一致性结果可能成功,失败或者不确定,成功就可以把记录的东西清理掉,对于失败和不确定,可以依靠定时方式把所有失败的事情重新尝试一遍,直到成功为止。系统在A扣钱成功下,把要给B通知这件事记录到数据库中,为了保证可靠性可以把通知B系统加钱和扣钱成功这2件事维护在本地1个事务里面,本地事务维护业务变化和通知消息一起落地(失败则一起回滚),然后RPC到达broker,在broker成功落地后RPC返回成功,本地消息记录可以删除,否则本地消息记录一致靠定时器任务轮询不断重发,这样就保证了消息可靠落地broker。broker一致发消息给consumer,直到consumer确认消费成功,先不理会重复消费,以来状态机做版本号去重,实现最终一致性。

13,顺带记录下spring的5中事务方式,每个bean都有1个事务代理;所有bean共享1个事务代理;使用拦截器;使用tx标签配置的拦截器;全注解方式。

14,创建不可见订单,同时锁券,失败则发送废弃订单消息到MQ,然后回库存和回券,锁券成功则订单变为可见。支付系统回调请求会重试到MQ,交易系统会更新交易主子菜单,交付单,减库存,加销量,送券等,如果业务失败则放入重试表,作重试。其中资金作为发起方保证重试和MQ可达。

15,最终一致性,T1发送half消息,T2存储half消息,T3返回消息入库结果,T3业务操作,S1定时检查未提交的消息,S2提交回滚,S3提交更新DB/回滚删除消息。

16,XA事务:1个数据库只将其操作(可恢复)映射到全局事务中(交易中间件),由它通知协调相关数据库的提交和回滚

2PC(二阶段):参与者将自己操作成败通知协调者,再由协调者根据所有参与者反馈情况情报决定参与者是否需要提交操作还是终止。2阶段为准备阶段和提交阶段。分为三个步骤,A协调者节点向所有参与者询问是否可执行提交,并等待所有参与者响应  B参与者节点询问发起为止所有事务操作,并将undo和redo写入日志  C各参与者节点响应协调者发起的询问,如果参与者节点事务操作实际执行成功,则返回1个同意,参与者操作失败则返回1个终止。


17,Sagas长事务

在Sagas事务模型中,一个长事务是由一个预先定义好执行顺序的子事务集合和他们对应的补偿子事务集合组成的。典型的一个完整的交易由T1、T2、……、Tn等多个业务活动组成,每个业务活动可以是本地操作、或者是远程操作,所有的业务活动在Sagas事务下要么全部成功,要么全部回滚,不存在中间状态。

Sagas事务模型的实现机制:

  • 每个业务活动都是一个原子操作;

  • 每个业务活动均提供正反操作;

  • 任何一个业务活动发生错误,按照执行的反顺序,实时执行反操作,进行事务回滚;

  • 回滚失败情况下,需要记录待冲正事务日志,通过重试策略进行重试;

  • 冲正重试依然失败的场景,提供定时冲正服务器,对回滚失败的业务进行定时冲正;

  • 定时冲正依然失败的业务,等待人工干预;

Sagas长事务模型支持对数据一致性要求比较高的场景比较适用,由于采用了补偿的机制,每个原子操作都是先执行任务,避免了长时间的资源锁定,能做到实时释放资源,性能相对有保障。

Sagas长事务方式如果由业务去实现,复杂度与难度并存。在我们实际使用过程中,开发了一套支持Sagas事务模型的框架来支撑业务快速交付。

开发人员:业务只需要进行交易编排,每个原子操作提供正反交易;

配置人员:可以针对异常类型设定事务回滚策略(哪些异常纳入事务管理、哪些异常不纳入事务管理);每个原子操作的流水是否持久化(为了不同性能可以支持缓存、DB、以及扩展其它持久化方式);以及冲正选项配置(重试次数、超时时间、是否实时冲正、定时冲正等);

Sagas事务框架:提供事务保障机制,负责原子操作的流水落地,原子操作的执行顺序,提供实时冲正、定时冲正、事务拦截器等基础能力;

Sagas框架的核心是IBusinessActivity、IAtomicAction。IBusinessActivity完成原子活动的enlist()、delist()、prepare()、commit()、rollback()等操作;IAtomicAction主要完成对状态上下文、正反操作执行。

限于文章篇幅,本文不对具体实现做详述;后面找时间可以详细介绍基于Sagas长事务模型具体的实现框架。Sagas长事务需要交易提供反操作,支持事务的强一致性,由于没有在整个事务周期内锁定资源,对性能影响较小,适合对数据要求比较高的场景中使用。


18,可靠事件模式(本地事件表、外地事件表)

可靠事件模式属于事件驱动架构,当某件重要事情发生时,例如更新一个业务实体,微服务会向消息代理发布一个事件。消息代理会向订阅事件的微服务推送事件,当订阅这些事件的微服务接收此事件时,就可以完成自己的业务,也可能会引发更多的事件发布。

可靠事件模式在于保证可靠事件投递和避免重复消费,靠事件投递定义为: 

  • 每个服务原子性的业务操作和发布事件;

  • 消息代理确保事件传递至少一次;避免重复消费要求服务实现幂等性。 

基于事件模式,需要重点考虑的是事件的可靠到达,在我们产品实际支持过程中,通常有本地事件表、外部事件表两种模式:

1、本地事件表方法将事件和业务数据保存在同一个数据库中,使用一个额外的“事件恢复”服务来恢复事件,由本地事务保证更新业务和发布事件的原子性。考虑到事件恢复可能会有一定的延时,服务在完成本地事务后可立即向消息代理发布一个事件。

  1. 微服务在同一个本地事务中记录业务数据和事件;

  2. 微服务实时发布一个事件立即通知关联的业务服务,如果事件发布成功立即删除记录的事件;

  3. 事件恢复服务定时从事件表中恢复未发布成功的事件,重新发布,重新发布成功才删除记录的事件;

其中第2条的操作主要是为了增加发布事件的实时性,由第三条保证事件一定被发布。本地事件表方式业务系统和事件系统耦合比较紧密,额外的事件数据库操作也会给数据库带来额外的压力,可能成为瓶颈。

2、外部事件表方法将事件持久化到外部的事件系统,事件系统需提供实时事件服务以接受微服务发布事件,同时事件系统还需要提供事件恢复服务来确认和恢复事件。

  1. 业务服务在事务提交前,通过实时事件服务向事件系统请求发送事件,事件系统只记录事件并不真正发送;

  2. 业务服务在提交后,通过实时事件服务向事件系统确认发送,事件得到确认后,事件系统才真正发布事件到消息代理;

  3. 业务服务在业务回滚时,通过实时事件向事件系统取消事件;

  4. 如果业务服务在发送确认或取消之前停止服务了怎么办呢?事件系统的事件恢复服务会定期找到未确认发送的事件向业务服务查询状态,根据业务服务返回的状态决定事件是要发布还是取消;

该方式将业务系统和事件系统独立解耦,都可以独立伸缩。但是这种方式需要一次额外的发送操作,并且需要发布者提供额外的查询接口。

基于可靠事件的事务保障模式可以有很多的变种实现,比如对消息可靠性不高的话,可以将本地表的方式换做缓存方式。为了提高消息投递的效率,可以将多次消息合并为投递模式。为了提供强一致性的事务保障,甚至可以将本地消息表持久化(保障发方法消息可靠落地)+远程消息表持久化(保障接收方消息可靠落地)结合的模式。

在我们的流程产品中针对业务和流程的分布式事务解决方案就采用了多次消息合并投递+本地缓存+远程消息表持久化的模式,接下来为大家介绍具体的使用方式。

使用场景

在实际业务项目中通常采用业务与流程分布式部署的模式,业务系统通过远程接口访问流程引擎,业务数据同流程数据存放到各自的数据库中。

在这种场景中,如果业务系统的流程操作和业务操作交叉在一起,当流程操作成功,而业务操作失败时,就会造成业务回滚,而流程在引擎端已经创建,导致业务系统和流程引擎状态不一致。

在业务应用中对一个事务中的流程操作采用本地缓存+批量投递+远程落地的模式(如果需要在客户端确保消息可靠性,可以将本地缓存换成本地表的方式);在流程引擎端在消息投递来之后,做了消息表落地的工作,保障可靠执行。在我们流程产品中流程引擎对外提供的客户端提供了统一的分布式事务API,和使用传统本地事务一样进行操作,保证了透明性,简化开发人员的复杂度。分布式事务API支持两种协议模式:

  1. HTTP + 二进制序列化的模式

  2. WebService模式

后续我们会增加Restful风格的接口。

可靠事件模式在互联网公司中有着较大规模的应用,该方式适合的业务场景非常广泛,而且能够做到数据的最终一致性,缺点是该模式实现难度较大,依赖数据库实现可靠性,在高并发场景下可能存在性能瓶颈,需要在公司层面搭建一套标准的可靠事件框架来支撑。

可靠事件模式(非事务消息、事务消息)

可靠事件模式的事件通知可以采用消息的模式来实现,其实现原理和本地事件表、外部事件表一致,本文就不在详述。目前常用的消息框架ActiveMQ、RabbitMQ、Kafka、RocketMQ可以用来作为消息投递的渠道。注意:Kafka通常不适合,因为Kafka的设计存在丢消息的场景。

目前市面上支持事务的消息产品比较少,RocketMQ虽然实现了可靠的事务模式,但并没有开源出来、没有开源出来、没有开源出来,顺便说一下国内的开源有太多需要改进的空间(关键点不开源,开源后没有持续的投入)。


20,

如B向S转账,那我们到底是先发送消息,还是先执行扣款操作?好像都有问题,如果先发消息,扣款操作失败,那么S账号就会多出一笔钱,如果先扣款操作再发MQ,那么扣款成功但是MQ发失败,S收不到钱。

RocketMQ第一阶段发送prepared消息时,会拿到消息的地址,第二步执行本地事务,第三阶段通过第一阶段拿到的地址去访问消息,并修改状态,如果确认消息发送失败了怎么办,RocketMQ会定期扫描消息集群中的事务消息,这时候发现了prepared消息,它会定向消息发送者确认。B的钱是否减少了呢?减少是回滚还是继续发MQ确认呢?RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息,这样就保证了发送和本地事务同时成功和同时失败。


21,

(mycat南哥作)先扩大点范围来说,标准XA分布式事务由于性能太烂,遭到互联网同仁的一致性抛弃,大家纷纷造轮子,TCC啊消息队列等等。常见的一种思路:分布式事务拆开,比如先用本地事务保证先完成一部分,剩下用mq去做,可以重试,这个就需要应用保证幂等性了。还有一种先执行,出了异常进行补偿或者反向补偿。这些对开发者就苦了,要自己去保证幂等性等等,一个不小心就可能导致不一致情况出现。
1.用raft paxos算法来实现,这种太高端了,坐等大牛来解答。
2.用事务表加消息队列方式,坐等阿里大牛来答,不过听说他们SDK都混淆了,估计愿意分享细节可能性不大哈。
3.binlog反向补偿,原理利用binlog包含前后数据方式来进行反向补偿
。如果能从DB端实现很多事情就比较好办了,比如oracle 11g就实现了可将已经commit的事务撤销的功能,mysql如果能实现或者类似flashback就好搞多了。京东金融的义林兄在他们团队实现了这种方式,想了解更多,可以去请教义林大牛。

mycat事务模型一个问题是commit的时候由于发生到多个节点commit,会可能出现部分节点commit不成功,mysql commit成功的事务又没法撤销(还是oracle 牛,commit成功的事务也可以撤销,哪位大牛可以去mysql里也实现一把^O^ )。这个时候会有不一致的情况,考虑到只是commit时间极短,发生概率不大,但是也很多人心中的痛。
mycat的ER模型等功能一部分设计目的就是解决分布式事务的问题,本质上其实是避免跨库事务,通过应用上的合理设计转换成单机事务。


22,(望舒作)
阿里云的分布式事务中间价实现猜想

1. TXC 是基于 rocketmq ,Netty 来实现的。

2. 实现了一个 以rocketmq 和 操作库 的 tx_undo_log 做日志存储 ,Netty RPC 做,事务控制的 二阶段提交协议。

3. 在事务提交阶段,会同时开启一个事务,锁住 分布式事务的参与记录,等待事务合并后释放。或者有节点提交错误时回滚。

一个事务的处理流程我认为可能是这样的

1. 启动之后 启动RPC 连接上事务控制服务

2. 包装了 DataSource ,和 jdbc 的 Connection ,在进行 DML 操作之前,先使用 解析到对应表的 ,使用 SELECT xxx from xx for update 语句 构造 undo 语句。

操作完了后,发送一个消息,通知事务控制服务,可以本事务可以提交了。

3. 判断自己外部 是否还有事务(即是自己是不是最外层事务)。如果自己不是最外层事务,则返回(不阻塞线程), 如果自己是最外层事务,则 等待事务 提交(当前线程阻塞).

4. (另外的线程)Rpc 接受到 事务控制器 提交事务命令后。获取合并事务。如果成功(柱塞住参与事务的表记录),回复成功消息,失败回复失败消息(回滚,不再锁住表记录)。

5. 根据事务提交情况,控制器会 下发,完成事务,或者回滚事务的命令,由RPC线程处理, 如果成功则释放,锁住的表记录,事务完成, 最外层业务线程的阻塞返回结束,

如果失败,则执行 uodo 日志里面的sql ,回滚操作,释放表记录, 最外层业务线程阻塞返回抛出 事务异常。

现在没想明白的问题:

1. 在事务提交阶段,提交后表记录,的锁已经释放,怎么样才能保证该行数据能被后续的锁操作锁住,业务在大量事务操作的时候,可能记录先被其他的事务抢到了。

我把我的猜想跟我同事说了后他觉得这个 做法有点像

ebay经典的BASE (basically available, soft state, eventually consistent)方案


23,幂等性

A,使用乐观锁update   set version = version+1 where version=xxversion         B,如果设计去重的地方比较多,例如多种不同单据都需要去重,搞一张去重表,在入库时,插入去重表,唯一索引特性保持唯一      C,分布式锁,在不同系统之间做同步唯一    D,例在form表单增加一个UUID唯一标识,然后到服务端根据这个UUID去重     E,业务单据上有个状态,如果状态机已经处于下一个状态,这时候来了上一个状态的变更,理论上是不能够变更的,保证了状态机的幂等性。 




0 0
原创粉丝点击