之前撤销接口参数是 order_code , 现在变成 order_code、trans_id 。
通过 trans_id 将扣款和撤销绑定到同一个操作事务上 , 只会撤销相应 trans_id 的扣款操作 。
trans_id 由客户端根据当前时间毫秒数生成(后面会说为啥取毫秒时间戳) , 它不一定需要全局唯一 , 只需要针对同一个订单是唯一的即可 。
加了事务的概念后 , 小林和小李发现压根不需要通过调度系统不断尝试 , 只要保证撤销接口调成功就能保证对应的扣款事务一定能够被撤销(或者阻止执行) 。
现在撤销接口做两件事:
- 写入一条撤销记录;
- 试图撤销掉已经产生的扣款;
文章插图
再看看扣款的逻辑 。
扣款记录表大致长这样子:
文章插图
扣款逻辑如下:
- 先检查是否存在该订单的扣款记录;
- 如果不存在 , 则走正常扣款流程;如果存在记录 , 则要比较事务编号:如果已存在的那条事务编号小于当前的 , 则用当前的事务编号覆盖 , 否则不做任何处理(后面会解释这么做的原因);
文章插图
现在我们看看当撤销流程执行时 , 被撤销的扣款事务处于不同状态下的情况:
- 扣款事务执行失败 。此时压根不会产生扣款;
- 扣款事务已经执行完毕 , 产生了实际扣款 。此时撤销流程会撤销掉这笔扣款;
- 扣款事务正在执行中 , 还没有写库 , 但稍后会写库 。扣款事务实际写库之前 , 会先检查是否存在对该事务的撤销记录 , 因为先前撤销流程已经写入了一条对该事务的撤销记录 , 扣款事务此时会查到撤销记录 , 从而阻止本次扣款事务写库(本次事务主动回滚) 。
现在我们解释下为何要用当前时间的毫秒时间戳作为事务编号 。
回到上面车主王某的场景 。王某第一次用卡支付超时 , 于是他决定重试 。该场景中 , 卡系统接收到同一笔订单的两次扣款事务以及一次撤销事务 。假如两次事务都尝试写库 , 那么当后面的事务(不一定是第二次扣款的那个)尝试写库时 , 肯定已经存在一条扣款记录 , 此时后面这个事务要如何做?
- 用后者的事务编号替换掉前者的 。
- 不做任何处理 。
两次事务的执行逻辑完全相同 , 产生的扣款记录数据也是完全相同的——除了事务编号和扣款时间 。这里的关键是 , 我们无法确定第一次扣款、第二次扣款、对第一次扣款的撤销这三个请求写库的先后顺序 。
所以 , 如果采用方案 1 , 替换事务编号 , 那么当第二次的提交先写库时 , 后面事务(第一次提交的扣款请求)的替换会导致事务编号变成了待撤销的那个 , 因而很可能会被撤销掉 , 这就会导致用户付的钱莫名其妙被退回了 。
如果采用方案 2 , 不做任何处理 , 那么当第一次的提交先写库时 , 事务编号就一直是待撤销的那个 , 也会被撤销掉 , 导致用户付的钱莫名其妙被退回 。
也就是说 , 无脑替换或不替换都是有问题的 。
文章插图
推荐阅读
- 窗帘为什么不能用蓝色?
- 网络编程之网络丢包故障如何定位?如何解决?
- 拖放式Glade界面设计与 FreeBASIC编程 + LinuxMint
- 签证为什么要存款证明?
- 为什么说番鸭有毒?
- 泡红茶时为什么起沫,安息乌龙茶的什么是白瓷
- |为什么生完孩子后,妻子宁可带孩子也不想出去上班?
- |为什么不在工作场所随便道歉?
- 为什么国外歌手不在中国开演唱会?
- 红茶走水不充分,红茶为什么泡不开