当前位置:网站首页>消息队列之通过幂等设计和原子锁避免重复退款
消息队列之通过幂等设计和原子锁避免重复退款
2022-06-29 06:37:00 【weixin_2047679575】
幂等性设计
我们回顾下上篇教程创建的 RefundAttendee 任务类,它会做两件事情 —— 退款和发送邮件:

如果这个队列任务执行失败,由于配置了 tries 值为 3,所以会重试,这里就存在一个隐患:如果退款成功,但邮件发送异常导致任务失败重试,就存在重复退款的问题。
我们在设计队列任务的执行时,由于存在重试机制,所以要考虑多次执行的幂等性,对于当前这个退款场景而言,就是多次执行 RefundAttendee,也不会出现重复退款。要实现这个幂等性,可以在 handle 方法中先判断该用户是否已经退款,只有未退款的用户才会发起退款操作:

双重检查
需要注意的是,调用第三方支付服务 API 进行退款会发起网络请求,如果退款成功,但是由于网络问题导致返回响应失败,那么永远也不会将订单标记为已退款。同样的问题也肯能会出现在退款后更新订单退款状态时。
这种情况下,只有第三方支付服务提供商才是唯一可信的数据源,只有他们知道是否完成了退款操作。因此,我们需要基于支付服务商提供的 API 来判断是否退款成功,而不是本地数据库字段:

这一点在开发与第三方服务 API 交互的功能时非常重要,也是最容易出问题,但又不方便排查的地方。
和 refund 方法一样,wasRefunded 方法也会通过 HTTP 请求获取服务商那里的退款状态:

三重检查与原子锁
虽然我们主观上不会将同一个 RefundAttendee 多次推送到队列,但是意外总是难免发生。
如果两个队列处理器进程同时获取到这两个执行同一笔退款的任务,可能会存在同时发送 HTTP 请求并进行退款的操作,进而出现并发安全问题:

要避免这个问题,可以使用原子锁来保证每笔退款操作串行执行:

在执行任何操作前,先基于缓存键 refund.{id} 获取一个原子锁,只有获取到这个锁,才能执行退款操作,而由于分配锁的操作是原子操作,其他并发执行的处理器进程执行到这里的时候由于锁已被其他进程获取就直接跳过了。
任务执行完成后,会自动释放这个锁。
这里我们使用了基于缓存的原子锁,这是 Laravel 底层提供的功能,除此之外,还有基于 Redis、数据库的实现版本,具体可以参考学院君之前发布的基于 Redis 实现分布式锁及其在 Laravel 底层的实现源码 这篇教程。
通过锁管理重试
如果队列处理器进程在获取到锁之后执行任务期间崩溃,而锁又还没有来得及释放,RefundAttendee 会被认为执行成功,即使还没有执行退款操作 —— 因为在后续重试的时候,这把锁还在。
要解决这个问题,需要设置原子锁的过期时间并确保下次重试的时候锁已经释放:

我们通过 Cache::lock 的第二个参数告知锁的过期时间是 10s。
由于处理器进程崩溃后,下次重试是在 90s 之后(参考上篇教程避免队列任务重复执行中的介绍),此时锁已过期,就会重新获取到一把新锁来执行这个任务。
配置任务延迟
如果是任务执行期间出现异常,锁会自动释放,但是如果释放锁的时候出问题咋整?
我们已经知道锁会在 10s 后过期,但是在此期间,重试任务时由于锁还在,就不会执行这个任务。要解决这个问题,可以在这个任务类中设置一个合理的延迟时间:

如果任务执行失败,会在 11s 之后执行,这样,就可以确保即便锁释放失败,也能正常执行这个任务了。
注意:如果队列任务执行失败,11s 后重试,如果是队列处理器进程崩溃,则是 90s 后重试,这两个重试时间是不一样的。
边栏推荐
- WDCP accesses all paths that do not exist and jumps to the home page without returning 404
- Annual inventory review of Alibaba cloud's observable practices in 2021
- JDBC | Chapter 6: simple use of database connection pool
- [MySQL technology topic] technical analysis and guide for analyzing the high availability architecture of MySQL
- 2022.02.15 - 240. Lucky number in matrix
- jetson tx2
- Client and server working modes of JVM
- Mongodb paging method
- 多线程工具类 CompletableFuture
- How to do the performance pressure test of "Health Code"
猜你喜欢

Annual inventory review of Alibaba cloud's observable practices in 2021

融入STEAM教育的劳动技能课程

It is the only one in China that Alibaba cloud container service has entered the Forrester leader quadrant

Draw multiple ROC curves on a graph

Easy to understand TCP four waves (multi picture explanation)

Illustrate plug-in -- AI plug-in development -- creative plug-in -- astute graphics -- path width style function

UVM验证平台

二叉树的迭代法前序遍历的两种方法

Hyperledger Fabric 2. X custom smart contract

转:侯宏:企业数字化转型的关键不是技术,而是战略
随机推荐
关于 localStorage 的一些高阶用法
作为一名合格的网工,你必须掌握的 DHCP Snooping 知识!
MySQL add / delete / modify query SQL statement exercise yyds dry goods inventory
jetson tx2
Honeypot based on MySQL load data local INFILE
Li Kou daily question - day 30 -1281 Difference of sum of bit product of integer
Illustrate plug-in -- AI plug-in development -- creative plug-in -- astute graphics -- length and angle measurement function
Why should enterprises do more application activities?
Open source 23 things shardingsphere and database mesh have to say
Print Yanghui triangle
UVM验证平台
json tobean
Redistemplate handles hash integer type problem resolution
Qt 程序打包发布-windeployqt工具
Some high-level usage of localstorage
Idea use
[deep learning] - maze task learning I (to realize the random movement of agents)
Ribbon 服务调用与负载均衡
Subtotal of C language -- basic data types and their representations
Flutter is configured with a domestic image and connected to the real machine