如何处理基金交易系统中的分布式事务

Posted 写爪哇的小龙

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何处理基金交易系统中的分布式事务相关的知识,希望对你有一定的参考价值。


1. 简介



本文讲述的分布式事务是: 基金交易系统(我),资金代扣系统,TA交易系统 3个系统之间的事务。


如何在3个系统间保证数据的完整性 和 最终一致性,是怎么做的?


前提:基金交易系统 没有分库分表,应用拆分,因为那会带来另外纬度的分布式事务,这不是本文的目的。

假设 基金交易系统 为单体应用,只有一个Database实例。






2. 业务介绍和核心流程



基金申购的过程是最复杂,最有代表性的分布式事务处理;

基金赎回,交易确认也有分布式事务处理,不过相对于基金申购,属于其中的子集;

所以,本文只讲解基金申购的处理;


购买流程:


主要表:

如何处理基金交易系统中的分布式事务

如何处理基金交易系统中的分布式事务

如何处理基金交易系统中的分布式事务

如何处理基金交易系统中的分布式事务


t_payment: 用户申购生成一条支付记录,记录资金代扣的信息

t_order: 用户可见的订单数据,包含申购,赎回,分红,强赎/增/减,份额赠送等,只有用户发起的申购才会有一条 t_payment 支付记录;

t_hold : 记录用户购买单个基金的资产
t_message: 每次交易成功后都需要发送Message 到消息队列MQ; 若干的消费者: 活动应用,发放卡券奖励等;基金系统,发送交易申请成功短信等;征信大数据等,建立用户画像等;


t_payment.payFlowNo,

t_order.orderFlowNo

t_message.messageFlowNo

由基金交易系统生成, 需要保证同一业务内唯一 ;


核心流程7步;

交互4个系统【基金交易系统,资金代扣系统,TA交易系统,MQ Broker 】;


很明显,传统的单机事务已经无法满足我们整体的应用需求;

不过拿到外部系统返回结果后,操作若干table,保证局部的ACID还是可以的。






3. 问题分析



假设所有的外部系统正常的work,网络也ok,Database也ok,一切都没问题。

可事实不如愿,总会发生异常,分为两种: 内部的 和 外部的。


内部异常:

    1-7 执行过程中,基金交易系统(我),因为 不可控原因,服务进程死掉或 服务器宕机;

外部异常:

    1. 【2. RPC 调用资金代扣系统,完成支付】,网络超时,代扣系统挂掉;

    2. 【4. RPC 调用调用TA,完成申购】,网络超时,TA系统挂掉;

    3. 【6. RPC,发送消息到MQ Broker】,网络超时,MQ Broker挂掉;




举例可能出现的情况

 1.this.doPayment();//2. RPC 调用资金代扣系统,完成支付

   支付系统宕机,网络超时; 我们自己进程被kill

   后果:支付结果无法获取;

  

2. 【this.updateOrderAfterPayment();//3. 本地,代扣后更新支付订单和交易订单

    因为未知的原因,程序断掉 or 进程死掉 

    后果:明确收到了支付结果,后续流程丢失


3. 【this.doTATrade();//4. RPC 调用调用TA,完成申购

    TA系统宕机,网络超时,我们自己进程被kill

    后果: TA申购结果无法获取;


4. 【this.updateOrderAfterTATrade();//5. 本地,申购完成后更新交易订单和持仓

    因为未知的原因,程序断掉or进程死掉 
    后果:TA申购结果已经拿到,后续流程丢失


5. 【this.doSendMessage();//6. RPC,发送消息到MQ Broker

   MQ broker宕机,网络超时; 我们自己进程被kill

   后果:消息发送状态未知,是否发送成功


6. 【this.updateMessageAfterSentOut();//7. 本地,更新消息发送状态

    因为未知的原因,程序断掉or进程死掉 
    后果:消息发送结果已经拿到,无法更新消息为 已发送


如何在多种异常可能出现的情况下,保证业务数据的完整性,一致性?






4. 解决思路



CAP,BASE,分布式事务,这些都高大上的理论,不再多做解释,自行查看;

https://blog.csdn.net/rickiyeat/article/details/70224722



调用外部系统获取结果判定原则:

1 成功:

    通信协议成功 && 业务字段明确成功

2 失败:

    通信协议成功 && 业务字段明确失败

3 结果不明确:

     RPC超时 || RPC抛出异常 || 通信协议失败

   


我们的解决方案: 补偿 & 幂等;

1. 资金代扣

    资金代扣系统提供 根据 payFlowNo 支付流水号查询支付订单状态的接口;

    在不明确支付结果,根据payFlowNo 查询支付状态;

2. TA申购

    TA交易系统提供根据 orderFlowNo 订单流水号查询 订单状态的接口;

    在不明确申购结果,根据orderFlowNo查询申购状态;

3. 发送消息到MQ Broker

    消息体中包含字段messageFlowNo=UUID 唯一消息编号;

    基金系统保证至少成功发送1次,明确得到MQ Broker结果时,才会标记已发送;

    MQ 消费者根据messageFlowNo保证只消费一次,避免重复消费;



1. 资金代扣2. TA申购 后续任务补偿中,返回结果一般有4种 :

1. 成功,补偿后续流程

2. 失败,更新订单状态

3. 未收到请求, 重新下单 or 其他,看业务方的需求

4. 处理中;如果长时间还处于处理中的订单【如15分钟】,需要人工介入






4. 具体实施



使用Database的事务来保证局部多表操作的ACID;

使用3个Job来补偿来整体流程中可能出现的异常;

    1. PaymentRetryJob, 补偿支付前后的异常

    2. TATradeRetryJob,补偿TA交易前后的异常

    3. SendMessageRetryJob,补偿发送MQ前后的异常



购买流程和调用方法如下:

如何处理基金交易系统中的分布式事务


如何处理基金交易系统中的分布式事务

如何处理基金交易系统中的分布式事务

如何处理基金交易系统中的分布式事务

如何处理基金交易系统中的分布式事务

如何处理基金交易系统中的分布式事务

如何处理基金交易系统中的分布式事务




补偿Job工作内容如下:

(Job职责单一,避免Job功能重叠)

(因为功能重叠,还需要另外的并发控制)

如何处理基金交易系统中的分布式事务


如何处理基金交易系统中的分布式事务



Job注意事项:

    1. 提高用户体验,5分钟一次

    2. 避免任务重复执行,如多个节点同时运行任务 || 单次补偿过多条记录

    3. 查询补偿任务增加时间限制,避免正常流程和补偿Job同时运行相同逻辑






5.结束语



查看【4. 具体实施】的实现细节,再看看我们的主流程,是否覆盖所有case?







以上是关于如何处理基金交易系统中的分布式事务的主要内容,如果未能解决你的问题,请参考以下文章

你们公司是如何处理分布式事务的?

微服务架构中的分布式事务,如何处理超时和失败的提交

分布式服务的事务如何处理?比如dubbo,服务与服务之间的事务怎么处理比较好,现在有没有开源的解决方案?

处理恢复购买时如何处理旧交易?

pyspark--FPGrowth:transform 如何处理看不见的交易?

分布式事务怎么处理?一文告诉你!