从储值卡充值业务看分布式事务的设计

公司有一项储值卡充值业务:客户在微信公众号开通储值卡服务,通过微信支付往卡里面充值,充值成功后客户可收到消息通知,并进行消费。

看起来是一项很简单的业务,最初我们储值卡团队的实现也确实很简单。我们看看最初的实现:


储值卡充值最初版本实现

相信聪明的你一眼就能看出问题:

  1. 压根没有考虑分布式事务一致性,比如第 12 步根本没有考虑卡系统充值失败的情况该如何处理,而是默认其一定能成功;
  2. 大部分的处理都是放在前端业务系统(除了这里的公众号系统,还有 POS 机系统,而 POS 机是通过调公众号系统接口来实现的);
  3. 第 4 步直接下单,第 5 步直接调微信支付,压根没有跟卡系统有任何通信:这里默认用户的充值行为一定是合法的;
  4. 在微信的支付回调中(第 10 步往后),是先处理一系列业务逻辑,最后才调充值接口,这里也是默认卡充值一定能成功;

看到这里你可能会大呼开发人员是不是没长脑子?

实际情况是,这个版本的开发是几年前的事情了,那时候公司还是创业早期,第一目标是尽快上线能用,而且客户量没有那么大,虽然中间也出现过一些数据不一致的情况,也都通过人工处理了事了。

随着公司业务的发展,用户量越来越大,而且还要和第三方合作(储值卡作为一种支付方式提供给第三方使用),问题出现得也越来越频繁,不得不将这块提上重构议程。

那么,针对上面提的几点问题,我们大体能想到如下重构项:

  1. 将充值业务逻辑从前端系统剥离,做成单独的服务;
  2. 在下单前,先调一下卡系统接口,检查用户的充值行为是否合法,避免后面不必要的麻烦;
  3. 在支付回调中,处理充值失败的场景;

初步设计如下:


储值卡充值:第一版重构设计

这里我们重点讨论下对第 14 步(卡充值接口返回结果)的处理:

  1. 如果返回充值成功,那万事大吉,该干嘛干嘛;
  2. 如果失败呢?可能的处理方式如下:
    1. 继续重试,最多重试 3 次,如果成功了,万事大吉;
    2. 如果上面重试还是失败,那么调微信退款,并将订单状态改成充值失败;

骚年你等等!
你说什么?重试失败了就去退款?

实践中,远程调用失败的一个很大原因是网络超时(而超时的很大原因又是对方负载过高),而面对超时,我们是不知道对方到底有没有处理成功的,万一这边把钱退掉了,那边又充值成功咋办?(我们是 SaaS 服务商,这时真正的损失方是我们的商户,而商户无疑会找我们索赔的)


立即退款会带来问题

一种方案是:
在多次重试失败后发起微信退款之前,先调卡系统查询接口,如果查询结果是充值成功,则不退款,继续后续流程,否则发起退款;

该方案在实际中也基本行不通,因为如果那段时间网络有问题或者对方服务器负载高,查询也有很大概率失败,或者就算查成功了并返回充值记录不存在,也有可能之前调的充值接口还在跑(比如处于锁等待状态)。

面对该问题,我们决定用定时任务来解决。在微信支付回调中,如果多次调卡充值接口失败,我们不发起退款,也不进行后续流程,而是在数据库中写入一条异常记录,然后结束本次处理。

在定时任务中(比如 10 分钟一次),我们取出那些异常记录,调卡系统相关接口核对最终状态,如果充值成功了,则补充执行充值成功的后续流程,否则发起微信退款,并执行其他充值失败流程(如改订单状态,给用户发通知、回调业务系统等)。

为了防止钱退了后卡又充值成功,定时任务中只处理 1 小时前的数据。

另一个隐藏的问题是,在前面的充值流程中,直到微信支付回调,卡系统都没有关于这次充值行为的任何记录。这可能会导致后续一系列问题,其中一个问题是,在最初下单(步骤 5)到最终充值(步骤 13)这段时间内,一旦任何变量(充值规则)发生改变,这次充值就有可能会失败(或者导致数据差错)。这个时间差短则几十毫秒,长则几分钟十几分钟都有可能。另一个次要问题是,一旦发生充值异常,卡系统自身是不知情的(因为没有任何记录),对卡系统的任何查询也都不会反映这次充值行为。

为了解决该问题,我们引入预充值的概念。在下单后调微信支付前,先同步调卡系统的预充值接口,该接口计算充值合法性并生成一条预充值记录,该记录包含充值账号、充值金额、支付金额、充值单号等关键信息,状态为“充值中”。

在微信支付回调中,将预充值状态改成“充值成功”,并处理一些其他逻辑。
综合,最终方案如图:


最终版本

总结:

  1. 任何涉及到分布式事务的地方都是复杂的,必须小心设计;
  2. 远程过程处理不具有时序性,设计时必须考虑进去(如退款后最终又充值成功的情况);
  3. 现实中的设计很多时候做不到完美,我们要做的是保证出现异常的概率最小化并设置最终检查哨兵(上面的定时任务);
  4. 就算增设了哨兵,也不排除需要人工干预的可能性,因而在设计上尽量保证需要人工干预时有迹可循、方便处理;
  5. 远程调用需要有重试机制(上面只说了对充值接口的重试,其实其他接口也一样需要有重试机制);
  6. 记住一句话:网络总是不可靠的;
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容