分布式事务之本地消息表解决方案(跨地区转账实际案例)

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 分布式事务之本地消息表解决方案(跨地区转账实际案例)

一、序言

1、现状


最近在做一个跨地区转账的功能,先说一下问题现状,公司业务范围主要分布在新加坡、香港和迪拜,相关交易、卡、账户等数据应各地区监管、合规要求必须分地区物理隔离,关于分库我们选择了中间件Sharding-Proxy,分片键为某个地区的区域码,所有的分片表都会带上区域码这个字段。


cf04f6870e584f7b9f50a7e9646cf3c6.png

如果是同地区转账,动账,交易记录读和写,带上当地区域码,所有的数据库请求都会由Sharding-Proxy路由到该地物理库,所以同地区转账同步在一个事务内跑完其实是没有问题的。


2、问题


如果是跨地区转账,比如从香港到迪拜,或者从新加坡到迪拜,那么问题就来了。虽然Sharding-Proxy本身支持分布式事务,但跨物理库分布式事务之前并没有过实际经验。


同时由于迪拜地区和新加坡、香港地域相隔很远,即使走专线网络传输时间也会比较长,而且转账业务中涉及到两个不同地区多个表的读写,余额变动时也会给出入账账户加上数据库行锁(排它锁),在大事务中很容易超时,同时接口响应慢也会影响用户体验。


于是,我们决定把出账和入账分离,入账通过MQ做成异步处理。


这个时候问题又来了,既然不能通过数据库本地事务保证ACID,那么分布式事务的问题该怎么解决呢?


二、方案探索


开源分布式事务解决方案有Seata,本身提供了ATTCCSAGA还有XA事务模式。但是该方案比较重量级、对外不透明,加上还要部署seata-server,额外增加了维护成本。

最后还是决定选择比较轻量级的解决方案,MQ+本地消息表+重试补发,大致流程如下。


671305731eab4b8988e820f0d2ecef15.png



先看出账方,出账记录、实际出账、本地消息表都在同一个事务,只要出账成功,那么本地消息表中一定会有转账相关的消息。


再看入账方,入账成功后,向ack_queue发送入账成功确认消息。


如果消费成功且入账成功,那么本地消息表中的消息会被逻辑删除,状态置为deleted。


如果消息发送失败、消费入账失败,本地消息表中消息状态会一直为undeleted状态,这时出账方会有一个补偿定时任务轮询本地消息表中状态为undeleted的出账消息。为了减少消息积压,当重试到一定次数后,停止发送消息到MQ,同时发送告警邮件给开发人员处理。


注意:入账方消费消息时需要做幂等,否则会重复入账,这里加上分布式锁就可以解决。


三、根据实际业务进行调整


实际我们的系统更加复杂,普通的转账是从A账户到B账户,而我们的转账A账户可能还会共享企业账户的余额。跨地区转账就更复杂了,每个地区都会有一个对公账户,资金变动流程如下:

60fc3ebbf0594f96a4aa7816c577db27.png

如果从香港地区的个人账户A转到迪拜地区的个人账户B,个人账户A和个人账户B又分别共享企业账户CACB的余额,那么完整的账户资金变动流程如下:

  1. 企业账户CA自动划账到个人账户A
  2. 个人账户A出账到对公账户PA
  1. 对公账户PB出账到个人账户B
  2. 个人账户B自动划账到企业账户CB

1、定时补偿扫表改为扫缓存


如果是直接轮询本地消息表,由于我们的本地消息表是广播表(PS:各个地区物理库都会有相同的表数据),查询时会随机查询各地区物理库,如果随机查询的是迪拜物理库本地消息表的记录,那么查询就会比较慢了。


因此在写完本地消息表后,我们同时会将本地消息表的记录写到Redis中,后续定时任务补偿直接扫缓存而不扫表。


同时在入账消费者方成功处理入账后,除了逻辑删除本地消息表中的记录(置为deleted),还要删除Redis缓存中的记录。


问题:写本地消息表和写Redis缓存这里可能会出现缓存不一致的情况,如果本地消息表写成功了,但Redis缓存写失败了,那么在做定时任务补偿扫缓存时会丢到该记录,这里缓存一致性需要保证。


2、出账异步处理


可以看到,出账时从企业余额共享账户CA个人账户A对公账户PA,中间会有3个账户的余额变动和出账记录,如果是同步返回,响应时间会比较长,也会影响用户体验,出账操作这里可以通过线程异步。


3、去掉ack_queue


目前系统出账方和入账方并没有跨系统,数据库也没有根据业务做垂直拆分,因此消费者入账成功后可以去掉发确认消息到ack_queue中,直接在消费端操作数据库将本地消息表中的记录状态置为deleted并删除缓存就好。


4、入账失败一直重试


跨地区转账业务这里还比较特别,有可能会出现公账的钱不够扣,导致交易失败。如果转入方公账钱不够扣就会导致入账失败,但转出方实际已经扣款成功了,因此入账操作必须成功,出账方定时任务针对本地消息表中的Pending记录会一直重试,直到入账方公账金额够扣,入账交易成功。

备注:在实际业务中,跨地区转账入账方真正到账是有延迟的,虽然出账和入账操作程序只需要在账面上进行余额的扣和减,但是比如从香港地区到迪拜地区,真正的钱要和当地结算后汇款过去才能到账的。


四、可能出现的系统瓶颈


1、各地区公账可能会出现抢锁超时


目前在每个地区都只设有一个公账账户,而在做跨地区转账时,每次都会操作出账方和入账方的公账余额。而在做余额变动时,将会对指定账户加数据库行锁(排它锁),虽然转账业务并不是很频繁,但是如果并发上来确实会导致过多线程等待数据库行锁释放,可能会出现锁争抢超时。


2、出账时异步扣款线程池大小不够用


由于扣账涉及多个账户的余额变动以及业务表记录,扣账操作响应时间会比较长,所以我们通过线程异步,提升用户体验。同时这里扣张操作核心线程数、阻塞队列长度和最大线程数不是很好把控,还是需要根据实际请求量进行调整,拒绝策略我们设置的是交由主线程执行。

相关文章
|
6月前
|
存储 数据挖掘
Vsan数据恢复——Vsan分布式文件系统数据恢复案例
一台采用VsSAN分布式文件系统的存储设备由于未知原因关机重启。管理员发现上层的虚拟机不可用,存储内的数据丢失。
199 30
|
7月前
|
负载均衡 NoSQL 算法
Redisson分布式锁数据一致性解决方案
通过以上的设计和实现, Redisson能够有效地解决分布式环境下数据一致性问题。但是, 任何技术都不可能万无一失, 在使用过程中还需要根据实际业务需求进行逻辑屏障的设计和错误处理机制的建立。
357 48
|
7月前
|
SQL 存储 分布式数据库
分布式存储数据恢复—hbase和hive数据库数据恢复案例
分布式存储数据恢复环境: 16台某品牌R730xd服务器节点,每台服务器节点上有数台虚拟机。 虚拟机上部署Hbase和Hive数据库。 分布式存储故障: 数据库底层文件被误删除,数据库不能使用。要求恢复hbase和hive数据库。
272 12
|
存储 SQL 微服务
常用的分布式事务解决方案(三)
常用的分布式事务解决方案(三)
|
关系型数据库 MySQL
常见分布式事务的解决方案(一)
常见分布式事务的解决方案(一)
|
存储 监控 固态存储
【vSAN分布式存储服务器数据恢复】VMware vSphere vSAN 分布式存储虚拟化平台VMDK文件1KB问题数据恢复案例
在一例vSAN分布式存储故障中,因替换故障闪存盘后磁盘组失效,一台采用RAID0策略且未使用置备的虚拟机VMDK文件受损,仅余1KB大小。经分析发现,该VMDK文件与内部虚拟对象关联失效导致。恢复方案包括定位虚拟对象及组件的具体物理位置,解析分配空间,并手动重组RAID0结构以恢复数据。此案例强调了深入理解vSAN分布式存储机制的重要性,以及定制化数据恢复方案的有效性。
329 5
|
11月前
|
运维 数据挖掘 索引
服务器数据恢复—Lustre分布式文件系统服务器数据恢复案例
5台节点服务器,每台节点服务器上有一组RAID5阵列。每组RAID5阵列上有6块硬盘(其中1块硬盘设置为热备盘,其他5块硬盘为数据盘)。上层系统环境为Lustre分布式文件系统。 机房天花板漏水导致这5台节点服务器进水,每台服务器都有至少2块硬盘出现故障。每台服务器中的RAID5阵列短时间内同时掉线2块或以上数量的硬盘,导致RAID崩溃,服务器中数据无法正常读取。
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
246 5
|
程序员
后端|一个分布式锁「失效」的案例分析
小猿最近很苦恼:明明加了分布式锁,为什么并发还是会出问题呢?
149 2
|
分布式计算 NoSQL Java
Hadoop-32 ZooKeeper 分布式锁问题 分布式锁Java实现 附带案例和实现思路代码
Hadoop-32 ZooKeeper 分布式锁问题 分布式锁Java实现 附带案例和实现思路代码
196 2

热门文章

最新文章

下一篇
oss云网关配置