分布式事务选型及对比

2020-09-17 14:31:02 浏览数 (1)

我是【envoke】,一个想在编程路上越走越远的人。 我热爱技术、热爱开源、热爱编程。技术是开源的、知识是共享的。

  1. Stay hungry, stay foolish.(求知若饥,虚心若愚)
  2. Go big or go home.(要么牛逼,要么滚蛋)

如果您也对Java感兴趣,我的文章能帮助到您,加入【Go Big】一起进步。群号:243108249


分布式事务产生的条件

说到分布式事务,我们先来看看分布式事务的产生条件 这里我们举一个栗子:

  1. 当用户进行下单以后,会去调用派单服务进行派单,即向派单服务数据库中插入一条派单业务。
  2. 派单成功之后,订单服务在执行后面的业务代码中,报错了
  3. 此时订单服务事务回滚,而派单服务的事务已经提交了,导致了数据的不一致。

此时,小伙伴可能有疑问:派单服务报错呢?会不会也产生分布式事务问题? 实际上如果派单服务报错,会将执行结果返回给订单服务,订单服务执行相应的处理即可,并不会发生分布式事务。

什么是分布式事务?

当A服务调用B服务成功以后,A服务报错导致事务回滚,B服务事务提交,导致数据不一致性的问题。

分布式事务的解决方案有哪些?

  • 基于Lcn解决分布式事务
  • 基于阿里巴巴seata解决分布式事务
  • 基于RabbitMq解决分布式事务
  • 基于RocketMq解决分布式事务

以下部分小伙伴们需要有一定的mq基础,或者使用过lcn和seata,这样理解起来更加轻松哦

基于Lcn解决分布式事务

LCN并不生产事务,LCN只是本地事务的协调工 小伙伴想要了解lcn,这里提供了传送门前往http://www.txlcn.org/zh-cn/docs/preface.html

LCN是分布式事务的搬运工,我是LCN的搬运工 此图来源于LCN官网,当然,我也把相应的注释给搬运过来

核心步骤

创建事务组

  • 是指在事务发起方开始执行业务代码之前先调用TxManager创建事务组对象,然后拿到事务标示GroupId的过程。

加入事务组

  • 添加事务组是指参与方在执行完业务方法以后,将该模块的事务信息通知给TxManager的操作。

通知事务组

  • 是指在发起方执行完业务代码以后,将发起方执行结果状态通知给TxManager,TxManager将根据事务最终状态和事务组的信息来通知相应的参与模块提交或回滚事务,并返回结果给事务发起方。

看完以上定义,不知道小伙伴们看懂了没有。我这里来个白话文翻译一下。 从我们使用的栗子来看

发起方

  • 订单服务就是事务的发起方

参与方

  • 派单服务被调用,即为事务的参与方

协调者

  • 该模块为lcn提供的管理服务,帮助我们解决分布式事务问题

结合案例分析其步骤:

  1. 订单服务为事务发起方,会在开始执行业务代码之前先调用TxManager创建事务组对象,然后拿到事务标示GroupId
  2. 当订单服务调用rpc远程调用支付服务之前(类似于图中发起方请求模块A),lcn会拦截请求,并将groupId放入到请求头中
  3. 当请求到达支付服务之前,请求会被拦截,判断请求头中是否含有groupId,如果有则是参与方,并且根据GroupId注册到TxManage的同一个事务组中
  4. 支付服务在执行业务代码之前,lcn会代理其数据源
  5. 支付服务和商品服务会在执行完业务方法以后,由于数据源被代理,本地事务不会立即提交。而是被代理假关闭
  6. 当订单服务业务执行完毕,并且提交本地事务,会将本地事务的结果通知给TxManager
  7. TxManager去遍历事务组,取出其中的事务单元,并进行一一通知。
  8. 参与方接收到通知以后,再进行相应的事务提交或者回滚操作,保持数据的一致性

优点

  1. 保证数据的强一致性

缺点

  1. 可能会造成死锁的现象,比如,订单服务调用派单服务成功以后,订单服务还没执行完毕就宕机,此时,TxManage并没有收到通知,派单服务的事务也不能顺利进行,导致死锁。
  2. lcn的性能不是特别强大

阿里巴巴seata框架

阿里爸爸还是特别强大的,由于seata框架问世的比较晚,目前还存在一些缺陷,比如说没有一个界面化的管理平台。 以阿里的更新速度来说,这些都不是大问题。 seata传送门 https://gitee.com/seata-io/seata

seata的原理分析 和上面一样,订单服务为发起方、派单服务为参与方

  1. 发起方(TM)和参与方(RM)项目启动后会和协调者(TC)保持长连接
  2. 发起方(TM)在调用参与方(TC)之前,会向协调者(TC)申请一个全局事务id(Xid),并保存到ThreadLocal中
  3. 发起方(TM)和参与方(RM)都会被seata代理数据源,利用aop在执行insert、update、delete语句之前和之后生成前置镜像和后置镜像,并写入到undo_log表中
  4. 发起方(TM)重写feign客户端请求,将全局事务id保存到请求头中传递给参与方(RM),参与方(RM)获取全局事务id,并在协调者(TC)中注册该分支
  5. 发起方(TM)调用参与方(RM)接口成功以后,执行本地业务代码也成功后,会将发起方(TM)本地事务结果commit通知给协调者(TC),协调者(TC)再将事务结果通知给给各事务分支(即参与方(RM))。
  6. 发起方(TM)调用参与方(RM)接口成功以后,执行本地业务代码失败,此时会产生分布式事务问题,发起方(TM)本地事务结果rollback通知给协调者(TC),协调者(TC)再将事务结果通知给给各事务分支(即参与方(RM))。
  7. 参与方(RM)接收到事务结果通知,如果是commit的情况下,代表事务执行成功,删除掉undo_log中对应的分支id的记录即可
  8. 参与方(RM)接收到事务结果通知,如果是rollback的情况下,代表事务执行失败,根据undo_log日志,逆向生成sql语句,比如先前是插入,现在就是删除。去删除掉相应的记录,完成以后,再删掉undo_log日志对应的记录。

什么是前置镜像和后置镜像

前置镜像:select * from order where orderId = 66; 并将查询的结果保存起来 state = 0; update order set state = 1 where orderId=66; 后置镜像:select * from order where orderId = 66; 更改后的数据进行记录 state = 1; 当我们需要回滚的时候,就可以根据前置镜像和后置镜像保存的记录,进行逆向生成sql语句回滚 update order set state = 0 where orderId = 66;

当发起方调用参与方成功以后,发起方宕机了怎么办?

  1. 当发起方(TM)调用参与方成功(RM)以后,发起方(TM)宕机
  2. 协调者(TC)会检测发起方(TM)是否还存在(会多次重试连接发起方(TM)), 如果发现发起方(TM)已经宕机,则会将本次事务结果记录为rollback,并且通知参与方(RM)
  3. 参与方(RM)接收到协调者(TC)发来的消息,根据undo_log日志进行回滚
  4. 当发起方(TM)重启后,会接收到协调者(TC)的消息通知,将上一次宕机执行的业务也进行rollback(如果接口相应超时,协调者(TC)还会进行重试)

优点

  1. seata的性能比lcn要好
  2. seata不会造成死锁的情况

缺点

  1. seata没有管理化界面
  2. seata会造成数据的脏读,不能保证数据的强一致性,只能保证最终一致性

seata和lcn比较,有什么不一致?

此题可能会被面试官问到,需要多多注意哦

  • seata和lcn大致的实现思路是一致的,但是回滚的机制不一样。
  • lcn是采取代理数据源的模式,再根据发起方执行本地事务的结果进行回滚或者提交
  • seata采取的是根据undo_log日志表,进行逆向生成sql语句,来解决回滚
  • lcn能够保证强一致性,但可能发生死锁的现象
  • seata能保证最终一致性,但可能造成脏读

基于RabbitMQ解决分布式事务

RabbitMQ是基于补单队列的形式实现分布式事务

实现流程

  1. 确保生产者投递消息成功,使用消息确认机制confirm,如果消息投递失败,人工进行补偿
  2. 确保消费者消费成功,采用手动ack的形式。如果消费失败,mq自动帮我们补偿,如果达到补偿上线,进行日志记录或者放入死信队列,后期进行人工补偿
  3. 当订单生产者投递成功以后,后续代码报错,导致事务回滚,我们需要采用补单队列,即订单生产者在投递消息的时候,不光要投递派单消息,还需要把订单消息一并带过去,补单消费者接收到消息以后,先去数据库查询数据是否存在,如果不存在的话,补单消费者进行补单

优点

  • 基于mq的形式实现,具有重试、消息持久化机制

缺点

  • RabbitMQ实现起来比较麻烦,需要手动添加补单消费者。

基于RocketMQ解决分布式事务

RocketMQ也是阿里巴巴开发的框架,是再kafka的基础上进行改造升级,自带解决分布式事务的功能,主要采用事务消息来解决分布式事务

原理分析:

  1. 生产者向Broker投递半消息,(半消息是不能被消费者进行消费的)
  2. Broker返回消息投递成功的结果
  3. 生产者执行本地事务,再将本地事务的结果返回给Broker
  4. 本地事务提交有两种情况,如果返回的成功是,COMMIT,则代表事务成功执行,然后标记该半消息可以为消费者消费。如果事务返回的是ROLLBACK,则代表事务执行失败,进行了回滚,Broker将会把该半消息移除
  5. broker根据事务结果决定该半消息是否让消费者消费
  6. 如果本地事务执行时间太久,或者是因为网络原因导致本地事务结果没有告诉Broker,Broker将每隔一分钟主动查询一次本地事务执行的结果。总共重试15次。以此判断半消息是否可以被消费

优点

  1. RocketMQ具有重试机制、持久化机制、分区机制、天生抗并发能力强。
  2. RocketMQ已经帮我们解决了分布式事务

缺点

  1. 可能功能上没有阿里云的付费产品ons强大吧

那解决分布式事务该选用那种框架呢?

  1. 首先看是否能扩展机器,在有成本的情况下,尽量考虑mq,毕竟已经帮我们做好了重试,消息持久化等功能
  2. RabbitMQ和RockteMQ选型,尽量采用RocketMQ,RabbitMQ不是用java语言编写,扩展能力比较差,而且抗并发能力与RocktMQ相差很远
  3. lcn和seata之间选型,如果数据需要保证强一致性,就选择lcn。如果不需要,就选择seata。而且,lcn现在已经停止更新。

如果小伙伴们有什么疑问,或者,博文有那里不对的地方,欢迎评论区发言。毕竟,月薪1800的小白懂的也不多,嘿嘿

0 人点赞