transaction in micro service architecture

#transaction #micro service #architecture 编辑

微服务(分布式)架构下,关于事务补偿机制

整个17年整理过不少遇到的case,在笔记内太凌乱,太懒没做整理,瞬间已经12月,17年算是结束了。整理一个笔记,给2017留下点回忆哈哈 :)  

概述

微服务,或是非集中式应用,也就是系统架构做拆分后,会涉及一个非常典型的问题,就是事务的问题。不管是特别在意一致性的金钱相关的,还是其他业务场景,都不同程度存在一个操作流转与多个服务之间的问题。     这其中涉及的分布式事务问题,随便搜一把,够看一礼拜了,不过多数都是重复的,TCC, 二阶段,三阶段,最重要还是在业务中挖掘发现自己试用的场景。 这里总结表述的是事务性数据补偿的方案。  

期间调研过RocketMQ, 数据中间件方式,TCC等,但最终的方案,还是自己在业务基础上设计的简易的事务或叫数据最终一致性补偿。都是比较成熟的做法组合,核心思想就是local transaction + tx-coordinator service + 幂等支持的tx executor sdk   这个思想其实跟RocketMQ是比较像的,不同的是rocketMQ对于这个过程中产生的数据自有处理,而且一样设计了类似rocketMQ的confirm call befor retry逻辑,同样的利用db,也做到了recover after breakdown. 且提供幂等sdk。  几个部分的概述:   前提: 系统仅处理最终一致性数据的补偿,暂时不考虑TCC中涉及的rollback  

  1. transaction producer, 事务发起方。 也就是本次数据的起点。我们需要提供sdk封装给应用开发,由于我们涉及多语言,所以还得准备3份,比较苦逼。这里的封装不同语言实现时封装程度不一样,但最终目标是尽量减少代码入侵(实际上解决分布式事务时,目前还不存在完全不入侵代码的方案,即使在java系这种各种讲究深度封装也是),producer利用db的事务,本地完成业务逻辑的提交和分布式事务数据的提交,严格来说因为这两个db因为不在一个库,依然会有事务问题,解决方法是利用2PC(太重),提供confirm befor retry的逻辑给外部的事务协调服务回调。基本能把本地事务问题降低到很低的出现问题概率。produer在和补偿协调器约定时,需要提供一个业务层的唯一id,比如可能是订单id或其他唯一标示, 也可以选择用协调器自己生成的唯一id,作为本次事务的id。

  2. 外部的事务协调器服务,这个服务本身无状态,所有跟补偿相关的状态,过程,步骤,都存在db内,它会定时检查db,依照约定条件,当触发某条事务,它的跨服务最终一致性没有完成时,协调器会根据之前约定的数据,约定的方式,如果需要confirm callback则先回调producer询问是否确认本地事务已完成,然后按约定方式发起重试,约定的重试方式有通过消息队列给下游transaction executor服务发通知,或是直接调用下游服务的RPC接口。这取决于producer在begin这条事务的时候选择的是哪个模式,比如A服务它本来就是通过RPC的方式调用B服务,那么它可能选择的重试机制也是RPC模式,那么协调器就会使用约定的数据在重试的时候调用B服务的相应RPC接口。

  3. 第3部分依然是封装好的sdk(又要多个语言,再次苦逼),  sdk的封装内,提供接口,让tx executor方实现接口,或是调用现成的方法。 加入在producer中约定的是rpc调用,则实现rpc类型的executorImpl, 消息队列的则实现kafkaImpl,sdk封装好对幂等的检查,这里需要分场景,对于一些数据一致性非常敏感的业务,应当由业务再次检查幂等性,但带来了额外的业务开发。 否则使用协调器sdk已有的方法,sdk会先利用producer提供的事务id,完成幂等检查,如果发现是重复的调用或消息则忽略直接返回,否则进入正常流程处理,tx executor作为被调用方完成本地业务逻辑后,提交业务数据,然后再提交事务协调db的数据,完成本次过程。 这里同样涉及跨db的本地事务问题,处理方式和producer阶段一样。

整个过程,与RocketMQ真有点像,应该说是一个弱化后的解决方案。另外这个事务补偿协调的过程可以扩展成支持多个步骤:A -> B -> C这样的调用过程。  方案的首要目标是首先完成逻辑正确性,然后是尽全力减少对应用层代码的入侵,毕竟一个东西需要推广就需要考虑成本。

完…