文章目录
  1. 1. 模型与背景提炼
  • 超时处理方式
    1. 1. 掉单查询
    2. 2. 结果通知
    3. 3. 并发提单
    4. 4. 幂等思路
    5. 5. 反交易先到
    6. 6. 前端用户不能一直等待处理思路
    7. 7. 通过对账保障数据一致性
    8. 8. 小结
  • 事情的经过是这样的,群友四哥发来一个问题,问大家有什么看法,我看了下,刚好之前接触过类似的业务场景,因此斗胆就问题谈谈自己的看法,抛砖引玉。

    问题如下:

    A系统联机同步调用B系统(A和B不是同一公司系统,不能用分布式事务),

    如何保证系统间数据准实时一致性(聊聊设计思路即可)?

    提醒:需要考虑调用超时、并发、幂等、反交易先到等。

    各种异常场景怎么处理要考虑更完善些,如事务隔离、并发、反交易先到调用方和服务方约定(前端客户不可能一直等着)

    这种聊思路的问题,往往问的都很大,或者说比较唬人,实际上遇到这种问题,我们要做的就是抽象。

    抽象出场景,抽象出问题的核心要点。

    我们能够提炼出要点,非同一公司系统(跨网络,异构)、反交易先到(我们基本能确定提问者大概想知道的思路与交易有关)、服务方预定,前端客户不可能一直等待(交易流程往往是长事务流程,不能简单依靠单个接口调用,基本上是异步流程)。

    有了这些前提,我们就可以基本抽象出讨论的背景和模型:

    模型与背景提炼

    因为前提是A、B系统分属不同的公司,也就是说A B系统是通过公网进行交互的两套异构系统,极有可能实现的技术栈也各不相同,因此互相之间只能通过暴露在外的接口进行交互,我们就认为是通过http接口进行的交互。

    由于是A系统调用B系统,因此我们可以抽象为点对点的消息通信场景,其中A为主动拉取方,B为被拉取方。

    问题提到说,我们说不能用分布式事务,其实是在说不能使用强一致/类2PC的事务实现,如2PC、3PC、SEATA等,但是可以利用诸如最大努力通知的柔性方式进行数据的同步。

    关于最大努力通知方案,笔者之前的一篇文章已经有过详细的讨论

    有了方案,我们接着抽象出 交易单 这个模型,并为其指定状态机:

    • 交易单创建时,状态为 初始化
    • A系统向B系统发送交易单时改为, 处理中
    • 如果B系统同步响应收单失败,则A系统修改状态为 失败
    • 同步B系统同步响应收单成功,则A系统修改状态为 已提单
    • 当B系统处理交易完成,通知A系统交易完成,则A系统修改交易单状态为 成功,(此处的成功为真实成功,因为已经发生了资金扣除/积分等标的物的消耗)
    • 当B系统处理交易失败,则通知A系统交易失败,则A系统修改交易单状态为 失败 (此时的失败假定为B系统在扣钱之前就失败)

    超时处理方式

    我们讨论一下提单请求发生超时应当如何处理。

    A系统的出口网关向B系统的入口网关发起提单请求,这是一个同步通信,对于同步请求的失败(如:签名失败,参数异常失败等),A系统可以发起重试,此时这种请求属于真实的失败,因为压根没有发起交易行为,所以原则上数据是一致的,对于资金而言就是没有发生扣减等行为。

    掉单查询

    如果A系统提交提单请求超时,此时未能收到B系统回复的同步 收单成功 的响应,则这时候就存在数据不一致的情况。这个场景就是所谓的 掉单,则A系统需要对掉单的数据(状态为处理中)发起掉单查询操作,思路就是定时发起查询,获取B系统对交易单的处理情况。

    一般而言B系统都会通知A系统发起掉单查询的建议时间,如发起交易单10分钟后即可有处理的确定结果,那么A系统就可以对已提交10分钟以上,状态为 处理中 的交易单发起掉单查询。也就是说,10分钟后,这类中间态的数据,AB系统间可以达成一致。

    特别的,如果一次掉单查询没能查到确定的结果,则可以设置下一次继续查询,这里推荐采用 时间衰减策略 进行查询,这是交易场景乃至中间件中常用的一种未知数据定时同步的通用思路。

    结果通知

    对于交易场景,处于对数据实时性的考虑,我们常常希望下游系统处理完数据之后能够及时通知我们结果。

    在这个场景中就是A系统需要对接B系统的 交易结果实时通知接口,当交易单被B系统处理完成之后,B系统会对交易单处理结果发起通知,及时回调A系统处理的结果。此时,A系统成为被调用方,B系统为调用方,相比于掉单查询,结果通知几乎是准实时的,从B系统发起通知到A系统接收到通知往往都在百毫秒级别(支付宝支付结果通知能够达到数十毫秒)。

    简单总结下,对于超时的处理,我们就是通过掉单查询和实时通知方式,通过主动轮询处理结果与被动接受结果通知的方式,通过推拉结合的方式共同保证AB系统之间的数据达成最终一致。

    并发提单

    对于并发提单而言,其实属于老生常谈类的话题。其本质在于分布式场景下请求的防重放处理思路。

    核心就在于请求串行化,我们往往通过CAS、加锁等方式进行处理。

    具体到具体的实现细节,CAS方式有数据库状态标识(状态机)、加锁方式其实就是分布式锁,简单的说就是通过分布式锁方式进行处理,通过对一笔交易单加分布式锁,获取分布式锁成功的请求才能发起请求,发起请求后写入幂等记录,完成请求后释放锁,防止并发提交。

    幂等思路

    关于幂等,展开说可以单独写一篇文章,那么择日不如状态,我们下一篇文章就聊聊幂等处理的那些常用姿势。

    这里为了解答问题,我们说说主要思路,对于细节就不展开了,懂的同学们应该能够很快的心领神会,不懂的也没关系,我们下篇文章就会对幂等进行详细的展开。

    对于幂等而言,我们通常需要通过幂等校验来进行,比如:

    • 高并发场景下,将幂等标识写入Redis缓存,用于对写请求幂等
    • 或者请求如果量不大,则通过数据库唯一约束进行幂等处理,保证只有一笔交易单落库(如唯一约束 订单号),唯一约束是最后一道防线,用于对写请求幂等
    • 低并发场景下,通过先查询,后插入(更新)的方式也可以进行幂等校验,但是高并发场景下会有重复更新/新增的风险,因此往往需要配合分布式锁共同作用,将并发请求串行化

    单单就幂等来说,查询天然幂等,更新则可以通过上面的方式进行幂等保证。具体的细节我们后续文章专门讲解。

    反交易先到

    首先明确何为“反交易”,反交易,顾名思义,反向交易,我们举个例子就好懂了。

    比如说,扣款的反交易,就是冲正(比如说,转账操作,扣除A的钱,给B加钱失败。则A扣除的钱需要补回,这个过程就是冲正。实际的冲正涵盖的范围更广,我们只需要简单认为是扣款的反向操作,但是要区别于提现和充值。)

    比如说,A系统请求对交易单支付100元,B系统扣款成功后向A系统返回支付成功的通知消息;此时B系统后续操作故障,导致该交易无法继续进行下去,则B系统对A系统扣除的100元执行了冲正之后,通知A系统交易已冲正退单。

    所谓反交易先到,就是说网络发生拥塞,导致冲正退单的消息,先于支付成功的消息先到了。

    我们的A系统的交易单不是有状态机么,状态机就是处理反交易先到的利器。

    我们要求对于交易的处理是串行的,如何串行,其实简单的说,通过状态机就能很好地实现。

    当然要说明的一点是,对于实际情况,需要具体业务具体分析,对于我们当前讨论的场景而言,我们通过状态机能够解决问题,具体过程如下,

    我们假定,A系统的交易单的状态机只能按照 处理中->支付成功->退单 这个流程进行流转,当退单先于支付成功到达时,我们需要在一个事务中同时完成流水的插入,交易单状态的更新。对于更新操作而言,我们的sql期望如下

    update order set order_status=退单 where order_status=支付成功 and order_id=xxxxx

    由于状态机只允许固定的订单状态迁移,我们在更新状态的时候带上老状态,实际上当前的order_status=处理中,因此update失败。最终处理为通知处理失败,A系统告知B系统对该通知进行重发。那么B系统就只能老老实实的重新发起通知,这也是一个合格的交易系统所必须具备的能力。否则你的系统不支持通知重复发起,用户体验也太差了。

    此时由于状态机的原因,交易单状态还是处理中,当被拥塞的支付成功的通知到达,交易单状态成功更新为 支付成功 此时执行的update语句为

    update order set order_status=支付成功 where order_status=处理中 and order_id=xxxxx

    后续重试的 退单通知(所谓的反交易)到达后,交易单根据状态机便能够成功进行流转,具体的update语句如下:

    update order set order_status=退单 where order_status=支付成功 and order_id=xxxxx

    从我们的分析看出,只要有状态机存在,无论如何,交易单的状态只能按照 处理中->支付成功->退单 这个方式流转而不会发生状态的跃迁跳跃。

    所以我们说,状态机,就是系统间处理反交易先到的利器,状态机也是交易类系统通用的神兵利器。

    前端用户不能一直等待处理思路

    还有一点就是前端客户不可能一直等着,实际上在上述的过程中我们已经解答了这个问题。

    我们本次分析问题采用整体的方案是基于最大努力通知的思路,核心的步骤就是 同步提单掉单查询结果通知。通过对这几个步骤进行结合,我们就能够避免前端一直等待,因为交易属于一个长事务业务,上游/前端只需要提交成功就可以去干别的事情了,剩下的复杂操作让下游系统慢慢处理,这其实就是体现了异步的思维。

    通过对账保障数据一致性

    最后还要提一下,对于交易系统而言,数据一致性保证的兜底方案就是对账机制,关于对账,我在近期也会单独写一篇文章进行详细讨论(又一个flag)。

    交易系统通常具备t+1的对账,简单的说就是,每天生成前一天的对账单,在我们的这个场景中,A系统每天都向B系统请求自己前一天的交易对账单,下载到本地,通过A系统自己的渠道流水号/交易单号,与B系统提供的交易单进行逐条的对账,这个过程往往能够通过定时任务来自动化的执行,把不一致的交易单对平。从而将两个系统之间的数据达成最终一致,比如说,A系统没收到B系统的通知,掉单查询也没有查到的交易单,往往最终通过对账都能够获取到数据的最终状态。

    小结

    本文,我们针对一个问题,给出了交易场景中常见的数据一致性的保障思路,并通过场景提炼,介绍了交易类场景设计过程中需要注意的思路,希望能够对读者朋友们有所帮助。如果有想法或者建议,欢迎进群讨论,笔者建立该微信群用于与读者朋友交流,只要是技术类的问题,都欢迎进群讨论,技术之路,感谢有你。

    3
    



    版权声明:

    原创不易,洗文可耻。除非注明,本博文章均为原创,转载请以链接形式标明本文地址。

    文章目录
    1. 1. 模型与背景提炼
  • 超时处理方式
    1. 1. 掉单查询
    2. 2. 结果通知
    3. 3. 并发提单
    4. 4. 幂等思路
    5. 5. 反交易先到
    6. 6. 前端用户不能一直等待处理思路
    7. 7. 通过对账保障数据一致性
    8. 8. 小结
  • Fork me on GitHub