3

如何选择最适合你的分布式事务方案

 3 years ago
source link: https://segmentfault.com/a/1190000040468762
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

之前我有一篇文章,介绍了分布式事务最经典的七种解决方案,这里我们从业务需求的角度,根据不同的业务场景,给出最适合的解决方案。

当我们采用服务/微服务架构,对业务进行分拆解耦后,原先在一个单体内,使用本地数据库保证ACID的数据修改,因为跨了多个服务,就不再适用了,就需要引入分布式事务来保证新的原子性。

由于分布式事务方案,无法做到ACID的保证,没有一种完美的方案,能够解决掉所有业务问题。因此在实际应用中,会根据业务的不同特性,选择最适合的分布式事务方案。

下面是常见的几种业务分类,以及适合的解决方案介绍

多个微服务组合成原子操作

有一类业务场景是需要把多个微服务组合成原子操作:假设您有一个活动业务,用户点击领取按钮后,会领取一张优惠券,和一个月的会员。优惠券和会员分别属于不同的服务,需要都被调用,不希望出现一个服务调用成功,另一个因为网络或者其他故障导致没有成功。

这个场景适合可靠消息方案,可以使用rocketmq、rabbitmq等,发送给消息队列的消息,一定要等收到队列接收确认,再返回应用程序。

本地事务+多个微服务组合为原子操作

有一类业务与前一种业务情况类似,但有一些差别:假设您有一个新用户注册成功后,领取一张优惠券和一个月会员。如果注册不成功,不希望调用领取;只有注册成功才领取。

这种情况,适合本地消息方案,或者事务消息方案。这两种方案都能保证本地事务和消息的原子性。

订单类对一致性要求较高的业务

订单交易类业务,涉及资金、库存、优惠券等多个服务,完成一个订单,需要相关的各个服务组合成一个整体可回滚的事务。如果订单进行过程中金额先扣减,后续因为库存不够只能退款,把金额补偿加回来。在这个过程中用户看到了金额减少,又金额变回来,体验很差。一般这类业务都会先冻结资金,如果订单能成功,再扣减资金;不能成功,则解冻资金,这样能够让资金信息对用户更友好。

这种场景适合TCC方案,可以在TCC的Try中冻结资金,Confirm中扣减资金,Cancel中解冻资金

一致性要求不高的可回滚业务

如果业务对事务中的一致性要求不高,允许用户看到中间状态,例如用户的积分数据等。

这种模式适用SAGA模式,SAGA对比与TCC,只有正向操作和逆向补偿操作,会更加简单

耗时较久的全局事务

耗时较旧的全局事务适合可靠消息和SAGA,不适合TCC和XA,因为大多数的XA和TCC实现,为了方便用户灵活的定义事务,通常把事务的进度保存在应用程序,一旦事务进行中应用程序崩溃,无法往前进行下一步,只能回滚。

SAGA和可靠消息,把事务进度保存在数据库或消息系统中,任何一个组件临时的失败,如果重试成功,能够让事务继续。

其中如果整个事务是需要回滚的,那么适合SAGA,不需要回滚的,适合可靠消息

并发度较低的业务

如果业务并发度不高,事务又需要支持回滚,那么适合XA方案。XA方案,除了并发不高,也还需要本地数据库能支持XA接口。这个方案的优点是,使用上较简单,比较接近本地事务

上面介绍完各种业务类型,以及适合的事务方案,通常情况下,您需要选择合适的开源项目来实施技术方案。在分布式事务领域,应用比较广泛的有DTMSEATARocketMq

其中seata用Java开发,支持Java语言的接入,支持TCC、SAGA、XA、AT(类似XA,性能更高,但有脏回滚)

RocketMq用Java开发,支持各类语言的接入,仅支持可靠消息、事务消息模式

这里重点介绍DTM,它用GO开发,基于HTTP协议,支持多种语言接入,支持TCC、SAGA、XA、可靠消息、事务消息模式。

可靠消息例子

我们拿第一个最简单的业务场景“多个微服务组合成原子操作”来看DTM是如何解决问题的

假设领取优惠券和会员的处理函数分别是:ObtainCoupon和ObtainVip,那么处理领取逻辑的处理函数(用Go做示例)只用这么写:

    msg := dtmcli.NewMsg(DtmServer,gid).
        Add(Busi+"/ObtainCoupon", req).
        Add(Busi+"/ObtainVip", req)
    err := msg.Submit()

dtm收到客户端提交的消息后,会保证ObtainCoupon和ObtainVip被调用,如果任何一个出现失败,会不断重试,直到成功。

假如您采用的是rocketmq方案,那么您需要做以下几个步骤:

  1. 发送"领取"的消息给队列
  2. 消费"领取”的消息,然后调用ObtainCoupon和ObtainVip,然后确认消息已成功消费

对比dtm和rocketmq的方案,dtm仅需要简单的几行代码即可(dtm也提供http的接口,可以用任何语言直接发http请求),清晰简单。而rocketmq方案,涉及较多队列的知识,要做的工作较多

SAGA例子

假设我们有一个积分兑换课程的业务,一方面积分不属于非常核心的资产,中间状态允许用户看到,另一方面兑换课程可能出现课程已拥有权限,则需要回滚,因此该业务属于“一致性要求不高的可回滚业务“。

我们采用SAGA方案来解决这个问题,来看看DTM的解决方式,代码大致如下:

    saga := dtmcli.NewSaga(DtmServer, gid).
        Add(Busi+"/AdjustIntegral", Busi+"/AdjustIntegralRevert", req).
        Add(Busi+"/AuthCourse", Busi+"/AuthCourseRevert", req)
    saga.WaitResult = true
    err := saga.Submit()

dtm收到客户端提交的saga事务之后,会按顺序调用AdjustIntegral,AuthCourse,如果函数返回错误要求回滚,dtm则会调用AuthCourseRevert,AdjustIntegralRevert进行回滚。

如果您没有采用dtm方案,那么您可以采用SEATA的SAGA,涉及比较多的背景知识,接入较复杂。

更多的例子

您可以访问https://github.com/yedf/dtm ,里面有很多的分布式事务例子

多种模式并存

如果您的实际项目,涉及分布式事务的场景较多,一种事务模式,可能并不满足需求,可能需要使用SEATA+Rocketmq,接入以及维护成本较高。而DTM提供了一站式的解决方案,对常见的各种业务场景都提供了便捷的支持。

dtm作为一个新兴起的分布式事务框架,提供了强大的功能,以及简单易用的接口,极大的简化了微服务架构下,分布式事务的使用。

下面是dtm与seata的主要特性对比:

特性DTMSEATA备注支持语言Golang、python、php、c# 及其他Javadtm可轻松接入一门新语言异常处理子事务屏障自动处理手动处理dtm解决了幂等、悬挂、空补偿TCC事务✓✓ XA事务✓✓ AT事务✗✓AT与XA类似,性能更好,但有脏回滚SAGA事务简单模式状态机复杂模式dtm的状态机模式在规划中事务消息✓✗dtm提供类似rocketmq的事务消息通信协议HTTPdubbo等协议,无HTTPdtm后续将支持grpc类协议

dtm的项目地址为https://github.com/yedf/dtm ,欢迎大家访问、试用、点亮star


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK