终于有人把“分布式事务”说清楚了( 四 )


JTA(Java Transaction Manager):

  • Transaction Manager:常用方法,可以开启,回滚,获取事务 。begin(),rollback()...
  • XAResouce:资源管理,通过 Session 来进行事务管理,commit(xid)...
  • XID : 每一个事务都分配一个特定的 XID 。
JTA 主要的原理是二阶段提交,当整个业务完成了之后只是第一阶段提交,在第二阶段提交之前会检查其他所有事务是否已经提交 。
如果前面出现了错误或是没有提交,那么第二阶段就不会提交,而是直接回滚,这样所有的事务都会做回滚操作 。基于 JTA 这种方案实现分布式事务的强一致性 。
JTA 的特点:
  • 基于两阶段提交,有可能会出现数据不一致的情况
  • 事务时间过长,阻塞
  • 性能低,吞吐量低
实现可以使用基于 JTA 实现的 Jar 包 Atomikos 例子可以自己百度一下 。
正常架构设计中是否应该出现这种跨库的操作,我觉得是不应该的,如果按业务拆分将数据源进行分库,我们应该同时将服务也拆分出去才合适,应遵循一个系统只操作一个数据源(主从没关系),避免后续可能会出现的多个系统调用一个数据源的情况 。
最终一致性分布式事务方案
JTA 方案适用于单体架构多数据源时实现分布式事务,但对于微服务间的分布式事务就无能为力了,我们需要使用其他的方案实现分布式事务 。
①本地消息表
本地消息表的核心思想是将分布式事务拆分成本地事务进行处理 。
以本文中例子,在订单系统新增一条消息表,将新增订单和新增消息放到一个事务里完成,然后通过轮询的方式去查询消息表,将消息推送到 MQ,库存系统去消费 MQ 。
 
终于有人把“分布式事务”说清楚了

文章插图
 
 
执行流程:
  • 订单系统,添加一条订单和一条消息,在一个事务里提交 。
  • 订单系统,使用定时任务轮询查询状态为未同步的消息表,发送到 MQ,如果发送失败,就重试发送 。
  • 库存系统,接收 MQ 消息,修改库存表,需要保证幂等操作 。
  • 如果修改成功,调用 RPC 接口修改订单系统消息表的状态为已完成或者直接删除这条消息 。
  • 如果修改失败,可以不做处理,等待重试 。
订单系统中的消息有可能由于业务问题会一直重复发送,所以为了避免这种情况可以记录一下发送次数,当达到次数限制之后报警,人工接入处理;库存系统需要保证幂等,避免同一条消息被多次消费造成数据一致 。
本地消息表这种方案实现了最终一致性,需要在业务系统里增加消息表,业务逻辑中多一次插入的 DB 操作,所以性能会有损耗,而且最终一致性的间隔主要由定时任务的间隔时间决定 。
②MQ 消息事务
消息事务的原理是将两个事务通过消息中间件进行异步解耦 。
订单系统执行自己的本地事务,并发送 MQ 消息,库存系统接收消息,执行自己的本地事务 。
乍一看,好像跟本地消息表的实现方案类似,只是省去了对本地消息表的操作和轮询发送 MQ 的操作,但实际上两种方案的实现是不一样的 。
消息事务一定要保证业务操作与消息发送的一致性,如果业务操作成功,这条消息也一定投递成功 。
 
终于有人把“分布式事务”说清楚了

文章插图
 
 
消息事务依赖于消息中间件的事务消息,基于消息中间件的二阶段提交实现的,RocketMQ 就支持事务消息 。
执行流程:
  • 发送 Prepare 消息到消息中间件 。
  • 发送成功后,执行本地事务 。
  • 如果事务执行成功,则 Commit,消息中间件将消息下发至消费端 。
  • 如果事务执行失败,则回滚,消息中间件将这条 Prepare 消息删除 。
  • 消费端接收到消息进行消费,如果消费失败,则不断重试 。
这种方案也是实现了最终一致性,对比本地消息表实现方案,不需要再建消息表,不再依赖本地数据库事务了,所以这种方案更适用于高并发的场景 。
③最大努力通知
最大努力通知相比前两种方案实现简单,适用于一些最终一致性要求较低的业务,比如支付通知,短信通知这种业务 。
以支付通知为例,业务系统调用支付平台进行支付,支付平台进行支付,进行操作支付之后支付平台会尽量去通知业务系统支付操作是否成功,但是会有一个最大通知次数 。
如果超过这个次数后还是通知失败,就不再通知,业务系统自行调用支付平台提供一个查询接口,供业务系统进行查询支付操作是否成功 。


推荐阅读