从单体系统演变成服务化项目后,我们面临的三大难题:服务的拆分、服务故障传播、分布式事务。

  • 服务的拆分:重点在于架构师对于业务场景的分析和理解,拆分成恰当的粒度,以便未来扩展;
  • 服务的故障传播:通过ribbon和hytrix基本上可以解决这一问题;
  • 今天重点想聊得是“分布式事务”问题;

    前言

    这个问题是我在做项目过程中真实经历过的,再深入聊解决方案之前,我想先聊下分布式环境下的两大原理。

CAP定理

  • 一致性(Consistency) :客户端知道一系列的操作都会同时发生(生效)
  • 可用性(Availability):每个操作都必须以可预期的响应结束
  • 分区容错性(Partition tolerance):即使出现单个组件无法可用,操作依然可以完成

BASE理论

  • Basically Available(基本可用)
  • Soft state(软状态)
  • Eventually consistent(最终一致性)

BASE理论是对CAP中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。

解决方案

两阶段提交

核心思想:先准备资源,然后统一提交执行,类似于数据库上的事务,只不过从数据库提升至业务级别上了,但是实现会更加复杂。

小结:没有采用这种方案,主要是实现复杂,而且性能还比较低,牺牲了可用性。

补偿事务

核心思想:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作,它分为三个阶段:

  • Try 阶段主要是对业务系统做检测及资源预留
  • Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。
  • Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。

本地消息表

核心思想:将分布式事务拆分成本地事务进行处理,
基本思路就是:

  • 消息生产方,需要额外建一个消息表,并记录消息发送状态。消息表和业务数据要在一个事务里提交,也就是说他们要在一个数据库里面。然后消息会经过MQ发送到消息的消费方。如果消息发送失败,会进行重试发送。
  • 消息消费方,需要处理这个消息,并完成自己的业务逻辑。此时如果本地事务处理成功,表明已经处理成功了,如果处理失败,那么就会重试执行。如果是业务上面的失败,可以给生产方发送一个业务补偿消息,通知生产方进行回滚等操作。

最终采用的解决方案

我们吸收了以上主流的解决方案,形成了目前的解决方案,主要思路如下

  • 首先对当前应用场景进行分析
    • 第一步,明确当前场景是否需要进行分布式补偿
    • 第二步,确定需要补偿的时候,需要业务拆解成不同的步骤
  • 实际执行的时候,第一个步骤比较重要,需要对入参进行严格的参数校验以及其他业务性校验,以保证后续业务需要
    • 如果校验未通过,如果是同步接口,直接返回响应错误信息,如果是异步接口,调用异步错误信息通知接口告知调用者;
    • 如果校验通过,则保存相关业务数据,并且同时生成一条补偿记录;【特别注意:业务数据的保存和补偿记录放在一个数据库事务里】
  • 后续步骤如果出现失败,则更新补偿记录表相关处理状态;
  • 还需要配置补偿定时任务,每个固定时间扫描补偿信息表,如果发现执行失败记录,则进行重试,如果连续补偿次数达到阈值,则将状态更新为error,停止补偿;
  • 还需要配置一个人工运维的定时任务,如果发现状态为error并且还未进行邮件预警的记录,则发告警邮件,需要人工运维;