高并发下订单状态更新

2022-03-09 10:37:03 +08:00
 frank1256

场景

订单存在 flag 字段,0 未支付,1 支付中,2 支付完成。 发起支付场景中,会先查询订单状态是否为 0 ,然后更新为 1 ,并且调用第三方支付系统获取 h5 的支付地址(耗时操作)。用户在 h5 上完成支付后,第三方支付系统会异步通知到后台服务。进行订单更新动作,并保存流水号。

具体代码

支付发起

支付发起之前,会查库,判断 flag 是否为 0 ,可以的才会继续

异步通知

接收到第三方系统的异步通知后,会查库,判断 flag 是否为 1 ,可以的话才会更新订单。

问题

高并发下,第一个线程查库,查到 flag 是 0 ,在数据库没更新完成的情况下,第二个线程也来查库,查到是 flag 也是 0.同时发起了支付。如何防止这种场景呢?假设在单节点情况下,直接加 synchronized ,可以避免。但是这样的话,是对所有的线程都进行了阻塞,实际情况下,我们只是要对相同订单进行阻塞。不同订单不进行阻塞的。

在异步回调的情况也是一样,也是要先查订单状态 flag 为 1 的话,才会进行下一步动作,如果并发情况下出现了 2 个线程都查到是 flag 为 1 怎么处理?

目前思路

加锁,但是锁了所有的线程,订单 1 多个线程同时发起支付的话,需要加锁阻塞,只能有一个发起成功,但是不能影响订单 2 的发起支付。实际上只是为了锁同一笔订单。

用乐观锁,然后数据库 update 的时候,where flag=某个条件。一定会有一个线程更新失败,更新成功的才会进行后续操作。这样的话,会对数据库有影响吗?

想请问大佬们,这种先查库得到条件,再根据条件做后续动作的场景,在高并发下应该如何处理呢?

7970 次点击
所在节点    Java
78 条回复
faceRollingKB
2022-03-09 14:33:59 +08:00
不要锁不要队列,只看最终一致性的话,数据库的压力最小
sanggao
2022-03-09 14:51:55 +08:00
哪来的并发? 这个是幂等
ZSeptember
2022-03-09 14:53:29 +08:00
不是并发情况,数据库乐观锁就可以了。
没有必要,不要引入分布式锁。
h123123h
2022-03-09 15:14:26 +08:00
你的问题其实有点奇怪,一般来说一个订单号对应一个商品,一个订单号只属于一个人的,不会属于多个人,那就不会存在高并发,只会存在重复提交问题,这个问题乐观锁就能解决。 除非你说的场景是秒杀情况
godfunc
2022-03-09 15:35:24 +08:00
我做的支付系统是这样处理的 update order set order_no = xxx, status = 2 where trade_no = xx and status = 1 ,update 成功就继续
tyqing
2022-03-09 15:53:46 +08:00
我们系统是重复支付就退款,一般这种现象极少,除非你们数据库真的性能很差排队入库。
支付回调的地方加 redis 分布式锁保证只有同一个线程在执行支付回调操作,查 redis 中的订单状态和 mysql 的状态判断取最新值,不涉及到数据库锁。
markgor
2022-03-09 16:09:45 +08:00
这个场景,我只能猜测到是不是代付场景?否则怎么会 第一个线程拉起支付,另一个也拉起支付呢....


如果是代付场景,其实我觉得为了保证付款成功率,没必要限制只能一个人支付,
假设 A 和 B 同时支付了,按通知时间为准,通过事务更新订单状态,另一笔支付通知走事务时发现装维为支付完成的就走退款接口。
Hug125
2022-03-09 16:14:32 +08:00
@xujihua #1 行锁+补充一个 redis 分布式锁, 这样多实例部署的服务也不会出问题了
xsqfjys
2022-03-09 16:18:17 +08:00
上 redis 咯
leafre
2022-03-09 17:09:33 +08:00
处理好幂等即可
watcher
2022-03-09 17:23:58 +08:00
这个业务不存在高并发
codeMore
2022-03-09 17:26:15 +08:00
我感觉也是幂等问题,控制好订单生成就行了
daimubai
2022-03-09 17:37:59 +08:00
是幂等性问题,用乐观锁或者 redis
Boolean lock = redisLock.tryLock("key", "1", 10L, TimeUnit.SECONDS);
if (lock) {
try {
//查库,调支付等
return "success";
} finally {
redisLock.unlock(lockName, "1");
}
}
return "操作过于频繁,稍后再试!";
daimubai
2022-03-09 17:40:06 +08:00
高并发是指多个线程抢一个资源。
zzfer
2022-03-09 17:48:21 +08:00
up 可以看一下接口幂等校验
Nillouise
2022-03-09 17:54:20 +08:00
第一个线程查库,查到 flag 是 0

感觉不需要查,直接 update flag = 1 where flag=0 就可以了,跟 ksedz 说的思路一样。

另外,这里直接用数据库里的状态当锁不行,非要弄一个分布式锁的理由是什么?你请求的资源只涉及单个数据库,直接数据库内部操作就可以了,按我理解,分布式锁是锁住涉及多个数据库的资源,或者根本不是数据库的资源,比如一些对第三方接口的锁定。
lingalonely
2022-03-09 17:59:35 +08:00
你这不是单体的应用吧,如果是分布式的话,还高并发,直接用分布式锁最好,至于使用数据库作支持还是 redis ,zookeeper ,取决于系统当前有什么,不要随便引入新组件就好
littlewing
2022-03-09 18:00:07 +08:00
乐观锁 注意 ABA 问题
Amayadream
2022-03-09 18:01:51 +08:00
1.可以考虑合并未支付和支付中的状态,因为不论是什么状态,接到第三方状态都应该进行处理(自动退款或完成订单),多一个状态只能增加状态机的复杂度
2.这种场景下存在的问题是重复支付而不是高并发,这个需要从多维度共同解决,例如前端防重复支付(支付按钮防重复点击,从第三方支付跳转回来主动调用后端查询订单状态等),对后端来说最好拆分交易和支付订单,在一笔交易订单已经存在支付完成的支付单时直接返回支付成功的结果,一笔交易订单存在多笔成功的支付订单时(即重复支付)进行自动退款处理
DinnyXu
2022-03-09 19:44:15 +08:00
楼上各位大佬都已经补充了很多种实现方式,我来说下海量消息消费的时候这种场景 rocketmq 是怎么处理的。rocketmq 有个消费模式,是将 id 进行 hash ,相同的 id 一定是一个线程来操作的。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/839046

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX