补偿框架

这个框架主要的目的是为了当我们的服务涉及到与外部系统交互的时候,我们服务的事务和外部接口可能由于各种问题不能保证一致,出现两个系统数据不一致的情况,往往需要去手动处理数据。

理论解决方案

当服务涉及与外部系统交互时,确保数据一致性是一个重要的问题。以下是一些解决方案:

  1. 使用分布式事务:如果外部系统支持分布式事务,可以使用分布式事务管理器(如Atomikos、Bitronix、Narayana等)来确保服务和外部接口的事务一致性。通过将服务和外部接口的操作纳入同一个分布式事务中,可以在两者之间实现原子性和一致性。
  2. 异步消息队列:将服务与外部接口的交互改为异步方式,使用消息队列作为中间件。服务将需要与外部接口交互的数据发送到消息队列中,然后由消费者异步处理。这样可以将服务和外部接口的事务解耦,即使其中一个失败,也不会影响另一个。如果发生故障,可以通过重试机制来保证数据最终一致性。
  3. 补偿机制:在服务与外部接口交互时,记录交互的操作和状态信息,以便在发生故障或数据不一致时进行补偿。可以使用补偿模式来处理这种情况,例如在服务中实现一个补偿机制,当发生故障时,自动触发补偿操作来修复数据不一致性。
  4. 重试和回滚:在与外部接口交互时,可以使用重试机制来处理临时的通信故障。如果发生故障,可以尝试重新发送请求,直到成功为止。另外,如果服务的事务失败,可以回滚事务并进行相应的错误处理。
  5. 监控和报警:建立监控系统来实时监测服务与外部接口的交互情况。通过监控指标和日志,可以及时发现潜在的数据不一致问题,并触发相应的报警机制,以便及时采取措施解决问题。

这里其实最常见的是使用分布式事务去解决,但是其实这种解决方式其实不一定能适用于所有情况,比如说

  • 复杂性:分布式事务的实现相对复杂,需要引入额外的组件和技术栈,如分布式事务管理器、消息队列等。这增加了系统的复杂性和维护成本。
  • 性能影响:分布式事务通常需要进行多次网络通信和资源协调,这可能导致性能开销增加。特别是在高并发和大规模系统中,分布式事务的性能问题可能会成为瓶颈。
  • 扩展性限制:分布式事务在跨多个服务和数据库进行协调时,可能会受到扩展性的限制。当系统需要水平扩展时,分布式事务可能会成为限制因素,因为它需要全局锁和协调。
  • 依赖外部系统:使用分布式事务时,需要确保外部系统也支持分布式事务。如果外部系统不支持或与服务的事务管理器不兼容,可能需要进行额外的工作来解决这个问题。
  • 高可用性和故障恢复:分布式事务的高可用性和故障恢复是一个挑战。当分布式事务管理器或其他组件发生故障时,需要有相应的机制来处理故障并保证数据的一致性。
  • 学习和开发成本:使用分布式事务需要对相关技术和概念有一定的了解,并且需要在开发过程中遵循一些规范和最佳实践。这可能需要额外的学习和培训成本。

综上所述,尽管分布式事务可以解决数据一致性的问题,但也需要权衡其复杂性、性能开销和扩展性限制等因素。在设计和选择系统架构时,需要综合考虑业务需求、系统规模和可用性要求,选择适合的事务处理方式。

所以为了避免将整个系统变得更加复杂,这里其实并不建议使用分布式事务,其次,这里还涉及到需要去增加维护,增加耦合,其实是非常不推荐的做法,也比如说使用MQ,其实也是一样的,需要多一个维护。

再细说一下MQ的解决方案

补偿机制和消息队列(MQ)是两种不同的处理方式,适用于不同的场景。以下是一些情况下可以考虑使用补偿机制而不使用消息队列:

  1. 低延迟要求:如果系统对低延迟有较高要求,即需要实时或近实时的处理能力,使用补偿机制可能更为合适。补偿机制通常是同步或异步的方式,可以直接在服务内部进行处理,而不需要依赖消息队列的异步处理和传递。
  2. 简化架构:使用消息队列需要引入额外的组件和技术栈,如消息中间件、消息生产者和消费者等。如果系统的架构相对简单,没有复杂的异步消息传递需求,使用补偿机制可以简化系统架构,减少复杂性和维护成本。
  3. 有限的系统规模:如果系统规模相对较小,没有大规模的并发和高吞吐量的需求,使用补偿机制可以更轻量级地处理事务和数据一致性,而不需要引入消息队列的复杂性。
  4. 事务性操作:如果需要进行事务性的操作,即需要保证一组操作的原子性和一致性,使用补偿机制可能更为合适。补偿机制可以在事务内部进行操作和补偿,而不需要依赖消息队列的事务性支持。

但是结合我们的场景,其实并不需要低延迟,而更多的是需要调通不同渠道的接口,所以,这里需要去实现简单,无侵入式,高可用,低耦合的框架,所以,再三选择之下,还是补偿机制更合适。

这里推荐使用自己去实现一个补偿机制

补偿机制

补偿机制和分布式事务是两种不同的处理方式,适用于不同的场景。以下是一些情况下可以考虑使用补偿机制而不是分布式事务:

  1. 高可扩展性要求:如果系统需要高度可扩展性,即需要支持大规模的并发和水平扩展,使用分布式事务可能会成为限制因素。在这种情况下,可以考虑使用补偿机制,将服务的事务解耦,通过异步处理和补偿操作来保证数据的一致性。
  2. 异构系统集成:当系统需要与多个异构的外部系统进行集成时,这些外部系统可能不支持分布式事务或与服务的事务管理器不兼容。在这种情况下,使用补偿机制可以更灵活地处理与外部系统的交互,通过记录操作和状态信息,进行补偿操作来保证数据的一致性。
  3. 高可用性和故障恢复:补偿机制可以更好地应对系统的高可用性和故障恢复需求。当发生故障或数据不一致时,补偿机制可以自动触发补偿操作来修复数据,而无需依赖全局的分布式事务协调。
  4. 非关键业务操作:对于一些非关键的业务操作,数据一致性要求可能相对较低。在这种情况下,使用补偿机制可以提供更好的性能和可扩展性,而不需要引入复杂的分布式事务。

生产环境遇到的问题

目前常见的处理手段及存在的问题

手段1: 本地数据库操作和外部接口调用放在同一个事务中

问题:外部接口成功,本地事务由于异常原因回滚,数据不一致

手段2:本地数据库事务提交后,再调用外部接口

问题:本地事务成功后,外部接口失败或者机器挂掉,数据不一致

手段3:使用mq、redis临时存储消息,后续消费

问题:mq、redis本身就是一个外部系统,一样存在问题

rocketmq提供了和本地事务一致的机制,但是需要申请维护mq且实现一套消费逻辑

设计一个补偿框架

这里考虑到无侵入式,和开箱即用,所以考虑将整个流程,融入到Spring的生命周期。并且,使用注解的方式去标注需要调用外部接口的方法,然后当接口调用失败的时候,会存入数据库,然后隔一段时间再去调用,并且为了加强效率,也会使用线程池去增强实现。

现在设计的补偿框架,主要有几个功能:

  • 在启动的时候,可以扫描整个包,看看哪里用到了@Component注解,将所有用到了这个注解的,都加入到hashmap中,从而全局持有这个方法的信息。
  • 然后,可以去使用执行链设计模式,去创建一个执行链,将需要的方法通通加入链上,分批次执行相关的方法
  • 对于每次执行方法,必然会出现成功或者是失败的状况,如果出现执行失败的情况,可以将其写入到数据库中,等待后续再逐步从数据库中读出相应的数据,然后去尝试不断的重新执行方法。
  • 在执行方法的时候,可以使用一个AOP切面,去根据注解信息去拦截想要执行的方法,然后先将方法持久化到数据库中,再执行相关的方法,如果执行成功,再去删除相应的ID,其次,如果执行失败,则暂时不做任何行为,因为还有定时任务去执行。
  • 那么对于那些执行失败的任务怎么办?对于失败的任务,就会定时的去数据库中读取出来,然后去进行反复的重试,直到成功为止。