异步处理会议门票支付及竞态条件处理


好了,经过全国和全世界人民的共同努力,假设现在新冠病毒在全球范围内终于被控制住了,各种行业开始复苏,各种国际和行业交流会议又开始恢复售票了。

通过队列任务异步处理支付

这里我们以信用卡支付为例,在创建订单时,只需要填写好信用卡卡号和 Token,就可以完成订单的自动支付,更加方便,由于这里会涉及到第三方支付服务的网络调用,为了优化用户体验,避免长时间等待,我们通过队列任务异步处理订单的支付流程:

优雅地处理支付失败

如果用户支付失败,BillingProvider::invoice 方法会抛出异常,这种情况下,SendTicketInformation 任务不会被推送到消息队列,但是 attendee 实例却被存储到了数据库,这会导致数据不一致,为了避免这个问题,可以使用数据库事务来包裹整个处理流程:

这样一来,支付失败会抛出异常后,数据库事务会进行回滚,将前面创建的 attendee 数据清理掉。

竞态条件及解决方案

线上运行这段代码期间,你可能会在 failed_jobs 数据表中看到 SendTicketInformation 运行失败的记录,原因是 ModelNotFoundException 异常,这是因为队列处理器是在和处理 Web 请求不同的独立 PHP 进程中执行,也就是说,数据库事务和队列任务处理是在两个互相独立的 PHP 进程中执行的。

当支付任务推送到消息队列后,队列处理器进程可能在事务提交之前就获取了这个任务并执行,所以当它获取任务类并试图在数据库中查询时,发现这个记录还不存在,我们把这种多个进程/线程试图操作同一个资源的情况叫做「竞态条件」。

如果基于数据库作为队列驱动则不存在这个问题,因为是在同一个事务中,只有事务提交之后才会将任务插入数据表。

要避免这个问题,有两个解决方案。

要么将任务分发放到数据库事务提交之后执行:

要么延迟任务分发,这个延迟时间要足够长,确保分发时事务已提交:


Vote Vote Cancel Collect Collect Cancel

<< 上一篇: 监控退款任务批处理过程

>> 下一篇: 业务流量峰值检测和数据一致性