如何处理基金交易系统中的分布式事务
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,服务与服务之间的事务怎么处理比较好,现在有没有开源的解决方案?