什么是分布式事务?
当A服务调用B服务成功以后,A服务报错导致事务回滚,B服务事务提交,导致数据不一致性的问题。
分布式事务产生的条件
说到分布式事务,我们先来看看分布式事务的产生条件
这里我们举一个栗子:
- 当用户进行下单以后,会去调用派单服务进行派单,即向派单服务数据库中插入一条派单业务。
- 派单成功之后,订单服务在执行后面的业务代码中,报错了
- 此时订单服务事务回滚,而派单服务的事务已经提交了,导致了数据的不一致。
此时,小伙伴可能有疑问:派单服务报错呢?会不会也产生分布式事务问题?
实际上如果派单服务报错,会将执行结果返回给订单服务,订单服务执行相应的处理即可,并不会发生分布式事务。
分布式事务的解决方案有哪些?
- 基于Lcn解决分布式事务
- 基于阿里巴巴seata解决分布式事务
- 基于RabbitMq解决分布式事务
- 基于RocketMq解决分布式事务
1、基于Lcn解决分布式事务
LCN实现原理
- 发起方与参与方都与我们的LCN管理器一直保持长连接;
- 发起方在调用接口之前,先向LCN管理器申请一个全局的事务分组id;
- 发起方调用接口的时候在请求头中传递事务分组id;
- 参与方获取到请求头中有事务分组的id的,则当前业务逻辑执行完实现假关闭,不会提交或者回滚当前的事务。
- 发起方调用完接口后,如果出现异常的情况下,在通知给事务协调者回滚事务,这时候事务协调则告诉给参与方回滚当前的事务。
2、阿里巴巴seata框架
和上面一样,订单服务为发起方、派单服务为参与方
- 发起方(TM)和参与方(RM)项目启动后会和协调者(TC)保持长连接
- 发起方(TM)在调用参与方(TC)之前,会向协调者(TC)申请一个全局事务id(Xid),并保存到ThreadLocal中
- 发起方(TM)和参与方(RM)都会被seata代理数据源,利用aop在执行insert、update、delete语句之前和之后生成前置镜像和后置镜像,并写入到undo_log表中
- 发起方(TM)重写feign客户端请求,将全局事务id保存到请求头中传递给参与方(RM),参与方(RM)获取全局事务id,并在协调者(TC)中注册该分支
- 发起方(TM)调用参与方(RM)接口成功以后,执行本地业务代码也成功后,会将发起方(TM)本地事务结果commit通知给协调者(TC),协调者(TC)再将事务结果通知给给各事务分支(即参与方(RM))。
- 发起方(TM)调用参与方(RM)接口成功以后,执行本地业务代码失败,此时会产生分布式事务问题,发起方(TM)本地事务结果rollback通知给协调者(TC),协调者(TC)再将事务结果通知给给各事务分支(即参与方(RM))。
- 参与方(RM)接收到事务结果通知,如果是commit的情况下,代表事务执行成功,删除掉undo_log中对应的分支id的记录即可
- 参与方(RM)接收到事务结果通知,如果是rollback的情况下,代表事务执行失败,根据undo_log日志,逆向生成sql语句,比如先前是插入,现在就是删除。去删除掉相应的记录,完成以后,再删掉undo_log日志对应的记录。
优点 - seata的性能比lcn要好
- seata不会造成死锁的情况
缺点
- seata没有管理化界面
- seata会造成数据的脏读,不能保证数据的强一致性,只能保证最终一致性
seata和lcn比较,有什么不一致?
- seata和lcn大致的实现思路是一致的,但是回滚的机制不一样。
- lcn是采取代理数据源的模式,再根据发起方执行本地事务的结果进行回滚或者提交
- seata采取的是根据undo_log日志表,进行逆向生成sql语句,来解决回滚
- lcn能够保证强一致性,但可能发生死锁的现象
- seata能保证最终一致性,但可能造成脏读
3、基于RabbitMQ解决分布式事务
实现流程
- 确保生产者投递消息成功,使用消息确认机制confirm,如果消息投递失败,人工进行补偿
- 确保消费者消费成功,采用手动ack的形式。如果消费失败,mq自动帮我们补偿,如果达到补偿上线,进行日志记录或者放入死信队列,后期进行人工补偿
- 当订单生产者投递成功以后,后续代码报错,导致事务回滚,我们需要采用补单队列,即订单生产者在投递消息的时候,不光要投递派单消息,还需要把订单消息一并带过去,补单消费者接收到消息以后,先去数据库查询数据是否存在,如果不存在的话,补单消费者进行补单
优点
- 基于mq的形式实现,具有重试、消息持久化机制
缺点
- RabbitMQ实现起来比较麻烦,需要手动添加补单消费者。
4、基于RocketMQ解决分布式事务
RocketMQ也是阿里巴巴开发的框架,是再kafka的基础上进行改造升级,自带解决分布式事务的功能,主要采用事务消息来解决分布式事务
原理分析:
- 生产者向Broker投递半消息,(半消息是不能被消费者进行消费的)
- Broker返回消息投递成功的结果
- 生产者执行本地事务,再将本地事务的结果返回给Broker
- 本地事务提交有两种情况,如果返回的成功是,COMMIT,则代表事务成功执行,然后标记该半消息可以为消费者消费。如果事务返回的是ROLLBACK,则代表事务执行失败,进行了回滚,Broker将会把该半消息移除
- broker根据事务结果决定该半消息是否让消费者消费
- 如果本地事务执行时间太久,或者是因为网络原因导致本地事务结果没有告诉Broker,Broker将每隔一分钟主动查询一次本地事务执行的结果。总共重试15次。以此判断半消息是否可以被消费
优点
- RocketMQ具有重试机制、持久化机制、分区机制、天生抗并发能力强。
- RocketMQ已经帮我们解决了分布式事务
缺点
- 可能功能上没有阿里云的付费产品ons强大吧
那解决分布式事务该选用那种框架呢?
- 首先看是否能扩展机器,在有成本的情况下,尽量考虑mq,毕竟已经帮我们做好了重试,消息持久化等功能
- RabbitMQ和RockteMQ选型,尽量采用RocketMQ,RabbitMQ不是用java语言编写,扩展能力比较差,而且抗并发能力与RocktMQ相差很远
- lcn和seata之间选型,如果数据需要保证强一致性,就选择lcn。如果不需要,就选择seata。而且,lcn现在已经停止更新。