分布式事务,第三方接口一致性问题

问题描述

各个子系统数据一致性问题

在过往单机系统的时代,把相关操作放在一个事务里,就能为我们解决数据一致性的问题。但在分布式系统和微服务架构盛行的今天,常常会遇到一个操作需要依赖多个外部服务的场景。要求我们自行解决各个系统数据一致性的问题。

解决方案

补偿事务

互联网场景下,解决分布式系统的一致性问题,基于系统复杂性与吞吐量的考虑,多数团队不会选择类似两阶段提交,甚至三阶段提交的分布式事务。一个简单的补偿事务就足以解决多数问题。当然,如果系统都是自家可控的,第三方中间件也是不错的选择

基本原理

放弃强一致性,实现最终一致性

不要ACID了,BASE就可以,系统间的数据在某个时间窗口可能是不一致的,通过添加一些补偿机制,来保证数据最终能够一致。只要数据的消费者感知不到这份不一致,系统总是可用的,就没有任何差异。举个不很恰当的例子,同事给你转了一万块,输完密码后的1秒内,其实银行扣除了他一万块,但并没有在你的账面上增加一万块(此时数据就是不一致的)。但1秒后,你的账面上到账一万块,此时数据最终一致了。作为数据的使用方,这1秒的不一致,你基本是感知不到的,最终系统的账面还是平的,于是乎,实现了子系统间的数据一致。

实现的关键点

接口幂等性

考虑到宕机,服务异常,网络闪断等不可控因素,依赖的第三方接口需要支持幂等性。所谓幂等性,指接口支持重入,同等条件下,不管调用你几次,所产生的影响都应该是相同的。譬如说,使用支付接口转账,不管调用多少次,只要支付ID不变,都只会产生一次转账。

DEMO

用户提现,需要扣除本地系统余额,并调用第三份支付接口
begin transaction

    //扣除本地库的余额
    //并生成一个全局唯一的支付ID(1)
    pay_id = deducted_balance()
    
    //MQ or 入库一条日志
    //记录本次扣除操作,并标记为扣除中(2)
    message_id = queue_message()

end transaction

//使用(1)生成的全局ID进行支付
res = pay(pay_id)

//支付结果一般会异步响应
//如果有同步异常,则按需重试
//【【== 此处应注意重用pay_id ==】】
if should_retry(res)
  re_pay(pay_id)

~

//处理异步响应支付结果

//支付成功,更新(2)生成的消息状态
if ok 
  update_ok(message_id)
  return


//支付失败,调用第三方支付查询接口
//二次校验是否没有支付
res = query_pay_state(pay_id)

//的确没有转账成功,回滚事务,把扣除的账补回去
if error
  begin transaction
  
    rollback_banlance(pay_id)
    update_error(message_id)
    
  end transaction
  return

//其实支付成功了
update_ok(message_id)

~

//考虑到异步回调的时延等问题,一般还会开个worker
//定时去捞取(2)中的消息,并主动查询支付结果

~

//以上仅为伪代码,忽略许多细节
//譬如似涉及到金额的系统
//应提供足量的日志,监控异常,定时核账

MORE

分布式系统事务一致性解决方案

保证分布式系统数据一致性的6种方案

拾零散记公众号

原文地址:https://www.cnblogs.com/WuYiStudio/p/10842042.html