笔者现在碰到一个项目,项目是类似于一卡通的数据模型,就是所有的金额存放在A系统中,A系统负责金额的处理,B系统负责业务处理,并向A系统发送相应的命令以进行数据处理,并返回相应处理的结果。
A和B系统之间的调用,由于项目和技术原因,并没有使用什么高深的调用手段,就是由A系统开放一个http接口,然后B在调用时即通过构建一个http请求向A系统发送一个请求,并对返回结果进行判断处理。
整个项目之间调用可以分为以下两个阶段
1 B系统向A系统请求,这中间涉及到请求发送和B系统处理
2 B系统接收到结果,进行自身业务处理
现在的问题在于:在1和2阶段之间,都可能发生异常,在1阶段可能发生的错误有比如http请求异常(连接失败),A系统处理异常,B系统处理业务异常。对于在第1阶段发生的错误,由于B系统可以接收到相应错误,所以没有什么问题。问题在于,当B系统发生异常时,A系统不能回滚相应的数据信息,或者回滚信息本身就不能完成。对于用户直接的结果就是,钱被扣了,但东西没买到(因为B系统显示未付款),或者A系统多扣了一笔(在第一笔失败之后, 又点了一次购买)。
从网上已经搜索了相应的的处理手段,大都是说需要有一个事务补偿机制来完成。即当系统发生错误时,通过事务补偿机制来保证数据恢复。现在的问题是,怎么样来实现这个事务补偿,是通过又向A发送请求恢复相应已经扣除的数据吗(如果发送请求时,又出现了错误怎么办?)?还是有其它的手段。
使用项目的用户是对计算机不懂的大妈级人物,因此期望用户操作系统来达到数据补偿不现实。
请大家谈谈解决思路?
说说我的经验:
数据库上面的账户跟资金有关的有3个字段 ‘当前余额balance''未确认credit金额'和‘未确认debit金额'
这里的credit/debit引用了财务上的借贷的概念,credit金额总是负数(消费、取款、转出等),debit金额总是正的(退款、存款、转入等)
为了保证资金不出问题,一个账户的可用额度总是 balance+credit(注意这里credit是负数,相当于当前balnce - abs(credit)。而debit在被确认之前,是不可用的。
交易确认的概念:在交易确认之前,交易的金额被累计在credit/debit字段中,确认之后,create/debit里面的金额被合并到balance中。确认之后的交易不能再改变。(退货/退款或是其他交易在在实际应用中应该被定义成单独的交易类型)。实际的交易系统必需在保证资金安全的前提下,尽量减少消费者的等待时间。
下面说说实际的交易过程:
跟楼主的应用场景一样,A系统是资金管理系统,B系统是业务交易子系统,分别在不同的网络位置,整个过程的网络通讯可能在任何时刻出现异常。更具体点我们来模拟一下使用储值卡在商场零售交易的情景(类似的系统有相同的特点:交易在客户端发生,而资金在服务器端管理):
同步、实时交易:
1:B系统,商场零售客户端在计算出消费金额后,在本地系统里记录一条记录,然后向资金管理系统发送一个卡消费交易请求。
2:A系统,收到消费交易请求后,判断对应的储值卡的可用余额是否>=消费金额。可用余额的计算方法是当前余额-未确认消费金额。如果可用余额>=消费金额,则记录该消费记录,并在credit(非确认消费金额)字段上累加这笔消费金额,并返回允许刷卡成功的应答。(当然实际系统还会有交易流水号和/或授权号等更多信息,这里就不讲太细了)
3:B系统:(这里是确认交易是否成功的地方,简单地说就是决定是否允许你拿着购买的东西走人)。如果收到一些类似余额不足的非刷卡成功的应答,则取消次交易。
如果收到刷卡成功的应答,在消费者签名后,则可以确认交易成功,允许客户把购买的东西拿走。如果消费者拒绝签名(当然也没有拿走商品),此交易被取消。但无论是否签名、此交易是否成功,此结果只在本地系统(B系统)里做相应记录后,结束当前的同步交易过程,(消费者只等待到这里)。后续的交易确认、交易取消信息,由后台系统异步完成。(实际可以采取每隔一段时间批量提交的策略)。
在收到A系统的应答之前的任何一个时刻,如果发生网络异常,资金管理系统(A系统)的逻辑不变,消费子系统(B系统)对次交易记录标记为异常,并取消次交易。此时,A系统的记录里可能有此消费刷卡成功的记录,也可能没有。
以上过程是在B系统的前台POS机上完成的同步实时交易子过程。该子过程完成后,此次交易已经被确定是否交易成功,或被取消,准确的交易结果/过程被记录在B系统里。此时可能会与A系统的信息不一致,但以B系统为准(因为实际交易是发生在这里的),读者可以自己分析此时上述过程中可能会发生哪些异常,会导致A、B系统的交易信息会有哪些不一致。总之,所有结果最多都是交易失败,而储值卡的可用余额却被扣除的情况。对资金管理系统来说是保证安全的。
这里要重提一下可用余额的概念,假设账户余额有1000元,此次消费金额是100元,则不管该交易是否成功,除了消费请求根本就没有发送成功的情况,该账户的可用余额都变成了900元,可以用于其它/后续消费。
后台异步、非实时过程
交易确认:
此过程在B系统通常是后台、异步、延时、批量提交的。B系统把在前台POS机上同步实时交易记录提交到A系统。A系统逐条处理,记录交易记录,在未确认消费字段(credit)减去交易金额,如果交易确认成功,同时在账户余额上减去交易金额。此时,A/B两系统的数据最终一致。如果交易成功,可用余额没有变化,如果交易取消,可用余额会恢复到交易前的数量。
此过程如果发生网络异常,B系统需要采取保守的策略,不断的尝试发送数据,允许重复,不允许丢失,A系统需要有能力处理重复的数据(这个不难,每个交易都有唯一的流水号和或授权号)。
可靠保证的最后一步:交易清算过程
通常完成上面的过程后,只要程序没有bug,不管是否发生过网络异常,A/B两端的数据应该是一致的。但对可靠保证更高的系统来说(本人认为只要涉及到钱,还是尽量多做一些补偿措施).
还有一些灾难性质的情况,比如B系统的数据库宕机,致使数据丢失,此时可能需要根据打印出来的交易单据(POS机上的小票),和有客户签名的刷卡单据,用人工的方式重新在B系统上逐笔核对和录入。如果A系统数据库宕机,则需要从所有的B系统重新传输重上一个恢复点后的交易记录。
此过程首先核对A、B两系统的当天(通常)的所有交易记录的汇总数据及其校验结果,如果一致,可以认为两端数据已经一致。否则,重新、逐笔从B系统把交易记录传输到A系统。(可能有一些优化措施,可以不用传输所有记录,但要保证不降低可靠性)。
这是一个数据集成的过程,B系统可以先把交易记录打包、并压缩成一个或多个文件再传输,这样做效率会比较高。
在过去的有些银行系统里,通常都没有后台传输交易记录的过程,只是在每天晚上的清算过程才传输交易数据。如果你在柜台或者取款机上取现没有成功,却发现账户余额却少了,通常只有等到第二天,这笔钱才会退回到你的账户中。注意,实际上你看到的账户余额是我们上面提到的可用余额,应用系统也一样,不要给用户看到过多细节,他看到的余额就是可用余额。
上面没有提到存款/充值的例子,交易过程类似,但不同的是只有在该交易被确认后,才会反映到可用余额和账户余额中,这是由于资金安全的考虑。为了提高实时性,通常在存款交易的同步过程完成后,B系统可以立即向A系统post确认信息。(post是指发送请求后立即返回,不等待应答,服务器端也无需返回应答,即使发送失败也被允许)。如果该确认信息被A系统收到并处理,此存款会立即有效,否则就要等待后续的确认过程和/或清算过程了。此种方法也可用于交易被取消的情况。
另外提一个个人对数据事务处理的看法,不要把A/B系统的网络通讯过程纳入到数据库事务当中去。在B系统,总是先在本地数据库记录完后再去跟A系统通讯,在A系统,总是在接收到交易请求后再开始数据库事务,并在数据库事务结束后再返回应答。网络通讯(非局域网)是可能在任何时候发生异常,甚至被阻塞产生长时间延迟,这些都会严重影响数据库性能。
说了一大堆,可能有些复杂。为了提高资金系统的安全性和可靠性,多做一些事情是必要的。
本人非金融交易的专业人士,观点可能是错误或不准确的,欢迎批评指正,但不对此产生的任何后果负责。
如果你有对涉及到资金交易安全的观点和经验,欢迎并感谢交流和分享,我也对此很感兴趣,希望能学习和了解到更多的经验和知识。
刷过信用卡吧,想想Pos机的流程应该能解决你的问题。
买过股票吧,想想买股票的流程,
这种情况在金融系统很常见的,股票在交易所或券商的系统中记录着,
钱在银行的系统中记录着,怎么保持彼此步调一致,和你的问题是否很相似?
简单跟你说就是
你说的 A系统不能收到数据立马就处理,
要分为好几个步骤,接收数据,验证,反馈验证结果给B系统,
B系统确定交易,告诉A锁定数据,并通知B系统交易达成,
然后A系统再慢慢处理数据。
涉及钱的东西都是先锁定,再处理!
就像你买股票的时候,股票不是立刻到账,钱也不是立刻到账一样,先锁定。
去银行汇款,钱也不是立刻到账,起码要等个几分钟。