简易支付系统的架构设计

本文探讨了一个简易支付系统的架构设计,包括发起支付、发起退款和接收回调三个核心流程。系统采用异步编程方式,利用Java8的ExecutorService和CompletableFuture,结合RabbitMQ、Redis和MongoDB。MongoDB用于存储动态扩展的数据结构,以减少对接成本。支付流程中,支付任务首先被放入队列,由消费者从MQ中取出执行,结果缓存在Redis中并最终同步到MongoDB。退款流程与支付类似,退款单号由支付系统生成。回调处理部分则采用事件驱动,解析不同渠道的回调内容并持久化到MongoDB。
摘要由CSDN通过智能技术生成

作者:文刀(微信公众号:jishuhui_2015),Java Web全栈工程师,高级架构师,技术布道者。曾任两家上市公司的技术主管,从事微服务架构设计,DevOps团队建设工作,在电商、LBS、IoT等相关应用领域有丰富的项目经验。
责编:钱曙光(qianshg@csdn.net)

支付系统是一个老生常谈的话题,每个公司开发的支付系统不尽相同,因为业务形态并不太一样。

本文并不是讲一个大而全的支付系统——一个支付系统应提供支付渠道管理、支付网关、基本支付/退款/转账能力、支付记录/明细,及其相关的监控运维系统。至于所谓的账务清算,对账功能,账户体系,风控体系,现金流量管理,应纳入到「财务系统」,也应该是大佬们谈论的都是广义的「支付系统」吧!而本文只谈狭义的「支付系统」。

目前,支付的流程包含了三大部分:发起支付,发起退款,接收回调。考虑到吞吐量的影响,将原先同步的编程方式改为异步的编程方式,不出意外的话,将会使用到Java8的ExecutorService和CompletableFuture。

此外,还用到了公司其他的现成的东西:RabbitMQ、Redis、MongoDB。

笔者打算将这套支付系统设计成与具体业务无关,并纳入到公司的公共平台系统中。

一、发起支付

这部分主要讲客户端和服务端如何配合完成一次支付请求。服务端必须要有一个意识,最终发起支付的还是客户端,服务端提供一些必要的参数配置信息。

发起支付的架构图如下所示:

这里写图片描述

发起支付架构图

跟着标注的序号,可以跟踪到一个支付请求是如何发起的(Sequence Diagram就免了),流程描述如下:

  1. Submit a pay task,当客户端需要发起支付的时候,起始是向支付任务队列里面加入了一个新的支付任务,这个过程是异步实现的。先根据客户端提交的参数,构造好一个新的支付任务;
  2. Offer a task,开启一个异步任务,做的事情就是向MQ中添加一个新的支付任务,等待被消费;
  3. Pay task description,一旦异步任务被成功创建,将会把第一步构造好的支付任务信息直接return给客户端;
  4. Poll a task,与此同时,支付任务的消费者将新的支付任务poll下来进行执行;
  5. Send a pay request,这一步需要根据实际情况而定。并不是所有的支付请求都要先经过第三方支付平台,比如支付宝;而对于微信,则还需要凭支付参数申请一个prepay_id,再经由客户端发起支付;
  6. Response,没什么好说的,第三方渠道返回的支付必要参数;
  7. Cache result,至此,一个支付任务可以算是完成了,可以将任务的执行结果(无论成功与否)缓存在Redis中,随时等待客户端的回访;
  8. Query result,客户端在提交支付任务后,间隔一定时间后(建议2~3s),发起一个结果查询的请求;
  9. Query,直接进Redis查找结果;
  10. Synchronize,这是一个异步的操作,将支付任务的执行结果“顺便”同步到MongoDB中,并删除Redis中缓存的任务执行结果。持久化到MongoDB主要是为后续的容错,重试,数据分析等提供落地的数据源;
  11. Return,由Redis返回给应用服务器;
  12. Return payment,应用服务器再将最终的支付对象返回给客户端。

让我们更深入一点,我们来看三张Class Diagram:

① 先说说支付任务(PayTask)部分。PayTask和Payment两个都是MongoDB中的Document对象,但在任务执行期间,PayTask是用Redis进行缓存的,方便客户端随时发起Query,任务执行成功后,会生成Payment对象,最终PayTask和Payment都会持久化到MongoDB中。在PayService中,有对支付任务的一些基本操作,包括任务提交,取消,重试,构建等等。

这里写图片描述

② 再说说任务的执行(runner)。这部分和RabbitMQ紧密相关,一旦一个支付任务形成了,就会放入任务执行队列中,由消费者取出执行。在TaskRunner中,有两个基本的接口方法:run(task)、retry(task),分别是执行任务和重试任务。在AbstractPayTaskRunner中已经封装好了这两个方法,继承AbstractPayTaskRunner需要实现doTask方法,从返回值可以看出,这个过程是异步化的。关于Retry机制,用户可以设置重试与否,一旦设置了TaskInfo.needRetry=true(不出意外,默认就是允许重试),就启用了Retry机制。还可以设置重试的次数(TaskInfo.retryTimes),默认三次,分别间隔1s,2s,3s,间隔时间以公差为1的等差数列组成。当然不会让用户无限重试,系统内置有一个最大重试次数,最大重试次数内置为5次。

为什么是5次?

你感受一下,1s,2s,3s,4s,5s,整个请求链条就被拉长到了15s,这对客户端简直就是灾难了!!

这里写图片描述

③ 接着说一

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值