
有一个用券逻辑,券有金额/剩余金额属性。并发场景有很多请求使用同一张券,我用到了 select for update 的方式进行锁券,当一个请求消耗完券之后,update 记录(券剩余金额),然后会释放锁,下一个请求再继续用券。
但现在同一张券,券金额比较大且并发场景的情况下,某些请求耗时会比较长,因为等待锁释放
请教下,这里有什么优化点么,合并请求之类的改动太大,需要上游配合
1 yankebupt 2024-11-25 16:09:18 +08:00 我完全不懂,但是按金额切分券,分别锁,闲时再合并? |
2 felo OP |
3 yiqiao 2024-11-25 16:16:43 +08:00 券不是发放到个人账户上然后消费吗? OP 问的是发的券超库存了吗? |
4 InDom 2024-11-25 16:20:54 +08:00 券余额也是余额, 所以你要问的是并发余额怎么减. 问题是单用户会有并发么? 如果是全局有一个“该券能用多少次、最多全局抵多少钱”. 那就是减库存问题了. 最好的就是落到缓存,然后用队列按顺序处理. |
5 yankebupt 2024-11-25 16:22:51 +08:00 当用户从页面点选那张优惠券还没有提交时,立刻把券切成两张,并锁住相应金额的那张,如果用户提交,就更新,如果不更新页面关闭了,把两张券再合起来...... |
6 yankebupt 2024-11-25 16:26:00 +08:00 或者你也可以提供一个功能让用户自己切然后一张一张用(很丑陋,但代码最少 |
7 byehair 2024-11-25 16:27:43 +08:00 1 、内存队列(或者 MQ ),排队对单券扣减,减少无畏的 CPU 消耗、加解锁消耗 2 、使用 Redis 的 decrby 进行扣减,通过内存扣减提升性能 3 、如果是多次的金额相同,还可以使用券预置拆分策略,减少锁竞争 |
8 lcy630409 2024-11-25 16:30:53 +08:00 就是类似京东的 e 卡 说是卷 就是一个单独的存储余额 |
9 clysto 2024-11-25 16:33:30 +08:00 用 redis 缓存剩余金额,直接在 redis 中 decrby 可行吗? |
10 yankebupt 2024-11-25 16:35:46 +08:00 楼上说的 MQ 比较靠谱。如果还没屎山堆到动不了数据结构,可以考虑 |
11 esee 2024-11-25 16:38:21 +08:00 你 update 剩余金额一定要放在处理完业务逻辑的事务的最后么,可不可以加锁后就 update 金额 释放锁,然后去处理其他业务逻辑,如果处理出错,订单失败,则回滚剩余金额。。这样慢不了多少。。 |
13 yinmin 2024-11-25 17:06:12 +08:00 创建事务,直接跑 sql: update tblStoredValueCard set fldBalance=fldBalance-@value where fldCardID=@id and fldBalance>=@value 然后获取变更记录数,如果变更记录为 0 条就回滚,如果>0 就写其它表,然后 commit trans |
14 lyxxxh2 2024-11-25 17:09:57 +08:00 赞同 11 楼。 读写锁 -> 读取数据库为变量 -> 修改 -> 解锁。 # 也就这部分会影响到其他用户 time.sleep(.5)# 业务逻辑 except Exception as e: 加读写锁,回滚。 (怕其他请求读取为变量,所以读也锁) ps: 似乎有点不对,"a 请求"回滚,"b 请求"的内存变量又不会改。 (不过很难发生) |
16 yinmin 2024-11-25 17:15:23 +08:00 “显性锁+事务处理”会扼制脏记录特性,可能会严重影响数据库性能 |
19 esee 2024-11-25 17:24:22 +08:00 @felo 个人感觉首先在处理逻辑上优化会比较好,像我刚刚说的加锁后立刻 update 后释放延迟不了多少,贸然引入第三方组件比如消息队列会增加业务复杂度,久而久之就变屎山了,尤其是系统本来就没用到消息队列的情况下。。。 |
20 yinmin 2024-11-25 17:37:06 +08:00 @felo 一个事务会有多条 sql 交互,应用服务器与数据库服务器交互 1 次至少 1-2ms ,如果一个事务有 5 次 sql 交互,总耗时 6-10ms ,加锁的情况下 100 次事务处理就累积耗时 1 秒+,感知明显。优化方式是用数据库的存储过程,减少应用服务器与数据库服务器的网络延时。 |
21 strong>felo OP @esee 对,我个人倾向也是不想用别的组件,链路越复杂问题越多。看了大家回复我感觉可以用乐观锁代替 select for update ,但乐观锁失败之后的重试处理以及如何保证请求顺序还得再看看 |
22 xuelu520 2024-11-25 17:42:33 +08:00 你对券的基础理解是不是错了? 看你说的是所有人共用一张券,实际你得每个人都发一张券或领一张,然后对券进行核销 |
24 esee 2024-11-25 18:06:13 +08:00 @felo 可是你的场景就是高并发的情况,感觉乐观锁并不适合,如果冲突会产生大量重试,数据库负担会加重,select for update 感觉就够了 |
26 admin7785 2024-11-25 18:43:57 +08:00 via iPhone 看起来有点像预算池的场景:比如:发了 100 张券,总共 100 元的预算,每个人都能用,先到先得,预算用完即止。 |
27 ucando 2024-11-26 10:54:50 +08:00 有点不是太理解需求,如果是后付费,是不是可以理解成用户有多个需要扣费的业务,然后在某一个时间点需要自动扣费?以我的理解这部分并不需要并行处理,因为不是用户通过多个客户端同时操作进行的,直接照#13 楼的处理方式就行了。如果需要返回余额,还可以使用 returning fldBalance 来获取最新的余额,select 都省了 |
28 INCerry 2024-11-26 13:19:12 +08:00 用 actor 模式,每个券都单线程处理 |
29 asAnotherJack 2024-11-26 16:28:17 +08:00 现在慢是因为有大事务吗,如果是的话可以考虑把大事务拆成多个小事务,再弄个状态机进行处理,比如分成多个步骤,扣减优惠券、扣减余额、其他业务逻辑 abc…,完成一个操作后就更新为另一个状态,通过状态流转,一步一步操作,某一步出错时区分错误类型决定是重试还是回滚之前的操作进入失败状态。这样每一个操作都耗时不长,不会因为操作优惠券的一个锁拖慢其他步骤。另外注意做好幂等,尤其是失败回滚时,不要退费退多了 |
31 yankebupt 2024-11-27 10:07:32 +08:00 |
32 realpg PRO @yankebupt #31 处理过程唯一话后设计好可以利用 MQ 但是不能分解步骤放到不可靠的 MQ 里面 大部分用 MQ 的都是设计不到位 涉及金钱最好是多账本完全一致才能操作 然后确保处理队列的唯一性 各种异常要充分照顾到并且能自动化处理 如果系统或者数据库是分布式的 就更麻烦 这个真的不太建议自己能力不足单纯靠问的去设计架构 如果评估自己能力不足设计这个架构,就简单化成单处理逻辑 你这种业务 基本就是阿里云的通用储值卡 完全一摸一样的逻辑 跟京东 E 卡也有点区别 因为 E 卡是主动使用 顶天多人登陆账号同时使用 |