在分布式事务场景下,如何设计一个高可靠的跨系统转账

转载至我的博客https://www.infrastack.cn ,公众号:架构成长指南

大家好,我是蜗牛哥,跨系统转账网上教程很多,但是都是讲的比较浅,这个功能看似简单,但是细节很多,要做好没那么容易,因为涉及到分布式事务、交易安全性等方面,做不好就出现资损,本文讲一下如何设计一个高可靠跨系统转账,以及要关注的重点

示例说明

假设有用户 A在 银行A 转账给B银行的用户B 100 元

这里只是为了便于理解,所以才把系统命名为银行A/B,具体可能与银行的流程有点细微区别

会遇到哪些问题?

转账失败,不能直接回滚

要根据返回的异常来判断,如果接收到的异常是一个业务异常,并且异常码是双方约定好的,那么可以进行回滚,如果返回的不是一个明确的异常,,那么不能擅自回滚,因为可能是网络超时异常,而网络超时,又分为响应超时和请求超时,如果是响应超时,对方系统可能已经入账了,所以要进行重试操作确认

面试题:超时异常,有哪几种情况,怎么处理?

系统重试要保持幂等

假如网络超时进行重试,入金方的接口需要支持幂等,否则会出现可能重复入金,而幂等条件是根据出金方的业务流水号+渠道号进行查询判断

  1. 如果有记录,并根据状态,来决定响应结果
  2. 如果没有记录则进行入金,在返回对应的响应结果

如果失败,那么出金方需要进行解冻回滚操作,如果成功,那么需要进行解冻出金操作。

同时入金方还要设置此组合字段为唯一索引,这样可以避免重复插入的问题,比如:未查询到数据,则进行插入,正好前面一笔请求事务未提交,如果不设置唯一索引就会导致出现重复插入的问题。

交易安全性

由于这种资产操作非常敏感,稍有失误影响非常大,所以交易安全性是非常重要的,比如:有攻击者知道B银行的入金接口,那么直接调用,他的账户就会加钱。。。,所以要进行以下安全措施

要进行签名调用

在转账前用私钥对账户进行签名,然后给B银行颁发一个公钥,进行入金的签名验签操作,来保证此请求是正常请求。

要对交易的时效性进行校验

为了进一步保证交易的安全性,双方要约定好一个交易的时效性,比如5 分钟,在进行接口调用时携带请求时间,如果这个请求时间是5分钟之前的进行拒绝,等待重新发起。

要进行系统对账

除了签名,双方系统还要进行对账,而对账又分为总账对账和明细对账

总账对账

比如查看银行A出金总额是否等于B银行的入金总额,对账频率有小时、天不等,计算公式如下

shell 复制代码
转账给银行B总额==接收到银行A的入金总额 ?
明细对账

除了总账要进行核对,明细账也要进行核对,因为总账不平后,要确保那一个账户出现问题,为了实现明细对账双方系统要保留对方系统流水号,这样才能对应起来,对账频率一般是天

要考虑并发扣款

在进行账户操作时,要考虑并发问题,进行加锁处理,否则会出现资损,例如

  1. 订单a和订单 b同一时间都查询到了,账户余额为1000
  2. 订单a扣款200,订单b扣款 100
  3. 假如订单 a先执行,那么账户余额为800,订单 b 修改为账户余额为900,最终为 900,反正则为 800,都不对

具体可以查看并发扣款,如何保证结果一致性

涉及到表可能有哪些?

出金方

转账流水表

此表可以进行对账,也可以进行定时任务重新发起重试

sql 复制代码
- 主键
- 流水号
- 用户 ID
- 方向:转出转入
- 金额
- 目标方流水号
- 时间
- 状态 (等待调用、调用成功、调用失败)
账户表

此表的作用不用多说,主要说下冻结资金密度,防止真正扣款时账户上没钱,导致交易失败,所以一般都是先进行冻结,如果失败则进行解冻

sql 复制代码
- 用户 id
- 总金额
- 冻结资金
- 账户状态(正常 冻结)
- 时间
冻结记录表

记录冻结流水,防止出问题没法追溯

sql 复制代码
- 主键
- 流水号
- 用户 Id
- 金额
- 类型:冻结、解冻
- 关联的业务流水号
- 时间

入金方

以下表为最核心的表,但不是最全的表,比如应该还有账账务流水表、账务订单、热点账户表等

渠道转账流水表

此表可以进行对账,也可以进行定时任务重新发起重试

sql 复制代码
- 主键
- 流水号
- 渠道
- 业务方流水号 //后期幂等要根据此字段进行判断,所以此字段+渠道号为唯一索引
- 用户 ID
- 方向:转出转入
- 金额
- 时间
- 状态  (1成功 2失败)
账务表
sql 复制代码
- 用户 id
- 总金额
- 冻结资金
- 账户状态(正常 冻结)
- 时间

最终流程应该是什么样的?

流程有4个,分别为

  • 正常的转账流程
  • 补偿转账流程
  • 总账对账流程
  • 明细对账流程

其实这也是分布式事务最通用的实现方式,失败就重试,直到最终成功,不管你是 tcc、还是其他的实现方式,只要出现异常,系统最终都要通过定时去重试,直到最终 一致,感兴趣可以去看看 SEATA 源码,遇到异常也是通过定时任务进行重试。

转账流程

转账补偿流程

这个流程是定时任务定时发起的,查询小于等于当前时间-指定时间,状态为等待调用的转账记录并重新发起转账

sql 复制代码
select * from transfer_list where   update_time <= #{queryEndDate}

总账对账流程

明细对账流程

明细对账,如果数量不大,一天天对没问题,现在银行大多数是基于这种做法,如果文件比较大,可以考虑使用Merkle树,这里就说传统的方式

直接查询对比

这种方式最快,数据不大可以这样搞,同时也需要对方系统提供接口支持

基于文件对比

这种方式也是比较常用的方式,适合数据量大的对比,一般银行会这么做

总结

以上我们介绍了如何设计一个高可靠的系统转账,可以看到还是比较复杂的,细节很多,主要要考虑补偿、安全、并发扣款几方面,这几方面做好才能设计一个高可靠的系统转账。

扫描下面的二维码关注我的微信公众帐号,在微信公众帐号中回复◉加群◉即可加入到我们的技术讨论群里面共同学习。

相关推荐
程序猿进阶6 天前
内存占用估算方法
java·jvm·面试·性能优化·职业发展·架构设计·对象池
灰色孤星A12 天前
分布式事务学习笔记(四)微服务实现Stata AT模式、Stata Saga模式介绍
微服务·seata·分布式事务·at模式·saga模式·微服务实现seata
灰色孤星A17 天前
分布式事务学习笔记(二)Seata架构、TC服务器部署、微服务集成Seata
分布式·微服务·架构·seata·分布式事务·tc服务器·微服务集成seata
Roc.Chang22 天前
日志收集工具 Fluentd vs Fluent Bit 的区别
架构设计·日志收集·分布式日志
长安初雪1 个月前
微服务集成 Seata
微服务·云原生·架构·seata·分布式事务
丁总学Java1 个月前
使用kafka改造分布式事务
分布式·kafka·分布式事务
JinYD20182 个月前
分布式事务和一致性
分布式·分布式事务·一致性
tmax52HZ2 个月前
分布式事务-使用队列实现最终一致性
消息队列·分布式事务·cap·最终一致性·下单扣减库存
普通程序员A2 个月前
springCloud组件专题(五) --- seata
后端·spring·spring cloud·面试·seata·分布式事务
lpruoyu3 个月前
【雷丰阳-谷粒商城 】【分布式高级篇-微服务架构篇】【25】【分布式事务】
分布式·微服务·架构·分布式事务